using Astralis; using Inversion; using InvercargillSqlInversion; using Spry; using Spry.Authentication; using Statum; using ClankerSaddle; using ClankerSaddle.Api; using ClankerSaddle.Data; using ClankerSaddle.Services; int main(string[] args) { int port = args.length > 1 ? int.parse(args[1]) : 8080; print("ClankerSaddle - LLM task orchestration platform\n"); print(@"Website starting on port $(port)\n"); try { var application = new WebApplication(port); application.use_compression(); application.add_module(); application.add_module(); var db_path = Environment.get_variable("SPRY_DB_PATH") ?? ""; if (db_path.strip().length == 0) { db_path = "./clanker-saddle.sqlite"; } var db = application.container.configure_with(); db.register_connection(@"sqlite://$(db_path)"); db.add_migration(); db.add_migration(); db.add_migration(); db.add_migration(); db.add_migration(); db.add_entity(Project.entity_mapping); db.add_entity(ProjectLink.entity_mapping); db.add_entity(TaskEntity.entity_mapping); db.add_entity(TaskDependency.entity_mapping); db.add_entity(TaskComment.entity_mapping); db.add_entity(TaskBlock.entity_mapping); db.add_entity(TaskProgress.entity_mapping); db.add_entity(Saddle.entity_mapping); db.add_entity(Session.entity_mapping); db.add_entity(SessionCommand.entity_mapping); db.add_entity(ApiToken.entity_mapping); db.add_entity(Rule.entity_mapping); db.add_entity(RuleProject.entity_mapping); db.add_entity(TaskRule.entity_mapping); db.add_entity(ProjectNote.entity_mapping); db.add_entity(SessionRawLog.entity_mapping); db.migrate_on_startup(); application.add_scoped(); application.add_scoped(); application.add_scoped(); application.add_scoped(); application.add_singleton(); application.add_singleton(); application.add_singleton(); var statum = application.configure_with(); statum.global(StatePulse.SLOT_TYPE, new ClankerSaddle.Services.PulsePublic()); // spry:pages-begin statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); statum.add_page(); // spry:pages-end // spry:actions-begin statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); statum.action(); // spry:actions-end // spry:resources-begin statum.add_resource(); // spry:resources-end // spry:auth-begin // spry:auth-end application.add_endpoint( new EndpointRoute("/api/saddle/{id}/update", Method.POST)); application.add_endpoint( new EndpointRoute("/api/saddle/{id}/poll", Method.GET)); application.add_endpoint( new EndpointRoute("/api/saddle/note", Method.POST)); application.add_endpoint( new EndpointRoute("/terminal-stream/{session_id}", Method.GET)); GLib.Idle.add(() => { seed_admin.begin(application.container); mint_callback_token.begin(application.container); start_dispatcher.begin(application.container); return false; }); application.run(); return 0; } catch (Error e) { printerr("Error: %s\n", e.message); return 1; } } /** * Seeds the admin account when BOTH SPRY_SEED_ADMIN_USERNAME and * SPRY_SEED_ADMIN_PASSWORD are set; skipped silently otherwise. The * password is never logged. */ private async void seed_admin(Container container) { var username = Environment.get_variable("SPRY_SEED_ADMIN_USERNAME") ?? ""; var password = Environment.get_variable("SPRY_SEED_ADMIN_PASSWORD") ?? ""; if (username.length == 0 || password.length == 0) { return; } try { var scope = container.create_scope(); yield SpryAuth.ensure_admin(scope.resolve(), username, @"$(username)@example.com", password); print("[ClankerSaddle] Seeded admin account: %s\n", username); } catch (Error e) { printerr("[ClankerSaddle] Admin seeding failed: %s\n", e.message); } } private async void start_dispatcher(Container container) { try { var scope = container.create_scope(); scope.resolve().start(); } catch (Error e) { printerr("[ClankerSaddle] Dispatcher startup failed: %s\n", e.message); } } /** * Mints the dedicated 'saddle-callback' token at startup when absent — * the credential daemons use to POST `saddle note` remarks back (see * {@link ClankerSaddle.Api.SaddleNoteEndpoint}). */ private async void mint_callback_token(Container container) { try { var scope = container.create_scope(); string token = yield scope.resolve().ensure_callback_token(); print("[ClankerSaddle] Saddle callback token ready (%s…)\n", token.substring(0, 16)); } catch (Error e) { printerr("[ClankerSaddle] Callback token minting failed: %s\n", e.message); } }