| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- 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<StatumModule>();
- application.add_module<SpryModule>();
- 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<DatabaseConfigurator>();
- db.register_connection(@"sqlite://$(db_path)");
- db.add_migration<ClankerSaddle.Data.Migrations.M0001_Initial>();
- db.add_migration<ClankerSaddle.Data.Migrations.M0002_Part2>();
- db.add_migration<ClankerSaddle.Data.Migrations.M0003_Rules>();
- db.add_migration<ClankerSaddle.Data.Migrations.M0004_ProjectNotes>();
- db.add_migration<ClankerSaddle.Data.Migrations.M0005_RawLog>();
- db.add_entity<Project>(Project.entity_mapping);
- db.add_entity<ProjectLink>(ProjectLink.entity_mapping);
- db.add_entity<TaskEntity>(TaskEntity.entity_mapping);
- db.add_entity<TaskDependency>(TaskDependency.entity_mapping);
- db.add_entity<TaskComment>(TaskComment.entity_mapping);
- db.add_entity<TaskBlock>(TaskBlock.entity_mapping);
- db.add_entity<TaskProgress>(TaskProgress.entity_mapping);
- db.add_entity<Saddle>(Saddle.entity_mapping);
- db.add_entity<Session>(Session.entity_mapping);
- db.add_entity<SessionCommand>(SessionCommand.entity_mapping);
- db.add_entity<ApiToken>(ApiToken.entity_mapping);
- db.add_entity<Rule>(Rule.entity_mapping);
- db.add_entity<RuleProject>(RuleProject.entity_mapping);
- db.add_entity<TaskRule>(TaskRule.entity_mapping);
- db.add_entity<ProjectNote>(ProjectNote.entity_mapping);
- db.add_entity<SessionRawLog>(SessionRawLog.entity_mapping);
- db.migrate_on_startup();
- application.add_scoped<ProjectService>();
- application.add_scoped<TaskService>();
- application.add_scoped<SaddleService>();
- application.add_scoped<RuleService>();
- application.add_singleton<SaddlePollState>();
- application.add_singleton<StatePulse>();
- application.add_singleton<Dispatcher>();
- var statum = application.configure_with<StatumConfigurator>();
- statum.global<ClankerSaddle.Services.PulsePublic>(StatePulse.SLOT_TYPE,
- new ClankerSaddle.Services.PulsePublic());
- // spry:pages-begin
- statum.add_page<HomePage, HomeEntrypoint>();
- statum.add_page<LoginPage, LoginEntrypoint>();
- statum.add_page<ProjectsPage, ProjectsEntrypoint>();
- statum.add_page<ProjectDetailPage, ProjectDetailEntrypoint>();
- statum.add_page<TasksPage, TasksEntrypoint>();
- statum.add_page<TaskDetailPage, TaskDetailEntrypoint>();
- statum.add_page<TaskTerminalPage, TaskTerminalEntrypoint>();
- statum.add_page<SaddlesPage, SaddlesEntrypoint>();
- statum.add_page<RulesPage, RulesEntrypoint>();
- // spry:pages-end
- // spry:actions-begin
- statum.action<LoginAction>();
- statum.action<Spry.Actions.LogoutAction>();
- statum.action<CreateProjectAction>();
- statum.action<UpdateProjectAction>();
- statum.action<AddProjectLinkAction>();
- statum.action<RemoveProjectLinkAction>();
- statum.action<CreateTaskAction>();
- statum.action<UpdateTaskAction>();
- statum.action<AddCommentAction>();
- statum.action<UpdatePlanAction>();
- statum.action<AnswerBlockAction>();
- statum.action<CreateSubtaskAction>();
- statum.action<AddDependencyAction>();
- statum.action<CompleteTaskAction>();
- statum.action<CancelTaskAction>();
- statum.action<RefreshTaskDataAction>();
- statum.action<RefreshDashboardAction>();
- statum.action<RegisterSaddleAction>();
- statum.action<UnregisterSaddleAction>();
- statum.action<RefreshSaddleStatusAction>();
- statum.action<CreateRuleAction>();
- statum.action<UpdateRuleAction>();
- statum.action<DeleteRuleAction>();
- statum.action<AttachProjectRuleAction>();
- statum.action<DetachProjectRuleAction>();
- statum.action<AttachTaskRuleAction>();
- statum.action<DetachTaskRuleAction>();
- statum.action<DeleteProjectNoteAction>();
- // spry:actions-end
- // spry:resources-begin
- statum.add_resource<ClankerSaddle.AppCss>();
- // spry:resources-end
- // spry:auth-begin
- // spry:auth-end
- application.add_endpoint<SaddleUpdateEndpoint>(
- new EndpointRoute("/api/saddle/{id}/update", Method.POST));
- application.add_endpoint<SaddlePollEndpoint>(
- new EndpointRoute("/api/saddle/{id}/poll", Method.GET));
- application.add_endpoint<SaddleNoteEndpoint>(
- new EndpointRoute("/api/saddle/note", Method.POST));
- application.add_endpoint<ClankerSaddle.Api.TerminalSseEndpoint>(
- 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<UserService>(),
- 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<Dispatcher>().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<SaddleService>().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);
- }
- }
|