Highly ambitious ATProtocol AppView service and sdks
138
fork

Configure Feed

Select the types of activity you want to include in your feed.

update cli init command, one shot app view

+777 -197
+378 -28
packages/cli/src/commands/init.ts
··· 1 1 import { parseArgs } from "@std/cli/parse-args"; 2 2 import { join, dirname } from "@std/path"; 3 3 import { ensureDir } from "@std/fs"; 4 + import { cyan, green, bold, dim } from "@std/fmt/colors"; 4 5 import { logger } from "../utils/logger.ts"; 5 6 import { getAllTemplates } from "../templates/embedded.ts"; 7 + import { generateSliceName, generateDomain } from "../utils/name_generator.ts"; 8 + import { createAuthenticatedClient } from "../utils/client.ts"; 9 + import { ConfigManager } from "../auth/config.ts"; 10 + import { pullCommand } from "./lexicon/pull.ts"; 11 + import { pushCommand } from "./lexicon/push.ts"; 12 + import { codegenCommand } from "./codegen.ts"; 13 + import { dasherize } from "../utils/strings.ts"; 14 + import { 15 + SYSTEM_SLICE_URI, 16 + REFERENCE_SLICE_URI, 17 + DEFAULT_API_URL, 18 + DEFAULT_AIP_BASE_URL, 19 + } from "../utils/constants.ts"; 6 20 7 21 export async function initCommand(args: string[], _globalArgs: unknown) { 8 22 const parsed = parseArgs(args, { ··· 19 33 return; 20 34 } 21 35 22 - const projectName = parsed.name || parsed._[0] as string; 36 + // Check if we're inside an existing project 37 + const currentDir = Deno.cwd(); 38 + const projectIndicators = ["deno.json", "slices.json", ".git"]; 39 + 40 + for (const indicator of projectIndicators) { 41 + try { 42 + await Deno.stat(join(currentDir, indicator)); 43 + logger.error( 44 + `Cannot initialize a new project here - found ${indicator} in current directory.` 45 + ); 46 + logger.info( 47 + "Please run this command from outside your project directory." 48 + ); 49 + Deno.exit(1); 50 + } catch { 51 + // File doesn't exist, which is what we want 52 + } 53 + } 54 + 55 + let projectName = parsed.name || (parsed._[0] as string); 56 + let wasNameGenerated = false; 23 57 58 + // If no project name provided, generate a random one 24 59 if (!projectName) { 25 - logger.error("Project name is required"); 26 - console.log("Usage: slices init <project-name> or slices init --name <project-name>"); 27 - Deno.exit(1); 60 + projectName = generateSliceName(); 61 + wasNameGenerated = true; 62 + 63 + console.log( 64 + `\n${cyan("📦 No project name provided, generated one for you:")}` 65 + ); 66 + console.log(` Project name: ${bold(projectName)}`); 67 + 68 + // Ask for confirmation 69 + const proceed = confirm("\nDo you want to proceed with this name?"); 70 + if (!proceed) { 71 + logger.info( 72 + "Initialization cancelled. Run 'slices init <project-name>' to specify your own name." 73 + ); 74 + return; 75 + } 76 + } else { 77 + // Dasherize user-provided name 78 + projectName = dasherize(projectName); 28 79 } 29 80 30 81 // Validate project name 31 82 if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) { 32 - logger.error("Project name can only contain letters, numbers, hyphens, and underscores"); 83 + logger.error( 84 + "Project name can only contain letters, numbers, hyphens, and underscores" 85 + ); 33 86 Deno.exit(1); 34 87 } 35 88 ··· 46 99 // Directory doesn't exist, which is what we want 47 100 } 48 101 49 - logger.info(`Creating new Deno SSR project: ${projectName}`); 102 + logger.info(`Creating project: ${projectName}`); 103 + 104 + // Try to create a slice with the same name 105 + let sliceUri: string | undefined; 106 + let sliceDomain: string | undefined; 107 + let oauthClientId: string | undefined; 108 + let oauthClientSecret: string | undefined; 109 + let oauthRedirectUri: string | undefined; 110 + const config = new ConfigManager(); 111 + await config.load(); 112 + 113 + if (config.isAuthenticated()) { 114 + try { 115 + // If we generated the name, use it for the domain too 116 + // Otherwise, prompt for domain 117 + if (wasNameGenerated) { 118 + sliceDomain = `network.slices.${projectName}`; 119 + } else { 120 + // User provided a name, ask for domain 121 + console.log(`\n${cyan("🌐 Setting up your slice domain:")}`); 122 + console.log( 123 + ` ${dim("Format: com.example (reverse domain notation)")}` 124 + ); 125 + console.log(` ${dim("Leave blank to generate one automatically")}`); 126 + 127 + const userDomain = prompt( 128 + "\nEnter your domain (or press Enter to generate):" 129 + ); 130 + 131 + if (userDomain && userDomain.trim()) { 132 + // Validate domain format (basic check for at least one dot) 133 + if (!userDomain.includes(".")) { 134 + logger.error( 135 + "Domain must contain at least one dot (e.g., com.example)" 136 + ); 137 + Deno.exit(1); 138 + } 139 + sliceDomain = userDomain.trim(); 140 + } else { 141 + // Generate a domain 142 + sliceDomain = generateDomain(); 143 + console.log(` Generated domain: ${bold(sliceDomain)}`); 144 + } 145 + } 146 + 147 + console.log(`\n${cyan("🌐 Creating a slice on the Slices network:")}`); 148 + console.log(` Slice name: ${bold(projectName)}`); 149 + console.log(` Domain: ${bold(sliceDomain)}`); 150 + console.log( 151 + ` ${dim("This will be your unique namespace for collections")}` 152 + ); 153 + 154 + const createSlice = confirm("\nCreate this slice?"); 155 + if (!createSlice) { 156 + logger.info( 157 + "Skipping slice creation. You can create one later at https://slices.network" 158 + ); 159 + } else { 160 + logger.info("Creating slice..."); 161 + const client = await createAuthenticatedClient( 162 + SYSTEM_SLICE_URI, 163 + DEFAULT_API_URL 164 + ); 165 + 166 + // Check if domain already exists in the system slice 167 + const existingSlices = await client.network.slices.slice.getRecords({ 168 + where: { domain: { eq: sliceDomain } }, 169 + limit: 1, 170 + }); 171 + 172 + if (existingSlices.records.length > 0) { 173 + logger.error(`A slice with domain '${sliceDomain}' already exists`); 174 + logger.info("Please try again with a different name or domain"); 175 + Deno.exit(1); 176 + } 177 + 178 + const result = await client.network.slices.slice.createRecord({ 179 + name: projectName, 180 + domain: sliceDomain, 181 + createdAt: new Date().toISOString(), 182 + }); 183 + 184 + sliceUri = result.uri; 185 + logger.success(`Created slice: ${sliceUri}`); 186 + 187 + // Ask if they want to create OAuth client 188 + const createOAuth = confirm( 189 + "\nWould you like to create OAuth credentials for this slice?" 190 + ); 191 + if (createOAuth) { 192 + logger.info("Creating OAuth client..."); 193 + 194 + // Default redirect URI for local development 195 + const redirectUri = "http://localhost:8080/oauth/callback"; 196 + 197 + try { 198 + const oauthResult = 199 + await client.network.slices.slice.createOAuthClient({ 200 + sliceUri: sliceUri, 201 + clientName: `${projectName} Development`, 202 + redirectUris: [redirectUri], 203 + grantTypes: ["authorization_code", "refresh_token"], 204 + responseTypes: ["code"], 205 + scope: "profile openid atproto transition:generic", 206 + }); 207 + 208 + // Store OAuth credentials to add to .env later 209 + oauthClientId = oauthResult.clientId; 210 + oauthClientSecret = oauthResult.clientSecret; 211 + oauthRedirectUri = redirectUri; 212 + 213 + logger.success("Created OAuth client!"); 214 + console.log(` Client ID: ${cyan(oauthResult.clientId)}`); 215 + console.log( 216 + ` ${dim("Client secret will be added to your .env file")}` 217 + ); 218 + } catch (oauthError) { 219 + const err = oauthError as Error; 220 + logger.warn(`Could not create OAuth client: ${err.message}`); 221 + logger.info( 222 + "You can create OAuth credentials later at https://slices.network" 223 + ); 224 + } 225 + } 226 + } 227 + } catch (error) { 228 + const err = error as Error; 229 + logger.warn(`Could not create slice: ${err.message}`); 230 + logger.info("You can create a slice later at https://slices.network"); 231 + } 232 + } else { 233 + logger.info("Not authenticated - skipping slice creation"); 234 + logger.info( 235 + "Run 'slices login' to authenticate, then create a slice at https://slices.network" 236 + ); 237 + } 50 238 51 239 try { 52 240 // Create target directory ··· 55 243 // Extract embedded templates 56 244 await extractEmbeddedTemplates(targetDir, projectName); 57 245 246 + // Update .env.example with slice URI if we created one 247 + if (sliceUri) { 248 + const envExamplePath = join(targetDir, ".env.example"); 249 + try { 250 + let envContent = await Deno.readTextFile(envExamplePath); 251 + envContent = envContent.replace( 252 + /SLICE_URI=.*/, 253 + `SLICE_URI="${sliceUri}"` 254 + ); 255 + await Deno.writeTextFile(envExamplePath, envContent); 256 + } catch { 257 + // Ignore if can't update .env.example 258 + } 259 + } 260 + 261 + // Create .env file with OAuth credentials if we have them 262 + if (oauthClientId && oauthClientSecret) { 263 + const envPath = join(targetDir, ".env"); 264 + const envExamplePath = join(targetDir, ".env.example"); 265 + 266 + try { 267 + // Read the example file as a template 268 + let envContent = await Deno.readTextFile(envExamplePath); 269 + 270 + // Replace with actual values 271 + if (sliceUri) { 272 + envContent = envContent.replace( 273 + /SLICE_URI=.*/, 274 + `SLICE_URI="${sliceUri}"` 275 + ); 276 + } 277 + envContent = envContent.replace( 278 + /OAUTH_CLIENT_ID=.*/, 279 + `OAUTH_CLIENT_ID="${oauthClientId}"` 280 + ); 281 + envContent = envContent.replace( 282 + /OAUTH_CLIENT_SECRET=.*/, 283 + `OAUTH_CLIENT_SECRET="${oauthClientSecret}"` 284 + ); 285 + envContent = envContent.replace( 286 + /OAUTH_REDIRECT_URI=.*/, 287 + `OAUTH_REDIRECT_URI="${oauthRedirectUri}"` 288 + ); 289 + envContent = envContent.replace( 290 + /OAUTH_AIP_BASE_URL=.*/, 291 + `OAUTH_AIP_BASE_URL="${DEFAULT_AIP_BASE_URL}"` 292 + ); 293 + envContent = envContent.replace( 294 + /API_URL=.*/, 295 + `API_URL="${DEFAULT_API_URL}"` 296 + ); 297 + 298 + // Write the actual .env file 299 + await Deno.writeTextFile(envPath, envContent); 300 + logger.success("Created .env file with your OAuth credentials"); 301 + } catch (error) { 302 + const err = error as Error; 303 + logger.warn(`Could not create .env file: ${err.message}`); 304 + } 305 + } 306 + 307 + // Create slices.json file with actual slice URI 308 + if (sliceUri) { 309 + const slicesJsonPath = join(targetDir, "slices.json"); 310 + const slicesConfig = { 311 + slice: sliceUri, 312 + lexiconPath: "./lexicons", 313 + clientOutputPath: "./src/generated_client.ts", 314 + }; 315 + 316 + try { 317 + await Deno.writeTextFile( 318 + slicesJsonPath, 319 + JSON.stringify(slicesConfig, null, 2) + "\n" 320 + ); 321 + logger.success("Created slices.json with your slice configuration"); 322 + } catch (error) { 323 + const err = error as Error; 324 + logger.warn(`Could not create slices.json file: ${err.message}`); 325 + } 326 + } 327 + 328 + // Pull lexicons from the base slice w/ Bsky Profile Lexicons 329 + try { 330 + logger.info("Pulling base lexicons..."); 331 + 332 + // Change to the project directory and run pull 333 + const originalCwd = Deno.cwd(); 334 + Deno.chdir(targetDir); 335 + 336 + // Pull from the base Slices platform slice which has all the proper lexicons 337 + await pullCommand(["--slice", REFERENCE_SLICE_URI], {}); 338 + 339 + logger.success("Pulled base lexicons"); 340 + 341 + // If we created a slice, push the lexicons to it 342 + if (sliceUri) { 343 + logger.info("Pushing lexicons to your slice..."); 344 + await pushCommand([], {}); 345 + logger.success("Pushed lexicons to your slice"); 346 + } 347 + 348 + // Generate TypeScript client from lexicons 349 + logger.info("Generating TypeScript client..."); 350 + await codegenCommand([], {}); 351 + logger.success("Generated TypeScript client"); 352 + 353 + // Change back to original directory 354 + Deno.chdir(originalCwd); 355 + } catch (error) { 356 + const err = error as Error; 357 + logger.warn(`Could not pull lexicons: ${err.message}`); 358 + 359 + // Create empty lexicons folder as fallback 360 + const lexiconsPath = join(targetDir, "lexicons"); 361 + try { 362 + await ensureDir(lexiconsPath); 363 + await Deno.writeTextFile(join(lexiconsPath, ".gitkeep"), ""); 364 + } catch { 365 + // Ignore if we can't create the folder 366 + } 367 + } 368 + 369 + // Initialize git repository 370 + try { 371 + const gitInit = new Deno.Command("git", { 372 + args: ["init"], 373 + cwd: targetDir, 374 + stdout: "piped", 375 + stderr: "piped", 376 + }); 377 + 378 + const result = await gitInit.output(); 379 + if (result.success) { 380 + logger.success("Initialized git repository"); 381 + } else { 382 + const stderr = new TextDecoder().decode(result.stderr); 383 + logger.warn(`Could not initialize git repository: ${stderr}`); 384 + } 385 + } catch (error) { 386 + const err = error as Error; 387 + logger.warn(`Could not initialize git repository: ${err.message}`); 388 + } 389 + 58 390 logger.success(`Created project: ${projectName}`); 59 - console.log(` 60 - Get started: 61 - cd ${projectName} 62 - cp .env.example .env 63 - deno task dev 391 + 392 + console.log(`\n${green("✨ Your project is ready!")}\n`); 393 + console.log(`${bold("Next steps:")}`); 394 + console.log(` 1. cd ${cyan(projectName)}`); 395 + if (oauthClientId && oauthClientSecret) { 396 + console.log(` 2. deno task dev`); 397 + console.log(` ${dim(" Your .env file is already configured!")}`); 398 + } else { 399 + console.log(` 2. cp .env.example .env`); 400 + console.log(` 3. ${dim("# Add your OAuth credentials to .env")}`); 401 + console.log(` 4. deno task dev`); 402 + } 403 + 404 + if (sliceUri && sliceDomain) { 405 + console.log(`\n${bold("Your slice details:")}`); 406 + console.log(` Name: ${cyan(projectName)}`); 407 + console.log(` Domain: ${cyan(sliceDomain)}`); 408 + console.log(` URI: ${dim(sliceUri)}`); 409 + } 64 410 65 - Available commands: 66 - deno task dev Start development server 67 - deno task start Start production server 68 - deno fmt Format code 69 - `); 411 + console.log(`\n${bold("Available commands:")}`); 412 + console.log(` ${cyan("deno task dev")} Start development server`); 413 + console.log(` ${cyan("deno task start")} Start production server`); 414 + console.log(` ${cyan("deno fmt")} Format code`); 70 415 } catch (error) { 71 416 const err = error as Error; 72 417 logger.error("Failed to create project:", err.message); ··· 74 419 } 75 420 } 76 421 77 - async function extractEmbeddedTemplates(targetDir: string, projectName: string) { 422 + async function extractEmbeddedTemplates( 423 + targetDir: string, 424 + projectName: string 425 + ) { 78 426 try { 79 427 const templates = getAllTemplates(); 80 428 ··· 106 454 107 455 async function processTemplateFiles(targetDir: string, projectName: string) { 108 456 // Process files that need variable replacement 109 - const filesToProcess = [ 110 - "README.md", 111 - ]; 457 + const filesToProcess = ["README.md", "src/config.ts"]; 112 458 113 459 for (const file of filesToProcess) { 114 460 const filePath = join(targetDir, file); ··· 128 474 Initialize a new Deno SSR project with OAuth authentication 129 475 130 476 USAGE: 131 - slices init <project-name> 132 - slices init --name <project-name> 477 + slices init <project-name> Create project with specified name 478 + slices init Create project with random generated name 479 + slices init --name <name> Create project with specified name 133 480 134 481 ARGUMENTS: 135 - <project-name> Name of the project to create 482 + <project-name> Name of the project to create (optional) 136 483 137 484 OPTIONS: 138 485 -n, --name <name> Project name 139 486 -h, --help Show this help message 140 487 141 488 EXAMPLES: 142 - slices init my-app 143 - slices init --name my-awesome-project 489 + slices init my-app Creates "my-app" project and slice 490 + slices init Creates project with random name like "stellar-wave" 491 + slices init --name my-project 144 492 145 - The generated project includes: 493 + FEATURES: 494 + • Automatically creates a matching slice (if authenticated) 495 + • Generates domain in format: network.slices.<project-name> 496 + • Updates .env.example with slice URI 146 497 • Deno SSR with Preact and JSX 147 498 • OAuth authentication with PKCE flow 148 499 • Session management with SQLite 149 500 • HTMX for interactive components 150 501 • Tailwind CSS for styling 151 502 • Feature-based architecture 152 - • Comprehensive documentation 153 503 `); 154 - } 504 + }
+1 -1
packages/cli/src/templates/deno-ssr/.env.example
··· 6 6 7 7 # API Configuration (required) 8 8 API_URL=https://api.slices.network 9 - SLICE_URI=at://did:plc:example/network.slices.slice/example 9 + SLICE_URI=at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lzbzumcmvo2z 10 10 11 11 # Database (optional, defaults to slices.db) 12 12 DATABASE_URL=slices.db
+3 -28
packages/cli/src/templates/deno-ssr/.gitignore
··· 1 - # Environment variables 2 - .env 3 - 4 - # Database 5 - *.db 6 - *.db-journal 7 - *.db-wal 8 - *.db-shm 9 - 10 - # Deno 11 - deno.lock 12 - 13 - # OS generated files 14 - .DS_Store 15 - Thumbs.db 16 - 17 - # IDE 18 - .vscode/ 19 - .idea/ 20 - *.swp 21 - *.swo 22 - 23 - # Logs 24 - *.log 25 - logs/ 26 - 27 - # Coverage 28 - coverage/ 1 + .env* 2 + node_modules 3 + *.db*
+22 -16
packages/cli/src/templates/deno-ssr/README.md
··· 5 5 6 6 ## Quick Start 7 7 8 - 1. **Copy environment file:** 9 - ```bash 10 - cp .env.example .env 11 - ``` 8 + ```bash 9 + # Start the development server 10 + deno task dev 11 + ``` 12 12 13 - 2. **Configure OAuth:** Edit `.env` with your OAuth client credentials from 14 - Slices 13 + Visit your app at http://localhost:8080 15 14 16 - 3. **Start development server:** 17 - ```bash 18 - deno task dev 19 - ``` 20 - 21 - 4. **Visit your app:** Open http://localhost:8080 15 + > **Note:** Your slice and OAuth credentials were automatically configured 16 + > during project creation. The `.env` file is already set up with your 17 + > credentials. 22 18 23 19 ## Features 24 20 ··· 49 45 ## Project Structure 50 46 51 47 ``` 48 + slices.json # Slices configuration file 49 + lexicons/ # AT Protocol lexicon definitions 52 50 src/ 53 51 ├── main.ts # Server entry point 54 52 ├── config.ts # OAuth & session configuration 53 + ├── generated_client.ts # Generated TypeScript client from lexicons 55 54 ├── routes/ # Route definitions 56 55 ├── features/ # Feature modules 57 56 │ └── auth/ # Authentication ··· 61 60 62 61 ## OAuth Setup 63 62 64 - 1. Register your application at 65 - [Slices Developer Portal](https://slices.network) 66 - 2. Add your OAuth credentials to `.env` 67 - 3. Configure redirect URI: `http://localhost:8080/oauth/callback` 63 + Your OAuth application was automatically created during project initialization 64 + with: 65 + 66 + - **Client ID & Secret**: Already configured in `.env` 67 + - **Redirect URI**: `http://localhost:8080/oauth/callback` 68 + - **Slice**: Automatically created and linked 69 + 70 + To manage your OAuth clients or create additional ones: 71 + 72 + 1. Visit [Slices Network](https://slices.network) 73 + 2. Use the `slices login` CLI command 68 74 69 75 ## Documentation 70 76
+5 -4
packages/cli/src/templates/deno-ssr/deno.json
··· 8 8 "jsxImportSource": "preact" 9 9 }, 10 10 "imports": { 11 - "@slices/client": "jsr:@slices/client@^0.1.0-alpha.3", 12 - "@slices/oauth": "jsr:@slices/oauth@^0.4.1", 13 - "@slices/session": "jsr:@slices/session@^0.2.1", 11 + "@slices/client": "jsr:@slices/client@^0.1.0-alpha.4", 12 + "@slices/oauth": "jsr:@slices/oauth@^0.6.0", 13 + "@slices/session": "jsr:@slices/session@^0.3.0", 14 14 "@std/assert": "jsr:@std/assert@^1.0.14", 15 + "@std/fmt": "jsr:@std/fmt@^1.0.8", 15 16 "preact": "npm:preact@^10.27.1", 16 17 "preact-render-to-string": "npm:preact-render-to-string@^6.5.13", 17 18 "typed-htmx": "npm:typed-htmx@^0.3.1", ··· 21 22 "lucide-preact": "npm:lucide-preact@^0.544.0" 22 23 }, 23 24 "nodeModulesDir": "auto" 24 - } 25 + }
+33 -20
packages/cli/src/templates/deno-ssr/src/config.ts
··· 1 1 import { OAuthClient, SQLiteOAuthStorage } from "@slices/oauth"; 2 2 import { SessionStore, SQLiteAdapter, withOAuthSession } from "@slices/session"; 3 + import { AtProtoClient } from "./generated_client.ts"; 3 4 4 5 const OAUTH_CLIENT_ID = Deno.env.get("OAUTH_CLIENT_ID"); 5 6 const OAUTH_CLIENT_SECRET = Deno.env.get("OAUTH_CLIENT_SECRET"); ··· 26 27 27 28 // OAuth setup 28 29 const oauthStorage = new SQLiteOAuthStorage(DATABASE_URL); 29 - const oauthClient = new OAuthClient( 30 - { 31 - clientId: OAUTH_CLIENT_ID, 32 - clientSecret: OAUTH_CLIENT_SECRET, 33 - authBaseUrl: OAUTH_AIP_BASE_URL, 34 - redirectUri: OAUTH_REDIRECT_URI, 35 - scopes: [ 36 - "openid", 37 - "email", 38 - "profile", 39 - "atproto", 40 - "transition:generic", 41 - ], 42 - }, 43 - oauthStorage 44 - ); 30 + const oauthConfig = { 31 + clientId: OAUTH_CLIENT_ID, 32 + clientSecret: OAUTH_CLIENT_SECRET, 33 + authBaseUrl: OAUTH_AIP_BASE_URL, 34 + redirectUri: OAUTH_REDIRECT_URI, 35 + scopes: ["atproto", "openid", "profile"], 36 + }; 37 + 38 + // Export config and storage for creating user-scoped clients 39 + export { oauthConfig, oauthStorage }; 45 40 46 41 // Session setup (shared database) 47 42 export const sessionStore = new SessionStore({ 48 43 adapter: new SQLiteAdapter(DATABASE_URL), 44 + cookieName: "{{PROJECT_NAME}}-session", 49 45 cookieOptions: { 50 46 httpOnly: true, 51 47 secure: Deno.env.get("DENO_ENV") === "production", ··· 55 51 }); 56 52 57 53 // OAuth + Session integration 58 - export const oauthSessions = withOAuthSession(sessionStore, oauthClient, { 59 - autoRefresh: true, 60 - }); 54 + export const oauthSessions = withOAuthSession( 55 + sessionStore, 56 + oauthConfig, 57 + oauthStorage, 58 + { 59 + autoRefresh: true, 60 + } 61 + ); 61 62 62 - export { oauthClient }; 63 + // Helper function to create user-scoped OAuth client 64 + export function createOAuthClient(userId: string): OAuthClient { 65 + return new OAuthClient(oauthConfig, oauthStorage, userId); 66 + } 67 + 68 + // Helper function to create authenticated AtProto client for a user 69 + export function createSessionClient(userId: string): AtProtoClient { 70 + const userOAuthClient = createOAuthClient(userId); 71 + return new AtProtoClient(API_URL!, SLICE_URI!, userOAuthClient); 72 + } 73 + 74 + // Public client for unauthenticated requests 75 + export const publicClient = new AtProtoClient(API_URL, SLICE_URI);
+41 -32
packages/cli/src/templates/deno-ssr/src/features/auth/handlers.tsx
··· 1 1 import type { Route } from "@std/http/unstable-route"; 2 2 import { withAuth } from "../../routes/middleware.ts"; 3 - import { oauthClient, oauthSessions, sessionStore } from "../../config.ts"; 3 + import { OAuthClient } from "@slices/oauth"; 4 + import { 5 + createOAuthClient, 6 + createSessionClient, 7 + oauthConfig, 8 + oauthStorage, 9 + oauthSessions, 10 + sessionStore, 11 + } from "../../config.ts"; 4 12 import { renderHTML } from "../../utils/render.tsx"; 5 13 import { LoginPage } from "./templates/LoginPage.tsx"; 6 14 ··· 14 22 } 15 23 16 24 const error = url.searchParams.get("error"); 17 - return renderHTML( 18 - <LoginPage error={error || undefined} /> 19 - ); 25 + return renderHTML(<LoginPage error={error || undefined} />); 20 26 } 21 27 22 28 async function handleOAuthAuthorize(req: Request): Promise<Response> { ··· 28 34 return new Response("Missing login hint", { status: 400 }); 29 35 } 30 36 31 - const authResult = await oauthClient.authorize({ 37 + const tempOAuthClient = new OAuthClient( 38 + oauthConfig, 39 + oauthStorage, 40 + loginHint 41 + ); 42 + const authResult = await tempOAuthClient.authorize({ 32 43 loginHint, 33 44 }); 34 45 35 46 return Response.redirect(authResult.authorizationUrl, 302); 36 47 } catch (error) { 37 48 console.error("OAuth authorize error:", error); 49 + 38 50 return Response.redirect( 39 51 new URL( 40 - "/login?error=" + encodeURIComponent("OAuth initialization failed"), 52 + "/login?error=" + 53 + encodeURIComponent("Please check your handle and try again."), 41 54 req.url 42 55 ), 43 56 302 ··· 61 74 ); 62 75 } 63 76 64 - await oauthClient.handleCallback({ code, state }); 65 - const sessionId = await oauthSessions.createOAuthSession(); 77 + const tempOAuthClient = new OAuthClient(oauthConfig, oauthStorage, "temp"); 78 + const tokens = await tempOAuthClient.handleCallback({ code, state }); 79 + const sessionId = await oauthSessions.createOAuthSession(tokens); 66 80 67 81 if (!sessionId) { 68 82 return Response.redirect( ··· 76 90 77 91 const sessionCookie = sessionStore.createSessionCookie(sessionId); 78 92 93 + let userInfo; 94 + try { 95 + const sessionOAuthClient = createOAuthClient(sessionId); 96 + userInfo = await sessionOAuthClient.getUserInfo(); 97 + } catch (error) { 98 + console.error("Failed to get user info:", error); 99 + } 100 + 101 + if (userInfo?.sub) { 102 + try { 103 + const userClient = createSessionClient(sessionId); 104 + await userClient.syncUserCollections(); 105 + console.log("Synced Bluesky profile for", userInfo.sub); 106 + } catch (error) { 107 + console.error("Error syncing Bluesky profile:", error); 108 + } 109 + } 110 + 79 111 return new Response(null, { 80 112 status: 302, 81 113 headers: { ··· 113 145 }); 114 146 } 115 147 116 - async function handleDashboard(req: Request): Promise<Response> { 117 - const context = await withAuth(req); 118 - 119 - if (!context.currentUser) { 120 - return Response.redirect(new URL("/login", req.url), 302); 121 - } 122 - 123 - return renderHTML( 124 - <div> 125 - <h1>Dashboard</h1> 126 - <p>Welcome, {context.currentUser.name || context.currentUser.sub}!</p> 127 - <form method="post" action="/logout"> 128 - <button type="submit">Logout</button> 129 - </form> 130 - </div> 131 - ); 132 - } 133 - 134 148 export const authRoutes: Route[] = [ 135 149 { 136 150 method: "GET", ··· 152 166 pattern: new URLPattern({ pathname: "/logout" }), 153 167 handler: handleLogout, 154 168 }, 155 - { 156 - method: "GET", 157 - pattern: new URLPattern({ pathname: "/dashboard" }), 158 - handler: handleDashboard, 159 - }, 160 - ]; 169 + ];
+49
packages/cli/src/templates/deno-ssr/src/features/dashboard/handlers.tsx
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { withAuth } from "../../routes/middleware.ts"; 3 + import { renderHTML } from "../../utils/render.tsx"; 4 + import { DashboardPage } from "./templates/DashboardPage.tsx"; 5 + import { publicClient } from "../../config.ts"; 6 + import { recordBlobToCdnUrl } from "@slices/client"; 7 + import { AppBskyActorProfile } from "../../generated_client.ts"; 8 + 9 + async function handleDashboard(req: Request): Promise<Response> { 10 + const context = await withAuth(req); 11 + 12 + if (!context.currentUser) { 13 + return Response.redirect(new URL("/login", req.url), 302); 14 + } 15 + 16 + let profile: AppBskyActorProfile | undefined; 17 + let avatarUrl: string | undefined; 18 + try { 19 + const profileResult = await publicClient.app.bsky.actor.profile.getRecord({ 20 + uri: `at://${context.currentUser.sub}/app.bsky.actor.profile/self`, 21 + }); 22 + 23 + if (profileResult) { 24 + profile = profileResult.value; 25 + 26 + if (profile.avatar) { 27 + avatarUrl = recordBlobToCdnUrl(profileResult, profile.avatar, "avatar"); 28 + } 29 + } 30 + } catch (error) { 31 + console.error("Error fetching profile:", error); 32 + } 33 + 34 + return renderHTML( 35 + <DashboardPage 36 + currentUser={context.currentUser} 37 + profile={profile} 38 + avatarUrl={avatarUrl} 39 + /> 40 + ); 41 + } 42 + 43 + export const dashboardRoutes: Route[] = [ 44 + { 45 + method: "GET", 46 + pattern: new URLPattern({ pathname: "/dashboard" }), 47 + handler: handleDashboard, 48 + }, 49 + ];
+56
packages/cli/src/templates/deno-ssr/src/features/dashboard/templates/DashboardPage.tsx
··· 1 + import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 + import { Button } from "../../../shared/fragments/Button.tsx"; 3 + import type { AppBskyActorProfile } from "../../../generated_client.ts"; 4 + 5 + interface DashboardPageProps { 6 + currentUser: { 7 + name?: string; 8 + sub: string; 9 + }; 10 + profile?: AppBskyActorProfile; 11 + avatarUrl?: string; 12 + } 13 + 14 + export function DashboardPage({ 15 + currentUser, 16 + profile, 17 + avatarUrl, 18 + }: DashboardPageProps) { 19 + return ( 20 + <Layout title="Dashboard"> 21 + <div className="min-h-screen bg-gray-50 p-8"> 22 + <div className="max-w-2xl mx-auto"> 23 + <div className="bg-white rounded-lg shadow p-6"> 24 + <div className="flex justify-between items-center mb-6"> 25 + <h1 className="text-2xl font-bold">Dashboard</h1> 26 + <form method="post" action="/logout"> 27 + <Button type="submit" variant="secondary"> 28 + Logout 29 + </Button> 30 + </form> 31 + </div> 32 + 33 + <div className="mb-6"> 34 + {avatarUrl && ( 35 + <img 36 + src={avatarUrl} 37 + alt="Profile" 38 + className="w-20 h-20 rounded-full mb-4" 39 + /> 40 + )} 41 + <h2 className="text-xl font-semibold mb-2"> 42 + {profile?.displayName || currentUser.name || currentUser.sub} 43 + </h2> 44 + {currentUser.name && ( 45 + <p className="text-gray-600 mb-2">@{currentUser.name}</p> 46 + )} 47 + {profile?.description && ( 48 + <p className="text-gray-700 mt-2">{profile.description}</p> 49 + )} 50 + </div> 51 + </div> 52 + </div> 53 + </div> 54 + </Layout> 55 + ); 56 + }
+7 -5
packages/cli/src/templates/deno-ssr/src/routes/middleware.ts
··· 1 - import { sessionStore, oauthSessions, oauthClient } from "../config.ts"; 1 + import { sessionStore, createOAuthClient } from "../config.ts"; 2 2 3 3 export interface AuthContext { 4 4 currentUser: { ··· 17 17 } 18 18 19 19 try { 20 - // Get user info from OAuth client 21 - const userInfo = await oauthClient.getUserInfo(); 20 + const sessionOAuthClient = createOAuthClient(session.sessionId); 21 + const userInfo = await sessionOAuthClient.getUserInfo(); 22 22 return { 23 23 currentUser: userInfo || null, 24 24 sessionId: session.sessionId, ··· 28 28 } 29 29 } 30 30 31 - export function requireAuth(handler: (req: Request, context: AuthContext) => Promise<Response>) { 31 + export function requireAuth( 32 + handler: (req: Request, context: AuthContext) => Promise<Response> 33 + ) { 32 34 return async (req: Request): Promise<Response> => { 33 35 const context = await withAuth(req); 34 36 ··· 38 40 39 41 return handler(req, context); 40 42 }; 41 - } 43 + }
+4
packages/cli/src/templates/deno-ssr/src/routes/mod.ts
··· 1 1 import type { Route } from "@std/http/unstable-route"; 2 2 import { authRoutes } from "../features/auth/handlers.tsx"; 3 + import { dashboardRoutes } from "../features/dashboard/handlers.tsx"; 3 4 4 5 export const allRoutes: Route[] = [ 5 6 // Root redirect to login for now ··· 11 12 12 13 // Auth routes 13 14 ...authRoutes, 15 + 16 + // Dashboard routes 17 + ...dashboardRoutes, 14 18 ];
+10 -11
packages/cli/src/templates/deno-ssr/src/shared/fragments/Button.tsx
··· 1 - import { clsx } from "clsx"; 1 + import { ComponentChildren, JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 2 3 3 - interface ButtonProps { 4 - children: any; 5 - type?: "button" | "submit" | "reset"; 4 + interface ButtonProps extends Omit<JSX.IntrinsicElements['button'], "size"> { 5 + children: ComponentChildren; 6 6 variant?: "primary" | "secondary" | "danger"; 7 7 size?: "sm" | "md" | "lg"; 8 - className?: string; 9 - disabled?: boolean; 10 - [key: string]: any; 11 8 } 12 9 13 10 export function Button({ ··· 19 16 disabled, 20 17 ...props 21 18 }: ButtonProps) { 22 - const baseClasses = "inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; 19 + const baseClasses = 20 + "inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; 23 21 24 22 const variantClasses = { 25 23 primary: "bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500", 26 - secondary: "bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500", 24 + secondary: 25 + "bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500", 27 26 danger: "bg-red-600 hover:bg-red-700 text-white focus:ring-red-500", 28 27 }; 29 28 ··· 37 36 <button 38 37 type={type} 39 38 disabled={disabled} 40 - className={clsx( 39 + className={cn( 41 40 baseClasses, 42 41 variantClasses[variant], 43 42 sizeClasses[size], ··· 48 47 {children} 49 48 </button> 50 49 ); 51 - } 50 + }
+4 -13
packages/cli/src/templates/deno-ssr/src/shared/fragments/Input.tsx
··· 1 - import { clsx } from "clsx"; 1 + import { JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 2 3 3 - interface InputProps { 4 - type?: string; 5 - name?: string; 6 - id?: string; 7 - placeholder?: string; 8 - required?: boolean; 9 - disabled?: boolean; 10 - className?: string; 11 - value?: string; 12 - [key: string]: any; 13 - } 4 + type InputProps = JSX.IntrinsicElements['input']; 14 5 15 6 export function Input({ 16 7 type = "text", ··· 20 11 return ( 21 12 <input 22 13 type={type} 23 - className={clsx( 14 + className={cn( 24 15 "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm", 25 16 "focus:outline-none focus:ring-blue-500 focus:border-blue-500", 26 17 "disabled:bg-gray-50 disabled:text-gray-500",
+3 -1
packages/cli/src/templates/deno-ssr/src/shared/fragments/Layout.tsx
··· 1 + import { ComponentChildren } from "preact"; 2 + 1 3 interface LayoutProps { 2 4 title?: string; 3 - children: any; 5 + children: ComponentChildren; 4 6 } 5 7 6 8 export function Layout({ title = "App", children }: LayoutProps) {
+6
packages/cli/src/templates/deno-ssr/src/utils/cn.ts
··· 1 + import { type ClassValue, clsx } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]): string { 5 + return twMerge(clsx(inputs)); 6 + }
+29 -4
packages/cli/src/templates/deno-ssr/src/utils/logging.ts
··· 1 - export function createLoggingHandler(handler: (req: Request) => Response | Promise<Response>) { 1 + import { cyan, green, red, yellow, bold, dim } from "@std/fmt/colors"; 2 + 3 + export function createLoggingHandler( 4 + handler: (req: Request) => Response | Promise<Response> 5 + ) { 2 6 return async (req: Request): Promise<Response> => { 3 7 const start = Date.now(); 4 8 const method = req.method; ··· 7 11 try { 8 12 const response = await Promise.resolve(handler(req)); 9 13 const duration = Date.now() - start; 10 - console.log(`${method} ${url.pathname} - ${response.status} (${duration}ms)`); 14 + 15 + const methodColor = cyan(bold(method)); 16 + const statusColor = 17 + response.status >= 200 && response.status < 300 18 + ? green(String(response.status)) 19 + : response.status >= 300 && response.status < 400 20 + ? yellow(String(response.status)) 21 + : response.status >= 400 22 + ? red(String(response.status)) 23 + : String(response.status); 24 + const durationText = dim(`(${duration}ms)`); 25 + 26 + console.log( 27 + `${methodColor} ${url.pathname} - ${statusColor} ${durationText}` 28 + ); 11 29 return response; 12 30 } catch (error) { 13 31 const duration = Date.now() - start; 14 - console.error(`${method} ${url.pathname} - ERROR (${duration}ms):`, error); 32 + const methodColor = cyan(bold(method)); 33 + const errorText = red(bold("ERROR")); 34 + const durationText = dim(`(${duration}ms)`); 35 + 36 + console.error( 37 + `${methodColor} ${url.pathname} - ${errorText} ${durationText}:`, 38 + error 39 + ); 15 40 throw error; 16 41 } 17 42 }; 18 - } 43 + }
+2 -1
packages/cli/src/templates/deno-ssr/src/utils/render.tsx
··· 1 1 import { renderToString } from "preact-render-to-string"; 2 + import { VNode } from "preact"; 2 3 3 - export function renderHTML(element: any): Response { 4 + export function renderHTML(element: VNode): Response { 4 5 const html = renderToString(element); 5 6 6 7 return new Response(html, {
+25 -13
packages/cli/src/templates/embedded.ts
··· 9 9 export const EMBEDDED_TEMPLATES: EmbeddedTemplate[] = [ 10 10 { 11 11 "path": "deno.json", 12 - "content": "ewogICJ0YXNrcyI6IHsKICAgICJzdGFydCI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgc3JjL21haW4udHMiLAogICAgImRldiI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgLS13YXRjaCBzcmMvbWFpbi50cyIKICB9LAogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAianN4IjogInByZWNvbXBpbGUiLAogICAgImpzeEltcG9ydFNvdXJjZSI6ICJwcmVhY3QiCiAgfSwKICAiaW1wb3J0cyI6IHsKICAgICJAc2xpY2VzL2NsaWVudCI6ICJqc3I6QHNsaWNlcy9jbGllbnRAXjAuMS4wLWFscGhhLjMiLAogICAgIkBzbGljZXMvb2F1dGgiOiAianNyOkBzbGljZXMvb2F1dGhAXjAuNC4xIiwKICAgICJAc2xpY2VzL3Nlc3Npb24iOiAianNyOkBzbGljZXMvc2Vzc2lvbkBeMC4yLjEiLAogICAgIkBzdGQvYXNzZXJ0IjogImpzcjpAc3RkL2Fzc2VydEBeMS4wLjE0IiwKICAgICJwcmVhY3QiOiAibnBtOnByZWFjdEBeMTAuMjcuMSIsCiAgICAicHJlYWN0LXJlbmRlci10by1zdHJpbmciOiAibnBtOnByZWFjdC1yZW5kZXItdG8tc3RyaW5nQF42LjUuMTMiLAogICAgInR5cGVkLWh0bXgiOiAibnBtOnR5cGVkLWh0bXhAXjAuMy4xIiwKICAgICJAc3RkL2h0dHAiOiAianNyOkBzdGQvaHR0cEBeMS4wLjIwIiwKICAgICJjbHN4IjogIm5wbTpjbHN4QF4yLjEuMSIsCiAgICAidGFpbHdpbmQtbWVyZ2UiOiAibnBtOnRhaWx3aW5kLW1lcmdlQF4yLjUuNSIsCiAgICAibHVjaWRlLXByZWFjdCI6ICJucG06bHVjaWRlLXByZWFjdEBeMC41NDQuMCIKICB9LAogICJub2RlTW9kdWxlc0RpciI6ICJhdXRvIgp9" 12 + "content": "ewogICJ0YXNrcyI6IHsKICAgICJzdGFydCI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgc3JjL21haW4udHMiLAogICAgImRldiI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgLS13YXRjaCBzcmMvbWFpbi50cyIKICB9LAogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAianN4IjogInByZWNvbXBpbGUiLAogICAgImpzeEltcG9ydFNvdXJjZSI6ICJwcmVhY3QiCiAgfSwKICAiaW1wb3J0cyI6IHsKICAgICJAc2xpY2VzL2NsaWVudCI6ICJqc3I6QHNsaWNlcy9jbGllbnRAXjAuMS4wLWFscGhhLjQiLAogICAgIkBzbGljZXMvb2F1dGgiOiAianNyOkBzbGljZXMvb2F1dGhAXjAuNi4wIiwKICAgICJAc2xpY2VzL3Nlc3Npb24iOiAianNyOkBzbGljZXMvc2Vzc2lvbkBeMC4zLjAiLAogICAgIkBzdGQvYXNzZXJ0IjogImpzcjpAc3RkL2Fzc2VydEBeMS4wLjE0IiwKICAgICJAc3RkL2ZtdCI6ICJqc3I6QHN0ZC9mbXRAXjEuMC44IiwKICAgICJwcmVhY3QiOiAibnBtOnByZWFjdEBeMTAuMjcuMSIsCiAgICAicHJlYWN0LXJlbmRlci10by1zdHJpbmciOiAibnBtOnByZWFjdC1yZW5kZXItdG8tc3RyaW5nQF42LjUuMTMiLAogICAgInR5cGVkLWh0bXgiOiAibnBtOnR5cGVkLWh0bXhAXjAuMy4xIiwKICAgICJAc3RkL2h0dHAiOiAianNyOkBzdGQvaHR0cEBeMS4wLjIwIiwKICAgICJjbHN4IjogIm5wbTpjbHN4QF4yLjEuMSIsCiAgICAidGFpbHdpbmQtbWVyZ2UiOiAibnBtOnRhaWx3aW5kLW1lcmdlQF4yLjUuNSIsCiAgICAibHVjaWRlLXByZWFjdCI6ICJucG06bHVjaWRlLXByZWFjdEBeMC41NDQuMCIKICB9LAogICJub2RlTW9kdWxlc0RpciI6ICJhdXRvIgp9Cg==" 13 13 }, 14 14 { 15 15 "path": "README.md", 16 - "content": "IyB7e1BST0pFQ1RfTkFNRX19CgpBIERlbm8gU1NSIHdlYiBhcHBsaWNhdGlvbiB3aXRoIEFUIFByb3RvY29sIGludGVncmF0aW9uLCBidWlsdCB3aXRoIFByZWFjdCwKSFRNWCwgYW5kIE9BdXRoIGF1dGhlbnRpY2F0aW9uLgoKIyMgUXVpY2sgU3RhcnQKCjEuICoqQ29weSBlbnZpcm9ubWVudCBmaWxlOioqCiAgIGBgYGJhc2gKICAgY3AgLmVudi5leGFtcGxlIC5lbnYKICAgYGBgCgoyLiAqKkNvbmZpZ3VyZSBPQXV0aDoqKiBFZGl0IGAuZW52YCB3aXRoIHlvdXIgT0F1dGggY2xpZW50IGNyZWRlbnRpYWxzIGZyb20KICAgU2xpY2VzCgozLiAqKlN0YXJ0IGRldmVsb3BtZW50IHNlcnZlcjoqKgogICBgYGBiYXNoCiAgIGRlbm8gdGFzayBkZXYKICAgYGBgCgo0LiAqKlZpc2l0IHlvdXIgYXBwOioqIE9wZW4gaHR0cDovL2xvY2FsaG9zdDo4MDgwCgojIyBGZWF0dXJlcwoKLSDwn5SQICoqT0F1dGggQXV0aGVudGljYXRpb24qKiB3aXRoIFBLQ0UgZmxvdwotIOKaoSAqKlNlcnZlci1TaWRlIFJlbmRlcmluZyoqIHdpdGggUHJlYWN0Ci0g8J+OryAqKkludGVyYWN0aXZlIFVJKiogd2l0aCBIVE1YCi0g8J+OqCAqKlN0eWxpbmcqKiB3aXRoIFRhaWx3aW5kIENTUwotIPCfl4TvuI8gKipTZXNzaW9uIE1hbmFnZW1lbnQqKiB3aXRoIFNRTGl0ZQotIPCflIQgKipBdXRvIFRva2VuIFJlZnJlc2gqKgotIPCfj5fvuI8gKipGZWF0dXJlLUJhc2VkIEFyY2hpdGVjdHVyZSoqCgojIyBEZXZlbG9wbWVudAoKYGBgYmFzaAojIFN0YXJ0IGRldmVsb3BtZW50IHNlcnZlciB3aXRoIGhvdCByZWxvYWQKZGVubyB0YXNrIGRldgoKIyBTdGFydCBwcm9kdWN0aW9uIHNlcnZlcgpkZW5vIHRhc2sgc3RhcnQKCiMgRm9ybWF0IGNvZGUKZGVubyBmbXQKCiMgQ2hlY2sgdHlwZXMKZGVubyBjaGVjayBzcmMvKiovKi50cyBzcmMvKiovKi50c3gKYGBgCgojIyBQcm9qZWN0IFN0cnVjdHVyZQoKYGBgCnNyYy8K4pSc4pSA4pSAIG1haW4udHMgICAgICAgICAgICAgICMgU2VydmVyIGVudHJ5IHBvaW50CuKUnOKUgOKUgCBjb25maWcudHMgICAgICAgICAgICAjIE9BdXRoICYgc2Vzc2lvbiBjb25maWd1cmF0aW9uCuKUnOKUgOKUgCByb3V0ZXMvICAgICAgICAgICAgICAjIFJvdXRlIGRlZmluaXRpb25zCuKUnOKUgOKUgCBmZWF0dXJlcy8gICAgICAgICAgICAjIEZlYXR1cmUgbW9kdWxlcwrilIIgICDilJTilIDilIAgYXV0aC8gICAgICAgICAgICMgQXV0aGVudGljYXRpb24K4pSc4pSA4pSAIHNoYXJlZC9mcmFnbWVudHMvICAgICMgUmV1c2FibGUgVUkgY29tcG9uZW50cwrilJTilIDilIAgdXRpbHMvICAgICAgICAgICAgICAjIFV0aWxpdHkgZnVuY3Rpb25zCmBgYAoKIyMgT0F1dGggU2V0dXAKCjEuIFJlZ2lzdGVyIHlvdXIgYXBwbGljYXRpb24gYXQKICAgW1NsaWNlcyBEZXZlbG9wZXIgUG9ydGFsXShodHRwczovL3NsaWNlcy5uZXR3b3JrKQoyLiBBZGQgeW91ciBPQXV0aCBjcmVkZW50aWFscyB0byBgLmVudmAKMy4gQ29uZmlndXJlIHJlZGlyZWN0IFVSSTogYGh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9vYXV0aC9jYWxsYmFja2AKCiMjIERvY3VtZW50YXRpb24KCi0gYENMQVVERS5tZGAgLSBBcmNoaXRlY3R1cmUgZ3VpZGUgZm9yIEFJIGFzc2lzdGFuY2UKLSBGZWF0dXJlIGRpcmVjdG9yaWVzIGNvbnRhaW4gaGFuZGxlcnMgYW5kIHRlbXBsYXRlcwotIENvbXBvbmVudHMgdXNlIFByZWFjdCB3aXRoIHNlcnZlci1zaWRlIHJlbmRlcmluZwotIEhUTVggcHJvdmlkZXMgaW50ZXJhY3RpdmUgYmVoYXZpb3Igd2l0aG91dCBwYWdlIHJlbG9hZHMKCiMjIExpY2Vuc2UKCk1JVAo=" 16 + "content": "IyB7e1BST0pFQ1RfTkFNRX19CgpBIERlbm8gU1NSIHdlYiBhcHBsaWNhdGlvbiB3aXRoIEFUIFByb3RvY29sIGludGVncmF0aW9uLCBidWlsdCB3aXRoIFByZWFjdCwKSFRNWCwgYW5kIE9BdXRoIGF1dGhlbnRpY2F0aW9uLgoKIyMgUXVpY2sgU3RhcnQKCmBgYGJhc2gKIyBTdGFydCB0aGUgZGV2ZWxvcG1lbnQgc2VydmVyCmRlbm8gdGFzayBkZXYKYGBgCgpWaXNpdCB5b3VyIGFwcCBhdCBodHRwOi8vbG9jYWxob3N0OjgwODAKCj4gKipOb3RlOioqIFlvdXIgc2xpY2UgYW5kIE9BdXRoIGNyZWRlbnRpYWxzIHdlcmUgYXV0b21hdGljYWxseSBjb25maWd1cmVkCj4gZHVyaW5nIHByb2plY3QgY3JlYXRpb24uIFRoZSBgLmVudmAgZmlsZSBpcyBhbHJlYWR5IHNldCB1cCB3aXRoIHlvdXIKPiBjcmVkZW50aWFscy4KCiMjIEZlYXR1cmVzCgotIPCflJAgKipPQXV0aCBBdXRoZW50aWNhdGlvbioqIHdpdGggUEtDRSBmbG93Ci0g4pqhICoqU2VydmVyLVNpZGUgUmVuZGVyaW5nKiogd2l0aCBQcmVhY3QKLSDwn46vICoqSW50ZXJhY3RpdmUgVUkqKiB3aXRoIEhUTVgKLSDwn46oICoqU3R5bGluZyoqIHdpdGggVGFpbHdpbmQgQ1NTCi0g8J+XhO+4jyAqKlNlc3Npb24gTWFuYWdlbWVudCoqIHdpdGggU1FMaXRlCi0g8J+UhCAqKkF1dG8gVG9rZW4gUmVmcmVzaCoqCi0g8J+Pl++4jyAqKkZlYXR1cmUtQmFzZWQgQXJjaGl0ZWN0dXJlKioKCiMjIERldmVsb3BtZW50CgpgYGBiYXNoCiMgU3RhcnQgZGV2ZWxvcG1lbnQgc2VydmVyIHdpdGggaG90IHJlbG9hZApkZW5vIHRhc2sgZGV2CgojIFN0YXJ0IHByb2R1Y3Rpb24gc2VydmVyCmRlbm8gdGFzayBzdGFydAoKIyBGb3JtYXQgY29kZQpkZW5vIGZtdAoKIyBDaGVjayB0eXBlcwpkZW5vIGNoZWNrIHNyYy8qKi8qLnRzIHNyYy8qKi8qLnRzeApgYGAKCiMjIFByb2plY3QgU3RydWN0dXJlCgpgYGAKc2xpY2VzLmpzb24gICAgICAgICAgICAgICMgU2xpY2VzIGNvbmZpZ3VyYXRpb24gZmlsZQpsZXhpY29ucy8gICAgICAgICAgICAgICAgIyBBVCBQcm90b2NvbCBsZXhpY29uIGRlZmluaXRpb25zCnNyYy8K4pSc4pSA4pSAIG1haW4udHMgICAgICAgICAgICAgICMgU2VydmVyIGVudHJ5IHBvaW50CuKUnOKUgOKUgCBjb25maWcudHMgICAgICAgICAgICAjIE9BdXRoICYgc2Vzc2lvbiBjb25maWd1cmF0aW9uCuKUnOKUgOKUgCBnZW5lcmF0ZWRfY2xpZW50LnRzICAjIEdlbmVyYXRlZCBUeXBlU2NyaXB0IGNsaWVudCBmcm9tIGxleGljb25zCuKUnOKUgOKUgCByb3V0ZXMvICAgICAgICAgICAgICAjIFJvdXRlIGRlZmluaXRpb25zCuKUnOKUgOKUgCBmZWF0dXJlcy8gICAgICAgICAgICAjIEZlYXR1cmUgbW9kdWxlcwrilIIgICDilJTilIDilIAgYXV0aC8gICAgICAgICAgICMgQXV0aGVudGljYXRpb24K4pSc4pSA4pSAIHNoYXJlZC9mcmFnbWVudHMvICAgICMgUmV1c2FibGUgVUkgY29tcG9uZW50cwrilJTilIDilIAgdXRpbHMvICAgICAgICAgICAgICAjIFV0aWxpdHkgZnVuY3Rpb25zCmBgYAoKIyMgT0F1dGggU2V0dXAKCllvdXIgT0F1dGggYXBwbGljYXRpb24gd2FzIGF1dG9tYXRpY2FsbHkgY3JlYXRlZCBkdXJpbmcgcHJvamVjdCBpbml0aWFsaXphdGlvbgp3aXRoOgoKLSAqKkNsaWVudCBJRCAmIFNlY3JldCoqOiBBbHJlYWR5IGNvbmZpZ3VyZWQgaW4gYC5lbnZgCi0gKipSZWRpcmVjdCBVUkkqKjogYGh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9vYXV0aC9jYWxsYmFja2AKLSAqKlNsaWNlKio6IEF1dG9tYXRpY2FsbHkgY3JlYXRlZCBhbmQgbGlua2VkCgpUbyBtYW5hZ2UgeW91ciBPQXV0aCBjbGllbnRzIG9yIGNyZWF0ZSBhZGRpdGlvbmFsIG9uZXM6CgoxLiBWaXNpdCBbU2xpY2VzIE5ldHdvcmtdKGh0dHBzOi8vc2xpY2VzLm5ldHdvcmspCjIuIFVzZSB0aGUgYHNsaWNlcyBsb2dpbmAgQ0xJIGNvbW1hbmQKCiMjIERvY3VtZW50YXRpb24KCi0gYENMQVVERS5tZGAgLSBBcmNoaXRlY3R1cmUgZ3VpZGUgZm9yIEFJIGFzc2lzdGFuY2UKLSBGZWF0dXJlIGRpcmVjdG9yaWVzIGNvbnRhaW4gaGFuZGxlcnMgYW5kIHRlbXBsYXRlcwotIENvbXBvbmVudHMgdXNlIFByZWFjdCB3aXRoIHNlcnZlci1zaWRlIHJlbmRlcmluZwotIEhUTVggcHJvdmlkZXMgaW50ZXJhY3RpdmUgYmVoYXZpb3Igd2l0aG91dCBwYWdlIHJlbG9hZHMKCiMjIExpY2Vuc2UKCk1JVAo=" 17 17 }, 18 18 { 19 19 "path": ".gitignore", 20 - "content": "IyBFbnZpcm9ubWVudCB2YXJpYWJsZXMKLmVudgoKIyBEYXRhYmFzZQoqLmRiCiouZGItam91cm5hbAoqLmRiLXdhbAoqLmRiLXNobQoKIyBEZW5vCmRlbm8ubG9jawoKIyBPUyBnZW5lcmF0ZWQgZmlsZXMKLkRTX1N0b3JlClRodW1icy5kYgoKIyBJREUKLnZzY29kZS8KLmlkZWEvCiouc3dwCiouc3dvCgojIExvZ3MKKi5sb2cKbG9ncy8KCiMgQ292ZXJhZ2UKY292ZXJhZ2Uv" 20 + "content": "LmVudioKbm9kZV9tb2R1bGVzCiouZGIqCg==" 21 21 }, 22 22 { 23 23 "path": ".env.example", 24 - "content": "IyBPQXV0aCBDb25maWd1cmF0aW9uIChyZXF1aXJlZCkKT0FVVEhfQ0xJRU5UX0lEPXlvdXJfb2F1dGhfY2xpZW50X2lkCk9BVVRIX0NMSUVOVF9TRUNSRVQ9eW91cl9vYXV0aF9jbGllbnRfc2VjcmV0Ck9BVVRIX1JFRElSRUNUX1VSST1odHRwOi8vbG9jYWxob3N0OjgwODAvb2F1dGgvY2FsbGJhY2sKT0FVVEhfQUlQX0JBU0VfVVJMPWh0dHBzOi8vYXV0aC5zbGljZXMubmV0d29yawoKIyBBUEkgQ29uZmlndXJhdGlvbiAocmVxdWlyZWQpCkFQSV9VUkw9aHR0cHM6Ly9hcGkuc2xpY2VzLm5ldHdvcmsKU0xJQ0VfVVJJPWF0Oi8vZGlkOnBsYzpleGFtcGxlL25ldHdvcmsuc2xpY2VzLnNsaWNlL2V4YW1wbGUKCiMgRGF0YWJhc2UgKG9wdGlvbmFsLCBkZWZhdWx0cyB0byBzbGljZXMuZGIpCkRBVEFCQVNFX1VSTD1zbGljZXMuZGIKCiMgRW52aXJvbm1lbnQgKG9wdGlvbmFsLCBhZmZlY3RzIGNvb2tpZSBzZWN1cml0eSkKREVOT19FTlY9ZGV2ZWxvcG1lbnQKCiMgU2VydmVyIChvcHRpb25hbCwgZGVmYXVsdHMgdG8gODA4MCkKUE9SVD04MDgw" 24 + "content": "IyBPQXV0aCBDb25maWd1cmF0aW9uIChyZXF1aXJlZCkKT0FVVEhfQ0xJRU5UX0lEPXlvdXJfb2F1dGhfY2xpZW50X2lkCk9BVVRIX0NMSUVOVF9TRUNSRVQ9eW91cl9vYXV0aF9jbGllbnRfc2VjcmV0Ck9BVVRIX1JFRElSRUNUX1VSST1odHRwOi8vbG9jYWxob3N0OjgwODAvb2F1dGgvY2FsbGJhY2sKT0FVVEhfQUlQX0JBU0VfVVJMPWh0dHBzOi8vYXV0aC5zbGljZXMubmV0d29yawoKIyBBUEkgQ29uZmlndXJhdGlvbiAocmVxdWlyZWQpCkFQSV9VUkw9aHR0cHM6Ly9hcGkuc2xpY2VzLm5ldHdvcmsKU0xJQ0VfVVJJPWF0Oi8vZGlkOnBsYzpiY2dsdHpxYXp3NXRiNmsyZzN0dGVuYmovbmV0d29yay5zbGljZXMuc2xpY2UvM2x6Ynp1bWNtdm8yegoKIyBEYXRhYmFzZSAob3B0aW9uYWwsIGRlZmF1bHRzIHRvIHNsaWNlcy5kYikKREFUQUJBU0VfVVJMPXNsaWNlcy5kYgoKIyBFbnZpcm9ubWVudCAob3B0aW9uYWwsIGFmZmVjdHMgY29va2llIHNlY3VyaXR5KQpERU5PX0VOVj1kZXZlbG9wbWVudAoKIyBTZXJ2ZXIgKG9wdGlvbmFsLCBkZWZhdWx0cyB0byA4MDgwKQpQT1JUPTgwODA=" 25 25 }, 26 26 { 27 27 "path": "CLAUDE.md", ··· 37 37 }, 38 38 { 39 39 "path": "src/features/auth/handlers.tsx", 40 - "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyBvYXV0aENsaWVudCwgb2F1dGhTZXNzaW9ucywgc2Vzc2lvblN0b3JlIH0gZnJvbSAiLi4vLi4vY29uZmlnLnRzIjsKaW1wb3J0IHsgcmVuZGVySFRNTCB9IGZyb20gIi4uLy4uL3V0aWxzL3JlbmRlci50c3giOwppbXBvcnQgeyBMb2dpblBhZ2UgfSBmcm9tICIuL3RlbXBsYXRlcy9Mb2dpblBhZ2UudHN4IjsKCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ2luUGFnZShyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3QgY29udGV4dCA9IGF3YWl0IHdpdGhBdXRoKHJlcSk7CiAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKTsKCiAgLy8gUmVkaXJlY3QgaWYgYWxyZWFkeSBsb2dnZWQgaW4KICBpZiAoY29udGV4dC5jdXJyZW50VXNlcikgewogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKSwgMzAyKTsKICB9CgogIGNvbnN0IGVycm9yID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoImVycm9yIik7CiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8TG9naW5QYWdlIGVycm9yPXtlcnJvciB8fCB1bmRlZmluZWR9IC8+CiAgKTsKfQoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlT0F1dGhBdXRob3JpemUocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIHRyeSB7CiAgICBjb25zdCBmb3JtRGF0YSA9IGF3YWl0IHJlcS5mb3JtRGF0YSgpOwogICAgY29uc3QgbG9naW5IaW50ID0gZm9ybURhdGEuZ2V0KCJsb2dpbkhpbnQiKSBhcyBzdHJpbmc7CgogICAgaWYgKCFsb2dpbkhpbnQpIHsKICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZSgiTWlzc2luZyBsb2dpbiBoaW50IiwgeyBzdGF0dXM6IDQwMCB9KTsKICAgIH0KCiAgICBjb25zdCBhdXRoUmVzdWx0ID0gYXdhaXQgb2F1dGhDbGllbnQuYXV0aG9yaXplKHsKICAgICAgbG9naW5IaW50LAogICAgfSk7CgogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KGF1dGhSZXN1bHQuYXV0aG9yaXphdGlvblVybCwgMzAyKTsKICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiT0F1dGggYXV0aG9yaXplIGVycm9yOiIsIGVycm9yKTsKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgbmV3IFVSTCgKICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIk9BdXRoIGluaXRpYWxpemF0aW9uIGZhaWxlZCIpLAogICAgICAgIHJlcS51cmwKICAgICAgKSwKICAgICAgMzAyCiAgICApOwogIH0KfQoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlT0F1dGhDYWxsYmFjayhyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgdHJ5IHsKICAgIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybCk7CiAgICBjb25zdCBjb2RlID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoImNvZGUiKTsKICAgIGNvbnN0IHN0YXRlID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoInN0YXRlIik7CgogICAgaWYgKCFjb2RlIHx8ICFzdGF0ZSkgewogICAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgICAgbmV3IFVSTCgKICAgICAgICAgICIvbG9naW4/ZXJyb3I9IiArIGVuY29kZVVSSUNvbXBvbmVudCgiSW52YWxpZCBPQXV0aCBjYWxsYmFjayIpLAogICAgICAgICAgcmVxLnVybAogICAgICAgICksCiAgICAgICAgMzAyCiAgICAgICk7CiAgICB9CgogICAgYXdhaXQgb2F1dGhDbGllbnQuaGFuZGxlQ2FsbGJhY2soeyBjb2RlLCBzdGF0ZSB9KTsKICAgIGNvbnN0IHNlc3Npb25JZCA9IGF3YWl0IG9hdXRoU2Vzc2lvbnMuY3JlYXRlT0F1dGhTZXNzaW9uKCk7CgogICAgaWYgKCFzZXNzaW9uSWQpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KAogICAgICAgIG5ldyBVUkwoCiAgICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIkZhaWxlZCB0byBjcmVhdGUgc2Vzc2lvbiIpLAogICAgICAgICAgcmVxLnVybAogICAgICAgICksCiAgICAgICAgMzAyCiAgICAgICk7CiAgICB9CgogICAgY29uc3Qgc2Vzc2lvbkNvb2tpZSA9IHNlc3Npb25TdG9yZS5jcmVhdGVTZXNzaW9uQ29va2llKHNlc3Npb25JZCk7CgogICAgcmV0dXJuIG5ldyBSZXNwb25zZShudWxsLCB7CiAgICAgIHN0YXR1czogMzAyLAogICAgICBoZWFkZXJzOiB7CiAgICAgICAgTG9jYXRpb246IG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKS50b1N0cmluZygpLAogICAgICAgICJTZXQtQ29va2llIjogc2Vzc2lvbkNvb2tpZSwKICAgICAgfSwKICAgIH0pOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBjYWxsYmFjayBlcnJvcjoiLCBlcnJvcik7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgIG5ldyBVUkwoCiAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJBdXRoZW50aWNhdGlvbiBmYWlsZWQiKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ291dChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3Qgc2Vzc2lvbiA9IGF3YWl0IHNlc3Npb25TdG9yZS5nZXRTZXNzaW9uRnJvbVJlcXVlc3QocmVxKTsKCiAgaWYgKHNlc3Npb24pIHsKICAgIGF3YWl0IG9hdXRoU2Vzc2lvbnMubG9nb3V0KHNlc3Npb24uc2Vzc2lvbklkKTsKICB9CgogIGNvbnN0IGNsZWFyQ29va2llID0gc2Vzc2lvblN0b3JlLmNyZWF0ZUxvZ291dENvb2tpZSgpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKG51bGwsIHsKICAgIHN0YXR1czogMzAyLAogICAgaGVhZGVyczogewogICAgICBMb2NhdGlvbjogbmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCkudG9TdHJpbmcoKSwKICAgICAgIlNldC1Db29raWUiOiBjbGVhckNvb2tpZSwKICAgIH0sCiAgfSk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZURhc2hib2FyZChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3QgY29udGV4dCA9IGF3YWl0IHdpdGhBdXRoKHJlcSk7CgogIGlmICghY29udGV4dC5jdXJyZW50VXNlcikgewogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9sb2dpbiIsIHJlcS51cmwpLCAzMDIpOwogIH0KCiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8ZGl2PgogICAgICA8aDE+RGFzaGJvYXJkPC9oMT4KICAgICAgPHA+V2VsY29tZSwge2NvbnRleHQuY3VycmVudFVzZXIubmFtZSB8fCBjb250ZXh0LmN1cnJlbnRVc2VyLnN1Yn0hPC9wPgogICAgICA8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iL2xvZ291dCI+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJzdWJtaXQiPkxvZ291dDwvYnV0dG9uPgogICAgICA8L2Zvcm0+CiAgICA8L2Rpdj4KICApOwp9CgpleHBvcnQgY29uc3QgYXV0aFJvdXRlczogUm91dGVbXSA9IFsKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9sb2dpbiIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVMb2dpblBhZ2UsCiAgfSwKICB7CiAgICBtZXRob2Q6ICJQT1NUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvb2F1dGgvYXV0aG9yaXplIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZU9BdXRoQXV0aG9yaXplLAogIH0sCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvb2F1dGgvY2FsbGJhY2siIH0pLAogICAgaGFuZGxlcjogaGFuZGxlT0F1dGhDYWxsYmFjaywKICB9LAogIHsKICAgIG1ldGhvZDogIlBPU1QiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9sb2dvdXQiIH0pLAogICAgaGFuZGxlcjogaGFuZGxlTG9nb3V0LAogIH0sCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvZGFzaGJvYXJkIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZURhc2hib2FyZCwKICB9LApdOw==" 40 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyBPQXV0aENsaWVudCB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgewogIGNyZWF0ZU9BdXRoQ2xpZW50LAogIGNyZWF0ZVNlc3Npb25DbGllbnQsCiAgb2F1dGhDb25maWcsCiAgb2F1dGhTdG9yYWdlLAogIG9hdXRoU2Vzc2lvbnMsCiAgc2Vzc2lvblN0b3JlLAp9IGZyb20gIi4uLy4uL2NvbmZpZy50cyI7CmltcG9ydCB7IHJlbmRlckhUTUwgfSBmcm9tICIuLi8uLi91dGlscy9yZW5kZXIudHN4IjsKaW1wb3J0IHsgTG9naW5QYWdlIH0gZnJvbSAiLi90ZW1wbGF0ZXMvTG9naW5QYWdlLnRzeCI7Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVMb2dpblBhZ2UocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIGNvbnN0IGNvbnRleHQgPSBhd2FpdCB3aXRoQXV0aChyZXEpOwogIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybCk7CgogIC8vIFJlZGlyZWN0IGlmIGFscmVhZHkgbG9nZ2VkIGluCiAgaWYgKGNvbnRleHQuY3VycmVudFVzZXIpIHsKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdChuZXcgVVJMKCIvZGFzaGJvYXJkIiwgcmVxLnVybCksIDMwMik7CiAgfQoKICBjb25zdCBlcnJvciA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJlcnJvciIpOwogIHJldHVybiByZW5kZXJIVE1MKDxMb2dpblBhZ2UgZXJyb3I9e2Vycm9yIHx8IHVuZGVmaW5lZH0gLz4pOwp9Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVPQXV0aEF1dGhvcml6ZShyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgdHJ5IHsKICAgIGNvbnN0IGZvcm1EYXRhID0gYXdhaXQgcmVxLmZvcm1EYXRhKCk7CiAgICBjb25zdCBsb2dpbkhpbnQgPSBmb3JtRGF0YS5nZXQoImxvZ2luSGludCIpIGFzIHN0cmluZzsKCiAgICBpZiAoIWxvZ2luSGludCkgewogICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKCJNaXNzaW5nIGxvZ2luIGhpbnQiLCB7IHN0YXR1czogNDAwIH0pOwogICAgfQoKICAgIGNvbnN0IHRlbXBPQXV0aENsaWVudCA9IG5ldyBPQXV0aENsaWVudCgKICAgICAgb2F1dGhDb25maWcsCiAgICAgIG9hdXRoU3RvcmFnZSwKICAgICAgbG9naW5IaW50CiAgICApOwogICAgY29uc3QgYXV0aFJlc3VsdCA9IGF3YWl0IHRlbXBPQXV0aENsaWVudC5hdXRob3JpemUoewogICAgICBsb2dpbkhpbnQsCiAgICB9KTsKCiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoYXV0aFJlc3VsdC5hdXRob3JpemF0aW9uVXJsLCAzMDIpOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBhdXRob3JpemUgZXJyb3I6IiwgZXJyb3IpOwoKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgbmV3IFVSTCgKICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKwogICAgICAgICAgZW5jb2RlVVJJQ29tcG9uZW50KCJQbGVhc2UgY2hlY2sgeW91ciBoYW5kbGUgYW5kIHRyeSBhZ2Fpbi4iKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZU9BdXRoQ2FsbGJhY2socmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIHRyeSB7CiAgICBjb25zdCB1cmwgPSBuZXcgVVJMKHJlcS51cmwpOwogICAgY29uc3QgY29kZSA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJjb2RlIik7CiAgICBjb25zdCBzdGF0ZSA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJzdGF0ZSIpOwoKICAgIGlmICghY29kZSB8fCAhc3RhdGUpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KAogICAgICAgIG5ldyBVUkwoCiAgICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIkludmFsaWQgT0F1dGggY2FsbGJhY2siKSwKICAgICAgICAgIHJlcS51cmwKICAgICAgICApLAogICAgICAgIDMwMgogICAgICApOwogICAgfQoKICAgIGNvbnN0IHRlbXBPQXV0aENsaWVudCA9IG5ldyBPQXV0aENsaWVudChvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlLCAidGVtcCIpOwogICAgY29uc3QgdG9rZW5zID0gYXdhaXQgdGVtcE9BdXRoQ2xpZW50LmhhbmRsZUNhbGxiYWNrKHsgY29kZSwgc3RhdGUgfSk7CiAgICBjb25zdCBzZXNzaW9uSWQgPSBhd2FpdCBvYXV0aFNlc3Npb25zLmNyZWF0ZU9BdXRoU2Vzc2lvbih0b2tlbnMpOwoKICAgIGlmICghc2Vzc2lvbklkKSB7CiAgICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgICBuZXcgVVJMKAogICAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJGYWlsZWQgdG8gY3JlYXRlIHNlc3Npb24iKSwKICAgICAgICAgIHJlcS51cmwKICAgICAgICApLAogICAgICAgIDMwMgogICAgICApOwogICAgfQoKICAgIGNvbnN0IHNlc3Npb25Db29raWUgPSBzZXNzaW9uU3RvcmUuY3JlYXRlU2Vzc2lvbkNvb2tpZShzZXNzaW9uSWQpOwoKICAgIGxldCB1c2VySW5mbzsKICAgIHRyeSB7CiAgICAgIGNvbnN0IHNlc3Npb25PQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHNlc3Npb25JZCk7CiAgICAgIHVzZXJJbmZvID0gYXdhaXQgc2Vzc2lvbk9BdXRoQ2xpZW50LmdldFVzZXJJbmZvKCk7CiAgICB9IGNhdGNoIChlcnJvcikgewogICAgICBjb25zb2xlLmVycm9yKCJGYWlsZWQgdG8gZ2V0IHVzZXIgaW5mbzoiLCBlcnJvcik7CiAgICB9CgogICAgaWYgKHVzZXJJbmZvPy5zdWIpIHsKICAgICAgdHJ5IHsKICAgICAgICBjb25zdCB1c2VyQ2xpZW50ID0gY3JlYXRlU2Vzc2lvbkNsaWVudChzZXNzaW9uSWQpOwogICAgICAgIGF3YWl0IHVzZXJDbGllbnQuc3luY1VzZXJDb2xsZWN0aW9ucygpOwogICAgICAgIGNvbnNvbGUubG9nKCJTeW5jZWQgQmx1ZXNreSBwcm9maWxlIGZvciIsIHVzZXJJbmZvLnN1Yik7CiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgY29uc29sZS5lcnJvcigiRXJyb3Igc3luY2luZyBCbHVlc2t5IHByb2ZpbGU6IiwgZXJyb3IpOwogICAgICB9CiAgICB9CgogICAgcmV0dXJuIG5ldyBSZXNwb25zZShudWxsLCB7CiAgICAgIHN0YXR1czogMzAyLAogICAgICBoZWFkZXJzOiB7CiAgICAgICAgTG9jYXRpb246IG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKS50b1N0cmluZygpLAogICAgICAgICJTZXQtQ29va2llIjogc2Vzc2lvbkNvb2tpZSwKICAgICAgfSwKICAgIH0pOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBjYWxsYmFjayBlcnJvcjoiLCBlcnJvcik7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgIG5ldyBVUkwoCiAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJBdXRoZW50aWNhdGlvbiBmYWlsZWQiKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ291dChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3Qgc2Vzc2lvbiA9IGF3YWl0IHNlc3Npb25TdG9yZS5nZXRTZXNzaW9uRnJvbVJlcXVlc3QocmVxKTsKCiAgaWYgKHNlc3Npb24pIHsKICAgIGF3YWl0IG9hdXRoU2Vzc2lvbnMubG9nb3V0KHNlc3Npb24uc2Vzc2lvbklkKTsKICB9CgogIGNvbnN0IGNsZWFyQ29va2llID0gc2Vzc2lvblN0b3JlLmNyZWF0ZUxvZ291dENvb2tpZSgpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKG51bGwsIHsKICAgIHN0YXR1czogMzAyLAogICAgaGVhZGVyczogewogICAgICBMb2NhdGlvbjogbmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCkudG9TdHJpbmcoKSwKICAgICAgIlNldC1Db29raWUiOiBjbGVhckNvb2tpZSwKICAgIH0sCiAgfSk7Cn0KCmV4cG9ydCBjb25zdCBhdXRoUm91dGVzOiBSb3V0ZVtdID0gWwogIHsKICAgIG1ldGhvZDogIkdFVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiL2xvZ2luIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZUxvZ2luUGFnZSwKICB9LAogIHsKICAgIG1ldGhvZDogIlBPU1QiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9vYXV0aC9hdXRob3JpemUiIH0pLAogICAgaGFuZGxlcjogaGFuZGxlT0F1dGhBdXRob3JpemUsCiAgfSwKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9vYXV0aC9jYWxsYmFjayIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVPQXV0aENhbGxiYWNrLAogIH0sCiAgewogICAgbWV0aG9kOiAiUE9TVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiL2xvZ291dCIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVMb2dvdXQsCiAgfSwKXTsK" 41 + }, 42 + { 43 + "path": "src/features/dashboard/templates/DashboardPage.tsx", 44 + "content": "aW1wb3J0IHsgTGF5b3V0IH0gZnJvbSAiLi4vLi4vLi4vc2hhcmVkL2ZyYWdtZW50cy9MYXlvdXQudHN4IjsKaW1wb3J0IHsgQnV0dG9uIH0gZnJvbSAiLi4vLi4vLi4vc2hhcmVkL2ZyYWdtZW50cy9CdXR0b24udHN4IjsKaW1wb3J0IHR5cGUgeyBBcHBCc2t5QWN0b3JQcm9maWxlIH0gZnJvbSAiLi4vLi4vLi4vZ2VuZXJhdGVkX2NsaWVudC50cyI7CgppbnRlcmZhY2UgRGFzaGJvYXJkUGFnZVByb3BzIHsKICBjdXJyZW50VXNlcjogewogICAgbmFtZT86IHN0cmluZzsKICAgIHN1Yjogc3RyaW5nOwogIH07CiAgcHJvZmlsZT86IEFwcEJza3lBY3RvclByb2ZpbGU7CiAgYXZhdGFyVXJsPzogc3RyaW5nOwp9CgpleHBvcnQgZnVuY3Rpb24gRGFzaGJvYXJkUGFnZSh7CiAgY3VycmVudFVzZXIsCiAgcHJvZmlsZSwKICBhdmF0YXJVcmwsCn06IERhc2hib2FyZFBhZ2VQcm9wcykgewogIHJldHVybiAoCiAgICA8TGF5b3V0IHRpdGxlPSJEYXNoYm9hcmQiPgogICAgICA8ZGl2IGNsYXNzTmFtZT0ibWluLWgtc2NyZWVuIGJnLWdyYXktNTAgcC04Ij4KICAgICAgICA8ZGl2IGNsYXNzTmFtZT0ibWF4LXctMnhsIG14LWF1dG8iPgogICAgICAgICAgPGRpdiBjbGFzc05hbWU9ImJnLXdoaXRlIHJvdW5kZWQtbGcgc2hhZG93IHAtNiI+CiAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJmbGV4IGp1c3RpZnktYmV0d2VlbiBpdGVtcy1jZW50ZXIgbWItNiI+CiAgICAgICAgICAgICAgPGgxIGNsYXNzTmFtZT0idGV4dC0yeGwgZm9udC1ib2xkIj5EYXNoYm9hcmQ8L2gxPgogICAgICAgICAgICAgIDxmb3JtIG1ldGhvZD0icG9zdCIgYWN0aW9uPSIvbG9nb3V0Ij4KICAgICAgICAgICAgICAgIDxCdXR0b24gdHlwZT0ic3VibWl0IiB2YXJpYW50PSJzZWNvbmRhcnkiPgogICAgICAgICAgICAgICAgICBMb2dvdXQKICAgICAgICAgICAgICAgIDwvQnV0dG9uPgogICAgICAgICAgICAgIDwvZm9ybT4KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8ZGl2IGNsYXNzTmFtZT0ibWItNiI+CiAgICAgICAgICAgICAge2F2YXRhclVybCAmJiAoCiAgICAgICAgICAgICAgICA8aW1nCiAgICAgICAgICAgICAgICAgIHNyYz17YXZhdGFyVXJsfQogICAgICAgICAgICAgICAgICBhbHQ9IlByb2ZpbGUiCiAgICAgICAgICAgICAgICAgIGNsYXNzTmFtZT0idy0yMCBoLTIwIHJvdW5kZWQtZnVsbCBtYi00IgogICAgICAgICAgICAgICAgLz4KICAgICAgICAgICAgICApfQogICAgICAgICAgICAgIDxoMiBjbGFzc05hbWU9InRleHQteGwgZm9udC1zZW1pYm9sZCBtYi0yIj4KICAgICAgICAgICAgICAgIHtwcm9maWxlPy5kaXNwbGF5TmFtZSB8fCBjdXJyZW50VXNlci5uYW1lIHx8IGN1cnJlbnRVc2VyLnN1Yn0KICAgICAgICAgICAgICA8L2gyPgogICAgICAgICAgICAgIHtjdXJyZW50VXNlci5uYW1lICYmICgKICAgICAgICAgICAgICAgIDxwIGNsYXNzTmFtZT0idGV4dC1ncmF5LTYwMCBtYi0yIj5Ae2N1cnJlbnRVc2VyLm5hbWV9PC9wPgogICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAge3Byb2ZpbGU/LmRlc2NyaXB0aW9uICYmICgKICAgICAgICAgICAgICAgIDxwIGNsYXNzTmFtZT0idGV4dC1ncmF5LTcwMCBtdC0yIj57cHJvZmlsZS5kZXNjcmlwdGlvbn08L3A+CiAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L0xheW91dD4KICApOwp9Cg==" 45 + }, 46 + { 47 + "path": "src/features/dashboard/handlers.tsx", 48 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyByZW5kZXJIVE1MIH0gZnJvbSAiLi4vLi4vdXRpbHMvcmVuZGVyLnRzeCI7CmltcG9ydCB7IERhc2hib2FyZFBhZ2UgfSBmcm9tICIuL3RlbXBsYXRlcy9EYXNoYm9hcmRQYWdlLnRzeCI7CmltcG9ydCB7IHB1YmxpY0NsaWVudCB9IGZyb20gIi4uLy4uL2NvbmZpZy50cyI7CmltcG9ydCB7IHJlY29yZEJsb2JUb0NkblVybCB9IGZyb20gIkBzbGljZXMvY2xpZW50IjsKaW1wb3J0IHsgQXBwQnNreUFjdG9yUHJvZmlsZSB9IGZyb20gIi4uLy4uL2dlbmVyYXRlZF9jbGllbnQudHMiOwoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlRGFzaGJvYXJkKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+IHsKICBjb25zdCBjb250ZXh0ID0gYXdhaXQgd2l0aEF1dGgocmVxKTsKCiAgaWYgKCFjb250ZXh0LmN1cnJlbnRVc2VyKSB7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMik7CiAgfQoKICBsZXQgcHJvZmlsZTogQXBwQnNreUFjdG9yUHJvZmlsZSB8IHVuZGVmaW5lZDsKICBsZXQgYXZhdGFyVXJsOiBzdHJpbmcgfCB1bmRlZmluZWQ7CiAgdHJ5IHsKICAgIGNvbnN0IHByb2ZpbGVSZXN1bHQgPSBhd2FpdCBwdWJsaWNDbGllbnQuYXBwLmJza3kuYWN0b3IucHJvZmlsZS5nZXRSZWNvcmQoewogICAgICB1cmk6IGBhdDovLyR7Y29udGV4dC5jdXJyZW50VXNlci5zdWJ9L2FwcC5ic2t5LmFjdG9yLnByb2ZpbGUvc2VsZmAsCiAgICB9KTsKCiAgICBpZiAocHJvZmlsZVJlc3VsdCkgewogICAgICBwcm9maWxlID0gcHJvZmlsZVJlc3VsdC52YWx1ZTsKCiAgICAgIGlmIChwcm9maWxlLmF2YXRhcikgewogICAgICAgIGF2YXRhclVybCA9IHJlY29yZEJsb2JUb0NkblVybChwcm9maWxlUmVzdWx0LCBwcm9maWxlLmF2YXRhciwgImF2YXRhciIpOwogICAgICB9CiAgICB9CiAgfSBjYXRjaCAoZXJyb3IpIHsKICAgIGNvbnNvbGUuZXJyb3IoIkVycm9yIGZldGNoaW5nIHByb2ZpbGU6IiwgZXJyb3IpOwogIH0KCiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8RGFzaGJvYXJkUGFnZQogICAgICBjdXJyZW50VXNlcj17Y29udGV4dC5jdXJyZW50VXNlcn0KICAgICAgcHJvZmlsZT17cHJvZmlsZX0KICAgICAgYXZhdGFyVXJsPXthdmF0YXJVcmx9CiAgICAvPgogICk7Cn0KCmV4cG9ydCBjb25zdCBkYXNoYm9hcmRSb3V0ZXM6IFJvdXRlW10gPSBbCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvZGFzaGJvYXJkIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZURhc2hib2FyZCwKICB9LApdOwo=" 49 + }, 50 + { 51 + "path": "src/utils/cn.ts", 52 + "content": "aW1wb3J0IHsgdHlwZSBDbGFzc1ZhbHVlLCBjbHN4IH0gZnJvbSAiY2xzeCI7CmltcG9ydCB7IHR3TWVyZ2UgfSBmcm9tICJ0YWlsd2luZC1tZXJnZSI7CgpleHBvcnQgZnVuY3Rpb24gY24oLi4uaW5wdXRzOiBDbGFzc1ZhbHVlW10pOiBzdHJpbmcgewogIHJldHVybiB0d01lcmdlKGNsc3goaW5wdXRzKSk7Cn0=" 41 53 }, 42 54 { 43 55 "path": "src/utils/logging.ts", 44 - "content": "ZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUxvZ2dpbmdIYW5kbGVyKGhhbmRsZXI6IChyZXE6IFJlcXVlc3QpID0+IFJlc3BvbnNlIHwgUHJvbWlzZTxSZXNwb25zZT4pIHsKICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+ID0+IHsKICAgIGNvbnN0IHN0YXJ0ID0gRGF0ZS5ub3coKTsKICAgIGNvbnN0IG1ldGhvZCA9IHJlcS5tZXRob2Q7CiAgICBjb25zdCB1cmwgPSBuZXcgVVJMKHJlcS51cmwpOwoKICAgIHRyeSB7CiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgUHJvbWlzZS5yZXNvbHZlKGhhbmRsZXIocmVxKSk7CiAgICAgIGNvbnN0IGR1cmF0aW9uID0gRGF0ZS5ub3coKSAtIHN0YXJ0OwogICAgICBjb25zb2xlLmxvZyhgJHttZXRob2R9ICR7dXJsLnBhdGhuYW1lfSAtICR7cmVzcG9uc2Uuc3RhdHVzfSAoJHtkdXJhdGlvbn1tcylgKTsKICAgICAgcmV0dXJuIHJlc3BvbnNlOwogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnQ7CiAgICAgIGNvbnNvbGUuZXJyb3IoYCR7bWV0aG9kfSAke3VybC5wYXRobmFtZX0gLSBFUlJPUiAoJHtkdXJhdGlvbn1tcyk6YCwgZXJyb3IpOwogICAgICB0aHJvdyBlcnJvcjsKICAgIH0KICB9Owp9" 56 + "content": "aW1wb3J0IHsgY3lhbiwgZ3JlZW4sIHJlZCwgeWVsbG93LCBib2xkLCBkaW0gfSBmcm9tICJAc3RkL2ZtdC9jb2xvcnMiOwoKZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUxvZ2dpbmdIYW5kbGVyKAogIGhhbmRsZXI6IChyZXE6IFJlcXVlc3QpID0+IFJlc3BvbnNlIHwgUHJvbWlzZTxSZXNwb25zZT4KKSB7CiAgcmV0dXJuIGFzeW5jIChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiA9PiB7CiAgICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7CiAgICBjb25zdCBtZXRob2QgPSByZXEubWV0aG9kOwogICAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKTsKCiAgICB0cnkgewogICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IFByb21pc2UucmVzb2x2ZShoYW5kbGVyKHJlcSkpOwogICAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydDsKCiAgICAgIGNvbnN0IG1ldGhvZENvbG9yID0gY3lhbihib2xkKG1ldGhvZCkpOwogICAgICBjb25zdCBzdGF0dXNDb2xvciA9CiAgICAgICAgcmVzcG9uc2Uuc3RhdHVzID49IDIwMCAmJiByZXNwb25zZS5zdGF0dXMgPCAzMDAKICAgICAgICAgID8gZ3JlZW4oU3RyaW5nKHJlc3BvbnNlLnN0YXR1cykpCiAgICAgICAgICA6IHJlc3BvbnNlLnN0YXR1cyA+PSAzMDAgJiYgcmVzcG9uc2Uuc3RhdHVzIDwgNDAwCiAgICAgICAgICA/IHllbGxvdyhTdHJpbmcocmVzcG9uc2Uuc3RhdHVzKSkKICAgICAgICAgIDogcmVzcG9uc2Uuc3RhdHVzID49IDQwMAogICAgICAgICAgPyByZWQoU3RyaW5nKHJlc3BvbnNlLnN0YXR1cykpCiAgICAgICAgICA6IFN0cmluZyhyZXNwb25zZS5zdGF0dXMpOwogICAgICBjb25zdCBkdXJhdGlvblRleHQgPSBkaW0oYCgke2R1cmF0aW9ufW1zKWApOwoKICAgICAgY29uc29sZS5sb2coCiAgICAgICAgYCR7bWV0aG9kQ29sb3J9ICR7dXJsLnBhdGhuYW1lfSAtICR7c3RhdHVzQ29sb3J9ICR7ZHVyYXRpb25UZXh0fWAKICAgICAgKTsKICAgICAgcmV0dXJuIHJlc3BvbnNlOwogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnQ7CiAgICAgIGNvbnN0IG1ldGhvZENvbG9yID0gY3lhbihib2xkKG1ldGhvZCkpOwogICAgICBjb25zdCBlcnJvclRleHQgPSByZWQoYm9sZCgiRVJST1IiKSk7CiAgICAgIGNvbnN0IGR1cmF0aW9uVGV4dCA9IGRpbShgKCR7ZHVyYXRpb259bXMpYCk7CgogICAgICBjb25zb2xlLmVycm9yKAogICAgICAgIGAke21ldGhvZENvbG9yfSAke3VybC5wYXRobmFtZX0gLSAke2Vycm9yVGV4dH0gJHtkdXJhdGlvblRleHR9OmAsCiAgICAgICAgZXJyb3IKICAgICAgKTsKICAgICAgdGhyb3cgZXJyb3I7CiAgICB9CiAgfTsKfQo=" 45 57 }, 46 58 { 47 59 "path": "src/utils/render.tsx", 48 - "content": "aW1wb3J0IHsgcmVuZGVyVG9TdHJpbmcgfSBmcm9tICJwcmVhY3QtcmVuZGVyLXRvLXN0cmluZyI7CgpleHBvcnQgZnVuY3Rpb24gcmVuZGVySFRNTChlbGVtZW50OiBhbnkpOiBSZXNwb25zZSB7CiAgY29uc3QgaHRtbCA9IHJlbmRlclRvU3RyaW5nKGVsZW1lbnQpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKGh0bWwsIHsKICAgIGhlYWRlcnM6IHsKICAgICAgIkNvbnRlbnQtVHlwZSI6ICJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiLAogICAgfSwKICB9KTsKfQ==" 60 + "content": "aW1wb3J0IHsgcmVuZGVyVG9TdHJpbmcgfSBmcm9tICJwcmVhY3QtcmVuZGVyLXRvLXN0cmluZyI7CmltcG9ydCB7IFZOb2RlIH0gZnJvbSAicHJlYWN0IjsKCmV4cG9ydCBmdW5jdGlvbiByZW5kZXJIVE1MKGVsZW1lbnQ6IFZOb2RlKTogUmVzcG9uc2UgewogIGNvbnN0IGh0bWwgPSByZW5kZXJUb1N0cmluZyhlbGVtZW50KTsKCiAgcmV0dXJuIG5ldyBSZXNwb25zZShodG1sLCB7CiAgICBoZWFkZXJzOiB7CiAgICAgICJDb250ZW50LVR5cGUiOiAidGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiwKICAgIH0sCiAgfSk7Cn0=" 49 61 }, 50 62 { 51 63 "path": "src/shared/fragments/Layout.tsx", 52 - "content": "aW50ZXJmYWNlIExheW91dFByb3BzIHsKICB0aXRsZT86IHN0cmluZzsKICBjaGlsZHJlbjogYW55Owp9CgpleHBvcnQgZnVuY3Rpb24gTGF5b3V0KHsgdGl0bGUgPSAiQXBwIiwgY2hpbGRyZW4gfTogTGF5b3V0UHJvcHMpIHsKICByZXR1cm4gKAogICAgPGh0bWwgbGFuZz0iZW4iPgogICAgICA8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCIgLz4KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCIgLz4KICAgICAgICA8dGl0bGU+e3RpdGxlfTwvdGl0bGU+CiAgICAgICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuLnRhaWx3aW5kY3NzLmNvbSI+PC9zY3JpcHQ+CiAgICAgICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vdW5wa2cuY29tL2h0bXgub3JnQDEuOS4xMCI+PC9zY3JpcHQ+CiAgICAgIDwvaGVhZD4KICAgICAgPGJvZHk+CiAgICAgICAge2NoaWxkcmVufQogICAgICA8L2JvZHk+CiAgICA8L2h0bWw+CiAgKTsKfQ==" 64 + "content": "aW1wb3J0IHsgQ29tcG9uZW50Q2hpbGRyZW4gfSBmcm9tICJwcmVhY3QiOwoKaW50ZXJmYWNlIExheW91dFByb3BzIHsKICB0aXRsZT86IHN0cmluZzsKICBjaGlsZHJlbjogQ29tcG9uZW50Q2hpbGRyZW47Cn0KCmV4cG9ydCBmdW5jdGlvbiBMYXlvdXQoeyB0aXRsZSA9ICJBcHAiLCBjaGlsZHJlbiB9OiBMYXlvdXRQcm9wcykgewogIHJldHVybiAoCiAgICA8aHRtbCBsYW5nPSJlbiI+CiAgICAgIDxoZWFkPgogICAgICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04IiAvPgogICAgICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIiAvPgogICAgICAgIDx0aXRsZT57dGl0bGV9PC90aXRsZT4KICAgICAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4udGFpbHdpbmRjc3MuY29tIj48L3NjcmlwdD4KICAgICAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly91bnBrZy5jb20vaHRteC5vcmdAMS45LjEwIj48L3NjcmlwdD4KICAgICAgPC9oZWFkPgogICAgICA8Ym9keT4KICAgICAgICB7Y2hpbGRyZW59CiAgICAgIDwvYm9keT4KICAgIDwvaHRtbD4KICApOwp9" 53 65 }, 54 66 { 55 67 "path": "src/shared/fragments/Button.tsx", 56 - "content": "aW1wb3J0IHsgY2xzeCB9IGZyb20gImNsc3giOwoKaW50ZXJmYWNlIEJ1dHRvblByb3BzIHsKICBjaGlsZHJlbjogYW55OwogIHR5cGU/OiAiYnV0dG9uIiB8ICJzdWJtaXQiIHwgInJlc2V0IjsKICB2YXJpYW50PzogInByaW1hcnkiIHwgInNlY29uZGFyeSIgfCAiZGFuZ2VyIjsKICBzaXplPzogInNtIiB8ICJtZCIgfCAibGciOwogIGNsYXNzTmFtZT86IHN0cmluZzsKICBkaXNhYmxlZD86IGJvb2xlYW47CiAgW2tleTogc3RyaW5nXTogYW55Owp9CgpleHBvcnQgZnVuY3Rpb24gQnV0dG9uKHsKICBjaGlsZHJlbiwKICB0eXBlID0gImJ1dHRvbiIsCiAgdmFyaWFudCA9ICJwcmltYXJ5IiwKICBzaXplID0gIm1kIiwKICBjbGFzc05hbWUsCiAgZGlzYWJsZWQsCiAgLi4ucHJvcHMKfTogQnV0dG9uUHJvcHMpIHsKICBjb25zdCBiYXNlQ2xhc3NlcyA9ICJpbmxpbmUtZmxleCBpdGVtcy1jZW50ZXIganVzdGlmeS1jZW50ZXIgZm9udC1tZWRpdW0gcm91bmRlZC1tZCBmb2N1czpvdXRsaW5lLW5vbmUgZm9jdXM6cmluZy0yIGZvY3VzOnJpbmctb2Zmc2V0LTIgZGlzYWJsZWQ6b3BhY2l0eS01MCBkaXNhYmxlZDpjdXJzb3Itbm90LWFsbG93ZWQiOwoKICBjb25zdCB2YXJpYW50Q2xhc3NlcyA9IHsKICAgIHByaW1hcnk6ICJiZy1ibHVlLTYwMCBob3ZlcjpiZy1ibHVlLTcwMCB0ZXh0LXdoaXRlIGZvY3VzOnJpbmctYmx1ZS01MDAiLAogICAgc2Vjb25kYXJ5OiAiYmctZ3JheS0yMDAgaG92ZXI6YmctZ3JheS0zMDAgdGV4dC1ncmF5LTkwMCBmb2N1czpyaW5nLWdyYXktNTAwIiwKICAgIGRhbmdlcjogImJnLXJlZC02MDAgaG92ZXI6YmctcmVkLTcwMCB0ZXh0LXdoaXRlIGZvY3VzOnJpbmctcmVkLTUwMCIsCiAgfTsKCiAgY29uc3Qgc2l6ZUNsYXNzZXMgPSB7CiAgICBzbTogInB4LTMgcHktMS41IHRleHQtc20iLAogICAgbWQ6ICJweC00IHB5LTIgdGV4dC1zbSIsCiAgICBsZzogInB4LTYgcHktMyB0ZXh0LWJhc2UiLAogIH07CgogIHJldHVybiAoCiAgICA8YnV0dG9uCiAgICAgIHR5cGU9e3R5cGV9CiAgICAgIGRpc2FibGVkPXtkaXNhYmxlZH0KICAgICAgY2xhc3NOYW1lPXtjbHN4KAogICAgICAgIGJhc2VDbGFzc2VzLAogICAgICAgIHZhcmlhbnRDbGFzc2VzW3ZhcmlhbnRdLAogICAgICAgIHNpemVDbGFzc2VzW3NpemVdLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICA+CiAgICAgIHtjaGlsZHJlbn0KICAgIDwvYnV0dG9uPgogICk7Cn0=" 68 + "content": "aW1wb3J0IHsgQ29tcG9uZW50Q2hpbGRyZW4sIEpTWCB9IGZyb20gInByZWFjdCI7CmltcG9ydCB7IGNuIH0gZnJvbSAiLi4vLi4vdXRpbHMvY24udHMiOwoKaW50ZXJmYWNlIEJ1dHRvblByb3BzIGV4dGVuZHMgT21pdDxKU1guSW50cmluc2ljRWxlbWVudHNbJ2J1dHRvbiddLCAic2l6ZSI+IHsKICBjaGlsZHJlbjogQ29tcG9uZW50Q2hpbGRyZW47CiAgdmFyaWFudD86ICJwcmltYXJ5IiB8ICJzZWNvbmRhcnkiIHwgImRhbmdlciI7CiAgc2l6ZT86ICJzbSIgfCAibWQiIHwgImxnIjsKfQoKZXhwb3J0IGZ1bmN0aW9uIEJ1dHRvbih7CiAgY2hpbGRyZW4sCiAgdHlwZSA9ICJidXR0b24iLAogIHZhcmlhbnQgPSAicHJpbWFyeSIsCiAgc2l6ZSA9ICJtZCIsCiAgY2xhc3NOYW1lLAogIGRpc2FibGVkLAogIC4uLnByb3BzCn06IEJ1dHRvblByb3BzKSB7CiAgY29uc3QgYmFzZUNsYXNzZXMgPQogICAgImlubGluZS1mbGV4IGl0ZW1zLWNlbnRlciBqdXN0aWZ5LWNlbnRlciBmb250LW1lZGl1bSByb3VuZGVkLW1kIGZvY3VzOm91dGxpbmUtbm9uZSBmb2N1czpyaW5nLTIgZm9jdXM6cmluZy1vZmZzZXQtMiBkaXNhYmxlZDpvcGFjaXR5LTUwIGRpc2FibGVkOmN1cnNvci1ub3QtYWxsb3dlZCI7CgogIGNvbnN0IHZhcmlhbnRDbGFzc2VzID0gewogICAgcHJpbWFyeTogImJnLWJsdWUtNjAwIGhvdmVyOmJnLWJsdWUtNzAwIHRleHQtd2hpdGUgZm9jdXM6cmluZy1ibHVlLTUwMCIsCiAgICBzZWNvbmRhcnk6CiAgICAgICJiZy1ncmF5LTIwMCBob3ZlcjpiZy1ncmF5LTMwMCB0ZXh0LWdyYXktOTAwIGZvY3VzOnJpbmctZ3JheS01MDAiLAogICAgZGFuZ2VyOiAiYmctcmVkLTYwMCBob3ZlcjpiZy1yZWQtNzAwIHRleHQtd2hpdGUgZm9jdXM6cmluZy1yZWQtNTAwIiwKICB9OwoKICBjb25zdCBzaXplQ2xhc3NlcyA9IHsKICAgIHNtOiAicHgtMyBweS0xLjUgdGV4dC1zbSIsCiAgICBtZDogInB4LTQgcHktMiB0ZXh0LXNtIiwKICAgIGxnOiAicHgtNiBweS0zIHRleHQtYmFzZSIsCiAgfTsKCiAgcmV0dXJuICgKICAgIDxidXR0b24KICAgICAgdHlwZT17dHlwZX0KICAgICAgZGlzYWJsZWQ9e2Rpc2FibGVkfQogICAgICBjbGFzc05hbWU9e2NuKAogICAgICAgIGJhc2VDbGFzc2VzLAogICAgICAgIHZhcmlhbnRDbGFzc2VzW3ZhcmlhbnRdLAogICAgICAgIHNpemVDbGFzc2VzW3NpemVdLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICA+CiAgICAgIHtjaGlsZHJlbn0KICAgIDwvYnV0dG9uPgogICk7Cn0K" 57 69 }, 58 70 { 59 71 "path": "src/shared/fragments/Input.tsx", 60 - "content": "aW1wb3J0IHsgY2xzeCB9IGZyb20gImNsc3giOwoKaW50ZXJmYWNlIElucHV0UHJvcHMgewogIHR5cGU/OiBzdHJpbmc7CiAgbmFtZT86IHN0cmluZzsKICBpZD86IHN0cmluZzsKICBwbGFjZWhvbGRlcj86IHN0cmluZzsKICByZXF1aXJlZD86IGJvb2xlYW47CiAgZGlzYWJsZWQ/OiBib29sZWFuOwogIGNsYXNzTmFtZT86IHN0cmluZzsKICB2YWx1ZT86IHN0cmluZzsKICBba2V5OiBzdHJpbmddOiBhbnk7Cn0KCmV4cG9ydCBmdW5jdGlvbiBJbnB1dCh7CiAgdHlwZSA9ICJ0ZXh0IiwKICBjbGFzc05hbWUsCiAgLi4ucHJvcHMKfTogSW5wdXRQcm9wcykgewogIHJldHVybiAoCiAgICA8aW5wdXQKICAgICAgdHlwZT17dHlwZX0KICAgICAgY2xhc3NOYW1lPXtjbHN4KAogICAgICAgICJibG9jayB3LWZ1bGwgcHgtMyBweS0yIGJvcmRlciBib3JkZXItZ3JheS0zMDAgcm91bmRlZC1tZCBzaGFkb3ctc20iLAogICAgICAgICJmb2N1czpvdXRsaW5lLW5vbmUgZm9jdXM6cmluZy1ibHVlLTUwMCBmb2N1czpib3JkZXItYmx1ZS01MDAiLAogICAgICAgICJkaXNhYmxlZDpiZy1ncmF5LTUwIGRpc2FibGVkOnRleHQtZ3JheS01MDAiLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICAvPgogICk7Cn0=" 72 + "content": "aW1wb3J0IHsgSlNYIH0gZnJvbSAicHJlYWN0IjsKaW1wb3J0IHsgY24gfSBmcm9tICIuLi8uLi91dGlscy9jbi50cyI7Cgp0eXBlIElucHV0UHJvcHMgPSBKU1guSW50cmluc2ljRWxlbWVudHNbJ2lucHV0J107CgpleHBvcnQgZnVuY3Rpb24gSW5wdXQoewogIHR5cGUgPSAidGV4dCIsCiAgY2xhc3NOYW1lLAogIC4uLnByb3BzCn06IElucHV0UHJvcHMpIHsKICByZXR1cm4gKAogICAgPGlucHV0CiAgICAgIHR5cGU9e3R5cGV9CiAgICAgIGNsYXNzTmFtZT17Y24oCiAgICAgICAgImJsb2NrIHctZnVsbCBweC0zIHB5LTIgYm9yZGVyIGJvcmRlci1ncmF5LTMwMCByb3VuZGVkLW1kIHNoYWRvdy1zbSIsCiAgICAgICAgImZvY3VzOm91dGxpbmUtbm9uZSBmb2N1czpyaW5nLWJsdWUtNTAwIGZvY3VzOmJvcmRlci1ibHVlLTUwMCIsCiAgICAgICAgImRpc2FibGVkOmJnLWdyYXktNTAgZGlzYWJsZWQ6dGV4dC1ncmF5LTUwMCIsCiAgICAgICAgY2xhc3NOYW1lCiAgICAgICl9CiAgICAgIHsuLi5wcm9wc30KICAgIC8+CiAgKTsKfQ==" 61 73 }, 62 74 { 63 75 "path": "src/config.ts", 64 - "content": "aW1wb3J0IHsgT0F1dGhDbGllbnQsIFNRTGl0ZU9BdXRoU3RvcmFnZSB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgeyBTZXNzaW9uU3RvcmUsIFNRTGl0ZUFkYXB0ZXIsIHdpdGhPQXV0aFNlc3Npb24gfSBmcm9tICJAc2xpY2VzL3Nlc3Npb24iOwoKY29uc3QgT0FVVEhfQ0xJRU5UX0lEID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9DTElFTlRfSUQiKTsKY29uc3QgT0FVVEhfQ0xJRU5UX1NFQ1JFVCA9IERlbm8uZW52LmdldCgiT0FVVEhfQ0xJRU5UX1NFQ1JFVCIpOwpjb25zdCBPQVVUSF9SRURJUkVDVF9VUkkgPSBEZW5vLmVudi5nZXQoIk9BVVRIX1JFRElSRUNUX1VSSSIpOwpjb25zdCBPQVVUSF9BSVBfQkFTRV9VUkwgPSBEZW5vLmVudi5nZXQoIk9BVVRIX0FJUF9CQVNFX1VSTCIpOwpjb25zdCBBUElfVVJMID0gRGVuby5lbnYuZ2V0KCJBUElfVVJMIik7CmV4cG9ydCBjb25zdCBTTElDRV9VUkkgPSBEZW5vLmVudi5nZXQoIlNMSUNFX1VSSSIpOwoKaWYgKAogICFPQVVUSF9DTElFTlRfSUQgfHwKICAhT0FVVEhfQ0xJRU5UX1NFQ1JFVCB8fAogICFPQVVUSF9SRURJUkVDVF9VUkkgfHwKICAhT0FVVEhfQUlQX0JBU0VfVVJMIHx8CiAgIUFQSV9VUkwgfHwKICAhU0xJQ0VfVVJJCikgewogIHRocm93IG5ldyBFcnJvcigKICAgICJNaXNzaW5nIE9BdXRoIGNvbmZpZ3VyYXRpb24uIFBsZWFzZSBlbnN1cmUgLmVudiBmaWxlIGNvbnRhaW5zOlxuIiArCiAgICAgICJPQVVUSF9DTElFTlRfSUQsIE9BVVRIX0NMSUVOVF9TRUNSRVQsIE9BVVRIX1JFRElSRUNUX1VSSSwgT0FVVEhfQUlQX0JBU0VfVVJMLCBBUElfVVJMLCBTTElDRV9VUkkiCiAgKTsKfQoKY29uc3QgREFUQUJBU0VfVVJMID0gRGVuby5lbnYuZ2V0KCJEQVRBQkFTRV9VUkwiKSB8fCAic2xpY2VzLmRiIjsKCi8vIE9BdXRoIHNldHVwCmNvbnN0IG9hdXRoU3RvcmFnZSA9IG5ldyBTUUxpdGVPQXV0aFN0b3JhZ2UoREFUQUJBU0VfVVJMKTsKY29uc3Qgb2F1dGhDbGllbnQgPSBuZXcgT0F1dGhDbGllbnQoCiAgewogICAgY2xpZW50SWQ6IE9BVVRIX0NMSUVOVF9JRCwKICAgIGNsaWVudFNlY3JldDogT0FVVEhfQ0xJRU5UX1NFQ1JFVCwKICAgIGF1dGhCYXNlVXJsOiBPQVVUSF9BSVBfQkFTRV9VUkwsCiAgICByZWRpcmVjdFVyaTogT0FVVEhfUkVESVJFQ1RfVVJJLAogICAgc2NvcGVzOiBbCiAgICAgICJvcGVuaWQiLAogICAgICAiZW1haWwiLAogICAgICAicHJvZmlsZSIsCiAgICAgICJhdHByb3RvIiwKICAgICAgInRyYW5zaXRpb246Z2VuZXJpYyIsCiAgICBdLAogIH0sCiAgb2F1dGhTdG9yYWdlCik7CgovLyBTZXNzaW9uIHNldHVwIChzaGFyZWQgZGF0YWJhc2UpCmV4cG9ydCBjb25zdCBzZXNzaW9uU3RvcmUgPSBuZXcgU2Vzc2lvblN0b3JlKHsKICBhZGFwdGVyOiBuZXcgU1FMaXRlQWRhcHRlcihEQVRBQkFTRV9VUkwpLAogIGNvb2tpZU9wdGlvbnM6IHsKICAgIGh0dHBPbmx5OiB0cnVlLAogICAgc2VjdXJlOiBEZW5vLmVudi5nZXQoIkRFTk9fRU5WIikgPT09ICJwcm9kdWN0aW9uIiwKICAgIHNhbWVTaXRlOiAibGF4IiwKICAgIHBhdGg6ICIvIiwKICB9LAp9KTsKCi8vIE9BdXRoICsgU2Vzc2lvbiBpbnRlZ3JhdGlvbgpleHBvcnQgY29uc3Qgb2F1dGhTZXNzaW9ucyA9IHdpdGhPQXV0aFNlc3Npb24oc2Vzc2lvblN0b3JlLCBvYXV0aENsaWVudCwgewogIGF1dG9SZWZyZXNoOiB0cnVlLAp9KTsKCmV4cG9ydCB7IG9hdXRoQ2xpZW50IH07" 76 + "content": "aW1wb3J0IHsgT0F1dGhDbGllbnQsIFNRTGl0ZU9BdXRoU3RvcmFnZSB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgeyBTZXNzaW9uU3RvcmUsIFNRTGl0ZUFkYXB0ZXIsIHdpdGhPQXV0aFNlc3Npb24gfSBmcm9tICJAc2xpY2VzL3Nlc3Npb24iOwppbXBvcnQgeyBBdFByb3RvQ2xpZW50IH0gZnJvbSAiLi9nZW5lcmF0ZWRfY2xpZW50LnRzIjsKCmNvbnN0IE9BVVRIX0NMSUVOVF9JRCA9IERlbm8uZW52LmdldCgiT0FVVEhfQ0xJRU5UX0lEIik7CmNvbnN0IE9BVVRIX0NMSUVOVF9TRUNSRVQgPSBEZW5vLmVudi5nZXQoIk9BVVRIX0NMSUVOVF9TRUNSRVQiKTsKY29uc3QgT0FVVEhfUkVESVJFQ1RfVVJJID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9SRURJUkVDVF9VUkkiKTsKY29uc3QgT0FVVEhfQUlQX0JBU0VfVVJMID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9BSVBfQkFTRV9VUkwiKTsKY29uc3QgQVBJX1VSTCA9IERlbm8uZW52LmdldCgiQVBJX1VSTCIpOwpleHBvcnQgY29uc3QgU0xJQ0VfVVJJID0gRGVuby5lbnYuZ2V0KCJTTElDRV9VUkkiKTsKCmlmICgKICAhT0FVVEhfQ0xJRU5UX0lEIHx8CiAgIU9BVVRIX0NMSUVOVF9TRUNSRVQgfHwKICAhT0FVVEhfUkVESVJFQ1RfVVJJIHx8CiAgIU9BVVRIX0FJUF9CQVNFX1VSTCB8fAogICFBUElfVVJMIHx8CiAgIVNMSUNFX1VSSQopIHsKICB0aHJvdyBuZXcgRXJyb3IoCiAgICAiTWlzc2luZyBPQXV0aCBjb25maWd1cmF0aW9uLiBQbGVhc2UgZW5zdXJlIC5lbnYgZmlsZSBjb250YWluczpcbiIgKwogICAgICAiT0FVVEhfQ0xJRU5UX0lELCBPQVVUSF9DTElFTlRfU0VDUkVULCBPQVVUSF9SRURJUkVDVF9VUkksIE9BVVRIX0FJUF9CQVNFX1VSTCwgQVBJX1VSTCwgU0xJQ0VfVVJJIgogICk7Cn0KCmNvbnN0IERBVEFCQVNFX1VSTCA9IERlbm8uZW52LmdldCgiREFUQUJBU0VfVVJMIikgfHwgInNsaWNlcy5kYiI7CgovLyBPQXV0aCBzZXR1cApjb25zdCBvYXV0aFN0b3JhZ2UgPSBuZXcgU1FMaXRlT0F1dGhTdG9yYWdlKERBVEFCQVNFX1VSTCk7CmNvbnN0IG9hdXRoQ29uZmlnID0gewogIGNsaWVudElkOiBPQVVUSF9DTElFTlRfSUQsCiAgY2xpZW50U2VjcmV0OiBPQVVUSF9DTElFTlRfU0VDUkVULAogIGF1dGhCYXNlVXJsOiBPQVVUSF9BSVBfQkFTRV9VUkwsCiAgcmVkaXJlY3RVcmk6IE9BVVRIX1JFRElSRUNUX1VSSSwKICBzY29wZXM6IFsiYXRwcm90byIsICJvcGVuaWQiLCAicHJvZmlsZSJdLAp9OwoKLy8gRXhwb3J0IGNvbmZpZyBhbmQgc3RvcmFnZSBmb3IgY3JlYXRpbmcgdXNlci1zY29wZWQgY2xpZW50cwpleHBvcnQgeyBvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlIH07CgovLyBTZXNzaW9uIHNldHVwIChzaGFyZWQgZGF0YWJhc2UpCmV4cG9ydCBjb25zdCBzZXNzaW9uU3RvcmUgPSBuZXcgU2Vzc2lvblN0b3JlKHsKICBhZGFwdGVyOiBuZXcgU1FMaXRlQWRhcHRlcihEQVRBQkFTRV9VUkwpLAogIGNvb2tpZU5hbWU6ICJ7e1BST0pFQ1RfTkFNRX19LXNlc3Npb24iLAogIGNvb2tpZU9wdGlvbnM6IHsKICAgIGh0dHBPbmx5OiB0cnVlLAogICAgc2VjdXJlOiBEZW5vLmVudi5nZXQoIkRFTk9fRU5WIikgPT09ICJwcm9kdWN0aW9uIiwKICAgIHNhbWVTaXRlOiAibGF4IiwKICAgIHBhdGg6ICIvIiwKICB9LAp9KTsKCi8vIE9BdXRoICsgU2Vzc2lvbiBpbnRlZ3JhdGlvbgpleHBvcnQgY29uc3Qgb2F1dGhTZXNzaW9ucyA9IHdpdGhPQXV0aFNlc3Npb24oCiAgc2Vzc2lvblN0b3JlLAogIG9hdXRoQ29uZmlnLAogIG9hdXRoU3RvcmFnZSwKICB7CiAgICBhdXRvUmVmcmVzaDogdHJ1ZSwKICB9Cik7CgovLyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIHVzZXItc2NvcGVkIE9BdXRoIGNsaWVudApleHBvcnQgZnVuY3Rpb24gY3JlYXRlT0F1dGhDbGllbnQodXNlcklkOiBzdHJpbmcpOiBPQXV0aENsaWVudCB7CiAgcmV0dXJuIG5ldyBPQXV0aENsaWVudChvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlLCB1c2VySWQpOwp9CgovLyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIGF1dGhlbnRpY2F0ZWQgQXRQcm90byBjbGllbnQgZm9yIGEgdXNlcgpleHBvcnQgZnVuY3Rpb24gY3JlYXRlU2Vzc2lvbkNsaWVudCh1c2VySWQ6IHN0cmluZyk6IEF0UHJvdG9DbGllbnQgewogIGNvbnN0IHVzZXJPQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHVzZXJJZCk7CiAgcmV0dXJuIG5ldyBBdFByb3RvQ2xpZW50KEFQSV9VUkwhLCBTTElDRV9VUkkhLCB1c2VyT0F1dGhDbGllbnQpOwp9CgovLyBQdWJsaWMgY2xpZW50IGZvciB1bmF1dGhlbnRpY2F0ZWQgcmVxdWVzdHMKZXhwb3J0IGNvbnN0IHB1YmxpY0NsaWVudCA9IG5ldyBBdFByb3RvQ2xpZW50KEFQSV9VUkwsIFNMSUNFX1VSSSk7" 65 77 }, 66 78 { 67 79 "path": "src/routes/middleware.ts", 68 - "content": "aW1wb3J0IHsgc2Vzc2lvblN0b3JlLCBvYXV0aFNlc3Npb25zLCBvYXV0aENsaWVudCB9IGZyb20gIi4uL2NvbmZpZy50cyI7CgpleHBvcnQgaW50ZXJmYWNlIEF1dGhDb250ZXh0IHsKICBjdXJyZW50VXNlcjogewogICAgc3ViOiBzdHJpbmc7CiAgICBuYW1lPzogc3RyaW5nOwogICAgZW1haWw/OiBzdHJpbmc7CiAgfSB8IG51bGw7CiAgc2Vzc2lvbklkOiBzdHJpbmcgfCBudWxsOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gd2l0aEF1dGgocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxBdXRoQ29udGV4dD4gewogIGNvbnN0IHNlc3Npb24gPSBhd2FpdCBzZXNzaW9uU3RvcmUuZ2V0U2Vzc2lvbkZyb21SZXF1ZXN0KHJlcSk7CgogIGlmICghc2Vzc2lvbikgewogICAgcmV0dXJuIHsgY3VycmVudFVzZXI6IG51bGwsIHNlc3Npb25JZDogbnVsbCB9OwogIH0KCiAgdHJ5IHsKICAgIC8vIEdldCB1c2VyIGluZm8gZnJvbSBPQXV0aCBjbGllbnQKICAgIGNvbnN0IHVzZXJJbmZvID0gYXdhaXQgb2F1dGhDbGllbnQuZ2V0VXNlckluZm8oKTsKICAgIHJldHVybiB7CiAgICAgIGN1cnJlbnRVc2VyOiB1c2VySW5mbyB8fCBudWxsLAogICAgICBzZXNzaW9uSWQ6IHNlc3Npb24uc2Vzc2lvbklkLAogICAgfTsKICB9IGNhdGNoIHsKICAgIHJldHVybiB7IGN1cnJlbnRVc2VyOiBudWxsLCBzZXNzaW9uSWQ6IHNlc3Npb24uc2Vzc2lvbklkIH07CiAgfQp9CgpleHBvcnQgZnVuY3Rpb24gcmVxdWlyZUF1dGgoaGFuZGxlcjogKHJlcTogUmVxdWVzdCwgY29udGV4dDogQXV0aENvbnRleHQpID0+IFByb21pc2U8UmVzcG9uc2U+KSB7CiAgcmV0dXJuIGFzeW5jIChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiA9PiB7CiAgICBjb25zdCBjb250ZXh0ID0gYXdhaXQgd2l0aEF1dGgocmVxKTsKCiAgICBpZiAoIWNvbnRleHQuY3VycmVudFVzZXIpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9sb2dpbiIsIHJlcS51cmwpLCAzMDIpOwogICAgfQoKICAgIHJldHVybiBoYW5kbGVyKHJlcSwgY29udGV4dCk7CiAgfTsKfQ==" 80 + "content": "aW1wb3J0IHsgc2Vzc2lvblN0b3JlLCBjcmVhdGVPQXV0aENsaWVudCB9IGZyb20gIi4uL2NvbmZpZy50cyI7CgpleHBvcnQgaW50ZXJmYWNlIEF1dGhDb250ZXh0IHsKICBjdXJyZW50VXNlcjogewogICAgc3ViOiBzdHJpbmc7CiAgICBuYW1lPzogc3RyaW5nOwogICAgZW1haWw/OiBzdHJpbmc7CiAgfSB8IG51bGw7CiAgc2Vzc2lvbklkOiBzdHJpbmcgfCBudWxsOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gd2l0aEF1dGgocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxBdXRoQ29udGV4dD4gewogIGNvbnN0IHNlc3Npb24gPSBhd2FpdCBzZXNzaW9uU3RvcmUuZ2V0U2Vzc2lvbkZyb21SZXF1ZXN0KHJlcSk7CgogIGlmICghc2Vzc2lvbikgewogICAgcmV0dXJuIHsgY3VycmVudFVzZXI6IG51bGwsIHNlc3Npb25JZDogbnVsbCB9OwogIH0KCiAgdHJ5IHsKICAgIGNvbnN0IHNlc3Npb25PQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHNlc3Npb24uc2Vzc2lvbklkKTsKICAgIGNvbnN0IHVzZXJJbmZvID0gYXdhaXQgc2Vzc2lvbk9BdXRoQ2xpZW50LmdldFVzZXJJbmZvKCk7CiAgICByZXR1cm4gewogICAgICBjdXJyZW50VXNlcjogdXNlckluZm8gfHwgbnVsbCwKICAgICAgc2Vzc2lvbklkOiBzZXNzaW9uLnNlc3Npb25JZCwKICAgIH07CiAgfSBjYXRjaCB7CiAgICByZXR1cm4geyBjdXJyZW50VXNlcjogbnVsbCwgc2Vzc2lvbklkOiBzZXNzaW9uLnNlc3Npb25JZCB9OwogIH0KfQoKZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVBdXRoKAogIGhhbmRsZXI6IChyZXE6IFJlcXVlc3QsIGNvbnRleHQ6IEF1dGhDb250ZXh0KSA9PiBQcm9taXNlPFJlc3BvbnNlPgopIHsKICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+ID0+IHsKICAgIGNvbnN0IGNvbnRleHQgPSBhd2FpdCB3aXRoQXV0aChyZXEpOwoKICAgIGlmICghY29udGV4dC5jdXJyZW50VXNlcikgewogICAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMik7CiAgICB9CgogICAgcmV0dXJuIGhhbmRsZXIocmVxLCBjb250ZXh0KTsKICB9Owp9Cg==" 69 81 }, 70 82 { 71 83 "path": "src/routes/mod.ts", 72 - "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IGF1dGhSb3V0ZXMgfSBmcm9tICIuLi9mZWF0dXJlcy9hdXRoL2hhbmRsZXJzLnRzeCI7CgpleHBvcnQgY29uc3QgYWxsUm91dGVzOiBSb3V0ZVtdID0gWwogIC8vIFJvb3QgcmVkaXJlY3QgdG8gbG9naW4gZm9yIG5vdwogIHsKICAgIG1ldGhvZDogIkdFVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiLyIgfSksCiAgICBoYW5kbGVyOiAocmVxKSA9PiBSZXNwb25zZS5yZWRpcmVjdChuZXcgVVJMKCIvbG9naW4iLCByZXEudXJsKSwgMzAyKSwKICB9LAoKICAvLyBBdXRoIHJvdXRlcwogIC4uLmF1dGhSb3V0ZXMsCl07" 84 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IGF1dGhSb3V0ZXMgfSBmcm9tICIuLi9mZWF0dXJlcy9hdXRoL2hhbmRsZXJzLnRzeCI7CmltcG9ydCB7IGRhc2hib2FyZFJvdXRlcyB9IGZyb20gIi4uL2ZlYXR1cmVzL2Rhc2hib2FyZC9oYW5kbGVycy50c3giOwoKZXhwb3J0IGNvbnN0IGFsbFJvdXRlczogUm91dGVbXSA9IFsKICAvLyBSb290IHJlZGlyZWN0IHRvIGxvZ2luIGZvciBub3cKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi8iIH0pLAogICAgaGFuZGxlcjogKHJlcSkgPT4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMiksCiAgfSwKCiAgLy8gQXV0aCByb3V0ZXMKICAuLi5hdXRoUm91dGVzLAoKICAvLyBEYXNoYm9hcmQgcm91dGVzCiAgLi4uZGFzaGJvYXJkUm91dGVzLApdOw==" 73 85 } 74 86 ]; 75 87
+14
packages/cli/src/utils/constants.ts
··· 1 + /** 2 + * System-wide constants for the Slices CLI 3 + */ 4 + 5 + // The main Slices platform slice URI and DID 6 + export const SYSTEM_SLICE_URI = "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z"; 7 + export const SYSTEM_SLICE_DID = "did:plc:bcgltzqazw5tb6k2g3ttenbj"; 8 + 9 + // Reference slice with base lexicons for new projects 10 + export const REFERENCE_SLICE_URI = "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lzbzumcmvo2z"; 11 + 12 + // API endpoints 13 + export const DEFAULT_API_URL = "https://api.slices.network"; 14 + export const DEFAULT_AIP_BASE_URL = "https://auth.slices.network";
+61
packages/cli/src/utils/name_generator.ts
··· 1 + /** 2 + * Simple Docker-style name generator for slices 3 + * Generates names in the format: adjective-noun 4 + */ 5 + 6 + const adjectives = [ 7 + "awesome", "blazing", "brilliant", "clever", "cool", 8 + "dazzling", "dynamic", "elegant", "epic", "fast", 9 + "fearless", "friendly", "gentle", "happy", "jolly", 10 + "kind", "lively", "mighty", "nimble", "peaceful", 11 + "quick", "radiant", "serene", "sharp", "smooth", 12 + "stellar", "swift", "tender", "vibrant", "wise", 13 + "zen", "cosmic", "digital", "quantum", "cyber", 14 + "neon", "pixel", "sonic", "turbo", "ultra" 15 + ]; 16 + 17 + const nouns = [ 18 + "slice", "wave", "spark", "pulse", "stream", 19 + "beacon", "bridge", "cloud", "comet", "crystal", 20 + "delta", "echo", "flame", "galaxy", "harbor", 21 + "horizon", "iris", "jet", "lens", "meteor", 22 + "nexus", "orbit", "phoenix", "prism", "quasar", 23 + "ray", "sphere", "star", "storm", "tide", 24 + "vertex", "vortex", "zen", "zone", "arc", 25 + "beam", "core", "drift", "edge", "flux" 26 + ]; 27 + 28 + /** 29 + * Generates a random slice name in the format: adjective-noun-xxxx 30 + * @param separator The separator to use between words (default: "-") 31 + * @returns A randomly generated name with 4-digit suffix 32 + */ 33 + export function generateSliceName(separator: string = "-"): string { 34 + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; 35 + const noun = nouns[Math.floor(Math.random() * nouns.length)]; 36 + const randomSuffix = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); 37 + return `${adjective}${separator}${noun}${separator}${randomSuffix}`; 38 + } 39 + 40 + /** 41 + * Generates a unique slice name with a random number suffix 42 + * @param separator The separator to use between words (default: "-") 43 + * @returns A randomly generated name with number suffix 44 + */ 45 + export function generateUniqueSliceName(separator: string = "-"): string { 46 + const baseName = generateSliceName(separator); 47 + const randomNum = Math.floor(Math.random() * 9999); 48 + return `${baseName}${separator}${randomNum}`; 49 + } 50 + 51 + /** 52 + * Generates a unique random domain name 53 + * @returns A unique domain in the format: network.slices.adjective-noun-xxxx 54 + */ 55 + export function generateDomain(): string { 56 + // Generate a completely random domain, independent of slice name 57 + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; 58 + const noun = nouns[Math.floor(Math.random() * nouns.length)]; 59 + const randomSuffix = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); 60 + return `network.slices.${adjective}-${noun}-${randomSuffix}`; 61 + }
+18
packages/cli/src/utils/strings.ts
··· 1 + /** 2 + * Convert a string to kebab-case (dash-separated lowercase) 3 + * 4 + * Examples: 5 + * "MyProject" -> "my-project" 6 + * "my_project" -> "my-project" 7 + * "My Cool Project" -> "my-cool-project" 8 + * "myProject123" -> "my-project123" 9 + */ 10 + export function dasherize(str: string): string { 11 + return str 12 + .replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase to kebab-case 13 + .replace(/[\s_]+/g, '-') // spaces and underscores to dashes 14 + .replace(/[^a-z0-9-]/gi, '') // remove invalid characters 15 + .replace(/-+/g, '-') // collapse multiple dashes 16 + .replace(/^-|-$/g, '') // trim dashes from start/end 17 + .toLowerCase(); 18 + }
+6 -20
packages/cli/src/utils/waitlist.ts
··· 1 1 import { AtProtoClient } from "../generated_client.ts"; 2 2 import { logger } from "./logger.ts"; 3 - 4 - const DEFAULT_SLICE_URI = 5 - "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z"; 6 - const DEFAULT_ADMIN_DID = "did:plc:bcgltzqazw5tb6k2g3ttenbj"; 7 - 8 - function getSliceUri(): string { 9 - return Deno.env.get("SLICE_URI") || DEFAULT_SLICE_URI; 10 - } 11 - 12 - function getAdminDid(): string { 13 - return Deno.env.get("ADMIN_DID") || DEFAULT_ADMIN_DID; 14 - } 3 + import { SYSTEM_SLICE_URI, SYSTEM_SLICE_DID, DEFAULT_API_URL } from "./constants.ts"; 15 4 16 5 export interface WaitlistCheckResult { 17 6 hasAccess: boolean; ··· 20 9 21 10 export async function checkUserWaitlistAccess( 22 11 userDid: string, 23 - apiUrl = "https://api.slices.network" 12 + apiUrl = DEFAULT_API_URL 24 13 ): Promise<WaitlistCheckResult> { 25 14 try { 26 - const sliceUri = getSliceUri(); 27 - const adminDid = getAdminDid(); 28 - 29 15 // Create a public client for checking waitlist status (no auth needed) 30 - const client = new AtProtoClient(apiUrl, sliceUri); 16 + const client = new AtProtoClient(apiUrl, SYSTEM_SLICE_URI); 31 17 32 18 // Query for invites for this DID - using json field to query the record content 33 19 const invitesResult = 34 20 await client.network.slices.waitlist.invite.getRecords({ 35 21 where: { 36 - did: { eq: adminDid }, 37 - slice: { eq: sliceUri }, 22 + did: { eq: SYSTEM_SLICE_DID }, 23 + slice: { eq: SYSTEM_SLICE_URI }, 38 24 json: { contains: userDid }, 39 25 }, 40 26 limit: 1, ··· 60 46 const requestsResult = 61 47 await client.network.slices.waitlist.request.getRecords({ 62 48 where: { 63 - slice: { eq: sliceUri }, 49 + slice: { eq: SYSTEM_SLICE_URI }, 64 50 json: { eq: userDid }, 65 51 }, 66 52 limit: 1,