@appsforgood/next-supabase-kit 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/BEST_PRACTICE_EVIDENCE.md +1 -1
  2. package/CHANGELOG.md +9 -0
  3. package/DOGFOOD.md +2 -1
  4. package/README.md +33 -4
  5. package/REPOSITORY_SETTINGS.md +12 -6
  6. package/SECURITY.md +14 -0
  7. package/SUPPLY_CHAIN.md +15 -14
  8. package/UPGRADE.md +14 -4
  9. package/assistant-adapters/README.md +3 -0
  10. package/assistant-adapters/antigravity.md +2 -0
  11. package/assistant-adapters/claude-code-subagents.md +1 -0
  12. package/assistant-adapters/codex-agents.md +2 -0
  13. package/assistant-adapters/cursor-agent-kit.mdc +2 -0
  14. package/assistant-adapters/cursor-planner.mdc +1 -1
  15. package/assistant-adapters/orchestrator-runtime.md +39 -0
  16. package/checklists/frontend-quality.md +1 -0
  17. package/checklists/ui-detectors.md +6 -0
  18. package/dist/index.js +1723 -688
  19. package/dist/index.js.map +1 -1
  20. package/dist/studio/office/assets/office.css +138 -7
  21. package/dist/studio/office/assets/office.js +200 -2
  22. package/dist/studio/wizard/assets/wizard.js +7 -1
  23. package/examples/next-supabase-installed/.agent-kit/manifest.json +187 -14
  24. package/examples/next-supabase-installed/audit-output.json +405 -380
  25. package/package.json +25 -4
  26. package/prompts/frontend-design-review.md +6 -0
  27. package/prompts/screenshot-review.md +2 -1
  28. package/schemas/audit-report-v2.schema.json +71 -0
  29. package/schemas/audit-report.schema.json +16 -16
  30. package/schemas/orchestrator.schema.json +167 -0
  31. package/schemas/runtime-event.schema.json +66 -0
  32. package/schemas/runtime-run.schema.json +58 -0
  33. package/schemas/session-event.schema.json +2 -0
  34. package/templates/next-supabase/.agent-kit/orchestrator.json +48 -0
  35. package/templates/next-supabase/.agent-kit/runtime/gitignore.template +2 -0
  36. package/templates/next-supabase/AGENTS.md +2 -0
  37. package/templates/next-supabase/ASSISTANT_ADAPTERS.md +20 -0
  38. package/templates/next-supabase/DECISIONS.md +14 -0
  39. package/templates/next-supabase/DEPLOYMENT.md +4 -0
  40. package/templates/next-supabase/DESIGN.md +6 -0
  41. package/templates/next-supabase/DOCS.md +8 -0
  42. package/templates/next-supabase/MODEL_ROUTING.md +9 -0
  43. package/templates/next-supabase/QUALITY_GATES.md +5 -1
  44. package/templates/next-supabase/SECURITY.md +10 -0
  45. package/templates/next-supabase/SPEC.md +3 -1
  46. package/templates/next-supabase/STYLE_GUIDE.md +15 -0
  47. package/templates/next-supabase/TESTING.md +6 -0
  48. package/templates/next-supabase/UPGRADE.md +1 -0
@@ -566,6 +566,10 @@ body {
566
566
  display: none !important;
567
567
  }
568
568
 
569
+ [hidden] {
570
+ display: none !important;
571
+ }
572
+
569
573
  code {
570
574
  font-family: ui-monospace, Menlo, Consolas, monospace;
571
575
  font-size: 12px;
@@ -600,6 +604,39 @@ code {
600
604
  max-height: calc(100vh - 64px);
601
605
  }
602
606
 
607
+ .studio-tabs {
608
+ display: grid;
609
+ grid-template-columns: 1fr 1fr;
610
+ margin-bottom: 12px;
611
+ border: 1px solid rgba(255, 255, 255, 0.12);
612
+ border-radius: 6px;
613
+ overflow: hidden;
614
+ }
615
+
616
+ .studio-tab {
617
+ min-height: 34px;
618
+ border: 0;
619
+ border-right: 1px solid rgba(255, 255, 255, 0.12);
620
+ background: #0f172a;
621
+ color: #94a3b8;
622
+ font: inherit;
623
+ cursor: pointer;
624
+ }
625
+
626
+ .studio-tab:last-child {
627
+ border-right: 0;
628
+ }
629
+
630
+ .studio-tab.active {
631
+ background: #334155;
632
+ color: #f8fafc;
633
+ }
634
+
635
+ .studio-tab:focus-visible {
636
+ outline: 2px solid #fbbf24;
637
+ outline-offset: -2px;
638
+ }
639
+
603
640
  .studio-controls {
604
641
  display: grid;
605
642
  gap: 10px;
@@ -620,7 +657,9 @@ code {
620
657
 
621
658
  .studio-note-form input,
622
659
  .studio-note-form select,
623
- #session-picker {
660
+ #session-picker,
661
+ #runtime-picker,
662
+ #runtime-goal {
624
663
  width: 100%;
625
664
  padding: 8px 10px;
626
665
  border-radius: 8px;
@@ -630,13 +669,95 @@ code {
630
669
  font-size: 13px;
631
670
  }
632
671
 
672
+ #runtime-goal {
673
+ min-height: 82px;
674
+ resize: vertical;
675
+ line-height: 1.4;
676
+ }
677
+
678
+ .runtime-start-form,
679
+ .runtime-toolbar {
680
+ display: grid;
681
+ gap: 8px;
682
+ padding-bottom: 12px;
683
+ margin-bottom: 12px;
684
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
685
+ }
686
+
687
+ .runtime-check {
688
+ display: flex;
689
+ align-items: center;
690
+ gap: 8px;
691
+ font-size: 12px;
692
+ color: #cbd5e1;
693
+ }
694
+
695
+ .runtime-check input {
696
+ width: 16px;
697
+ height: 16px;
698
+ }
699
+
700
+ .runtime-summary {
701
+ margin: 0 0 12px;
702
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
703
+ }
704
+
705
+ .runtime-summary div {
706
+ display: grid;
707
+ grid-template-columns: 72px minmax(0, 1fr);
708
+ gap: 8px;
709
+ padding: 7px 0;
710
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
711
+ font-size: 12px;
712
+ }
713
+
714
+ .runtime-summary dt {
715
+ color: #94a3b8;
716
+ }
717
+
718
+ .runtime-summary dd {
719
+ margin: 0;
720
+ color: #f8fafc;
721
+ overflow-wrap: anywhere;
722
+ }
723
+
724
+ .runtime-approval {
725
+ padding: 10px;
726
+ margin-bottom: 10px;
727
+ border: 1px solid rgba(251, 191, 36, 0.32);
728
+ border-radius: 6px;
729
+ background: rgba(251, 191, 36, 0.08);
730
+ color: #f8fafc;
731
+ }
732
+
733
+ .runtime-approval p {
734
+ margin: 6px 0 10px;
735
+ color: #cbd5e1;
736
+ font-size: 12px;
737
+ line-height: 1.4;
738
+ white-space: pre-wrap;
739
+ overflow-wrap: anywhere;
740
+ }
741
+
742
+ .runtime-actions {
743
+ display: grid;
744
+ grid-template-columns: 1fr 1fr;
745
+ gap: 8px;
746
+ }
747
+
748
+ .runtime-cancel {
749
+ width: 100%;
750
+ margin-bottom: 12px;
751
+ }
752
+
633
753
  .transcript-panel h2 {
634
754
  margin: 0 0 8px;
635
755
  font-size: 14px;
636
756
  color: #f8fafc;
637
757
  }
638
758
 
639
- #transcript-list {
759
+ #transcript-list,
760
+ #runtime-event-list {
640
761
  list-style: none;
641
762
  margin: 0;
642
763
  padding: 0;
@@ -645,7 +766,8 @@ code {
645
766
  font-size: 12px;
646
767
  }
647
768
 
648
- #transcript-list li {
769
+ #transcript-list li,
770
+ #runtime-event-list li {
649
771
  padding: 8px;
650
772
  border-radius: 8px;
651
773
  background: rgba(255, 255, 255, 0.04);
@@ -692,11 +814,20 @@ code {
692
814
  @media (max-width: 900px) {
693
815
  .office-main.studio-layout {
694
816
  grid-template-columns: 1fr;
817
+ grid-template-areas:
818
+ "activity"
819
+ "office";
820
+ }
821
+ .office-main.studio-layout > .canvas-wrap {
822
+ grid-area: office;
695
823
  }
696
824
  .transcript-panel {
697
- max-height: 200px;
825
+ grid-area: activity;
826
+ height: min(640px, 72vh);
827
+ max-height: none;
698
828
  border-left: 0;
699
- border-top: 1px solid rgba(255, 255, 255, 0.08);
829
+ border-top: 0;
830
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
700
831
  }
701
832
  }
702
833
 
@@ -732,8 +863,8 @@ code {
732
863
  padding: 6px 10px;
733
864
  background: #fff;
734
865
  color: var(--ink);
735
- border: 2px solid var(--accent);
736
- border-radius: 10px;
866
+ border: 1px solid #cbd5e1;
867
+ border-radius: 8px;
737
868
  font-size: 11px;
738
869
  line-height: 1.3;
739
870
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
@@ -36,6 +36,9 @@
36
36
  studioSessionId: boot.activeSessionId || "",
37
37
  studioEvents: [],
38
38
  studioEventSource: null,
39
+ runtimeRuns: [],
40
+ runtimeRunId: "",
41
+ runtimeEvents: [],
39
42
  speechBubbles: [],
40
43
  agenticLevel: null
41
44
  };
@@ -77,7 +80,25 @@
77
80
  studioNoteForm: document.getElementById("studio-note-form"),
78
81
  studioNoteAgent: document.getElementById("studio-note-agent"),
79
82
  studioNoteText: document.getElementById("studio-note-text"),
80
- studioRenderBtn: document.getElementById("studio-render-btn")
83
+ studioRenderBtn: document.getElementById("studio-render-btn"),
84
+ studioCouncilTab: document.getElementById("studio-council-tab"),
85
+ studioRuntimeTab: document.getElementById("studio-runtime-tab"),
86
+ studioCouncilView: document.getElementById("studio-council-view"),
87
+ studioRuntimeView: document.getElementById("studio-runtime-view"),
88
+ runtimeStartForm: document.getElementById("runtime-start-form"),
89
+ runtimeStartBtn: document.getElementById("runtime-start-btn"),
90
+ runtimeGoal: document.getElementById("runtime-goal"),
91
+ runtimeDirtyBase: document.getElementById("runtime-dirty-base"),
92
+ runtimePicker: document.getElementById("runtime-picker"),
93
+ runtimeRefreshBtn: document.getElementById("runtime-refresh-btn"),
94
+ runtimeSummary: document.getElementById("runtime-summary"),
95
+ runtimeApproval: document.getElementById("runtime-approval"),
96
+ runtimeApprovalTitle: document.getElementById("runtime-approval-title"),
97
+ runtimeApprovalDetail: document.getElementById("runtime-approval-detail"),
98
+ runtimeApproveBtn: document.getElementById("runtime-approve-btn"),
99
+ runtimeRejectBtn: document.getElementById("runtime-reject-btn"),
100
+ runtimeCancelBtn: document.getElementById("runtime-cancel-btn"),
101
+ runtimeEventList: document.getElementById("runtime-event-list")
81
102
  };
82
103
 
83
104
  const ctx = els.canvas?.getContext("2d");
@@ -175,7 +196,13 @@
175
196
  }
176
197
 
177
198
  async function api(path, options) {
178
- const response = await fetch(path, options);
199
+ const requestOptions = options ? { ...options } : {};
200
+ const method = String(requestOptions.method || "GET").toUpperCase();
201
+ if (!["GET", "HEAD", "OPTIONS"].includes(method)) {
202
+ const token = document.querySelector('meta[name="agent-kit-csrf-token"]')?.getAttribute("content") || "";
203
+ requestOptions.headers = { ...(requestOptions.headers || {}), "Content-Type": "application/json", "X-Agent-Kit-CSRF": token };
204
+ }
205
+ const response = await fetch(path, requestOptions);
179
206
  const data = await response.json();
180
207
  if (!response.ok) throw new Error(data.error || "Request failed");
181
208
  return data;
@@ -230,6 +257,7 @@
230
257
  window.setTimeout(() => els.officeHint?.classList.add("hidden"), 6000);
231
258
  }
232
259
  connectStudioStream();
260
+ await loadRuntimeRuns().catch((error) => renderRuntimeUnavailable(error.message));
233
261
  }
234
262
 
235
263
  function populateSessionPicker(sessions, selectedId) {
@@ -257,7 +285,123 @@
257
285
  : '<option value="planner">Planner</option>';
258
286
  }
259
287
 
288
+ function switchStudioView(view) {
289
+ const runtime = view === "runtime";
290
+ if (els.studioCouncilView) els.studioCouncilView.hidden = runtime;
291
+ if (els.studioRuntimeView) els.studioRuntimeView.hidden = !runtime;
292
+ els.studioCouncilTab?.classList.toggle("active", !runtime);
293
+ els.studioRuntimeTab?.classList.toggle("active", runtime);
294
+ els.studioCouncilTab?.setAttribute("aria-selected", String(!runtime));
295
+ els.studioRuntimeTab?.setAttribute("aria-selected", String(runtime));
296
+ if (runtime) void loadRuntimeRuns().catch((error) => renderRuntimeUnavailable(error.message));
297
+ }
298
+
299
+ function populateRuntimePicker() {
300
+ if (!els.runtimePicker) return;
301
+ els.runtimePicker.innerHTML = state.runtimeRuns.length
302
+ ? state.runtimeRuns
303
+ .map(
304
+ (run) =>
305
+ '<option value="' +
306
+ escapeHtml(run.runId) +
307
+ '"' +
308
+ (run.runId === state.runtimeRunId ? " selected" : "") +
309
+ ">" +
310
+ escapeHtml(run.runId.slice(0, 30) + " · " + run.status) +
311
+ "</option>"
312
+ )
313
+ .join("")
314
+ : '<option value="">No runs</option>';
315
+ }
316
+
317
+ async function loadRuntimeRuns() {
318
+ const response = await api("/api/runtime/runs");
319
+ const runtimeEnabled = response.enabled === true;
320
+ if (els.runtimeStartBtn) els.runtimeStartBtn.disabled = !runtimeEnabled;
321
+ state.runtimeRuns = Array.isArray(response.runs) ? response.runs : [];
322
+ if (!state.runtimeRunId || !state.runtimeRuns.some((run) => run.runId === state.runtimeRunId)) {
323
+ state.runtimeRunId = state.runtimeRuns[0]?.runId || "";
324
+ }
325
+ populateRuntimePicker();
326
+ if (state.runtimeRunId) await loadRuntimeRun(state.runtimeRunId);
327
+ else if (runtimeEnabled) renderRuntimeRun(null, []);
328
+ else renderRuntimeUnavailable("Runtime is disabled in .agent-kit/orchestrator.json.");
329
+ }
330
+
331
+ async function loadRuntimeRun(runId) {
332
+ if (!runId) {
333
+ renderRuntimeRun(null, []);
334
+ return;
335
+ }
336
+ const response = await api("/api/runtime/runs/" + encodeURIComponent(runId));
337
+ state.runtimeRunId = runId;
338
+ state.runtimeEvents = response.events || [];
339
+ const index = state.runtimeRuns.findIndex((run) => run.runId === runId);
340
+ if (index >= 0) state.runtimeRuns[index] = response.run;
341
+ else state.runtimeRuns.unshift(response.run);
342
+ populateRuntimePicker();
343
+ renderRuntimeRun(response.run, state.runtimeEvents);
344
+ }
345
+
346
+ function renderRuntimeUnavailable(message) {
347
+ if (els.runtimeStartBtn) els.runtimeStartBtn.disabled = true;
348
+ if (els.runtimeSummary) els.runtimeSummary.innerHTML = "<div><dt>Status</dt><dd>" + escapeHtml(message) + "</dd></div>";
349
+ if (els.runtimeEventList) els.runtimeEventList.innerHTML = "";
350
+ if (els.runtimeApproval) els.runtimeApproval.hidden = true;
351
+ if (els.runtimeCancelBtn) els.runtimeCancelBtn.hidden = true;
352
+ }
353
+
354
+ function renderRuntimeRun(run, events) {
355
+ if (els.runtimeSummary) {
356
+ const items = run
357
+ ? [
358
+ ["Status", run.status],
359
+ ["Workflow", run.workflowId],
360
+ ["Agent", run.activeAgentId || "—"],
361
+ ["Commit", run.commit ? run.commit.slice(0, 12) : "—"]
362
+ ]
363
+ : [["Status", "No runs"]];
364
+ els.runtimeSummary.innerHTML = items.map(([label, value]) => "<div><dt>" + escapeHtml(label) + "</dt><dd>" + escapeHtml(value) + "</dd></div>").join("");
365
+ }
366
+ const pending = run?.pendingApproval;
367
+ if (els.runtimeApproval) els.runtimeApproval.hidden = !pending;
368
+ if (els.runtimeApprovalTitle) els.runtimeApprovalTitle.textContent = pending?.title || "";
369
+ if (els.runtimeApprovalDetail) els.runtimeApprovalDetail.textContent = pending?.detail || "";
370
+ const terminal = !run || ["complete", "failed", "cancelled"].includes(run.status);
371
+ if (els.runtimeCancelBtn) els.runtimeCancelBtn.hidden = terminal;
372
+ if (els.runtimeEventList) {
373
+ els.runtimeEventList.innerHTML = (events || [])
374
+ .slice(-100)
375
+ .map(
376
+ (event) =>
377
+ '<li><span class="tx-time">' +
378
+ escapeHtml((event.createdAt || "").slice(11, 19)) +
379
+ "</span> <strong>" +
380
+ escapeHtml(event.agentId || "run") +
381
+ "</strong> " +
382
+ escapeHtml(eventLabel(event)) +
383
+ "</li>"
384
+ )
385
+ .join("");
386
+ els.runtimeEventList.scrollTop = els.runtimeEventList.scrollHeight;
387
+ }
388
+ if (run && els.sessionPill && !els.studioRuntimeView?.hidden) els.sessionPill.textContent = run.runId.slice(0, 24);
389
+ }
390
+
391
+ async function decideRuntime(decision) {
392
+ if (!state.runtimeRunId) return;
393
+ const response = await api("/api/runtime/runs/" + encodeURIComponent(state.runtimeRunId) + "/decision", {
394
+ method: "POST",
395
+ body: JSON.stringify({ decision, actor: "studio-operator" })
396
+ });
397
+ state.runtimeEvents = response.events || [];
398
+ renderRuntimeRun(response.run, state.runtimeEvents);
399
+ await loadRuntimeRuns();
400
+ }
401
+
260
402
  function bindStudioControls() {
403
+ els.studioCouncilTab?.addEventListener("click", () => switchStudioView("council"));
404
+ els.studioRuntimeTab?.addEventListener("click", () => switchStudioView("runtime"));
261
405
  if (els.sessionPicker) {
262
406
  els.sessionPicker.addEventListener("change", () => {
263
407
  state.studioSessionId = els.sessionPicker.value;
@@ -309,6 +453,60 @@
309
453
  }
310
454
  });
311
455
  }
456
+ if (els.runtimePicker) {
457
+ els.runtimePicker.addEventListener("change", () => {
458
+ state.runtimeRunId = els.runtimePicker.value;
459
+ void loadRuntimeRun(state.runtimeRunId).catch((error) => setStatus("error", error.message));
460
+ });
461
+ }
462
+ els.runtimeRefreshBtn?.addEventListener("click", () => {
463
+ void loadRuntimeRuns().catch((error) => setStatus("error", error.message));
464
+ });
465
+ if (els.runtimeStartForm) {
466
+ els.runtimeStartForm.addEventListener("submit", async (event) => {
467
+ event.preventDefault();
468
+ const goal = (els.runtimeGoal?.value || "").trim();
469
+ if (!goal) {
470
+ setStatus("error", "Enter a run goal.");
471
+ return;
472
+ }
473
+ try {
474
+ const response = await api("/api/runtime/runs", {
475
+ method: "POST",
476
+ body: JSON.stringify({ goal, acknowledgeDirtyBase: Boolean(els.runtimeDirtyBase?.checked) })
477
+ });
478
+ state.runtimeRunId = response.run.runId;
479
+ state.runtimeEvents = response.events || [];
480
+ if (els.runtimeGoal) els.runtimeGoal.value = "";
481
+ await loadRuntimeRuns();
482
+ setStatus("ok", "Run started.");
483
+ } catch (error) {
484
+ setStatus("error", error.message);
485
+ }
486
+ });
487
+ }
488
+ els.runtimeApproveBtn?.addEventListener("click", () => {
489
+ void decideRuntime("approve").catch((error) => setStatus("error", error.message));
490
+ });
491
+ els.runtimeRejectBtn?.addEventListener("click", () => {
492
+ void decideRuntime("reject").catch((error) => setStatus("error", error.message));
493
+ });
494
+ els.runtimeCancelBtn?.addEventListener("click", async () => {
495
+ if (!state.runtimeRunId) return;
496
+ try {
497
+ const response = await api("/api/runtime/runs/" + encodeURIComponent(state.runtimeRunId) + "/cancel", { method: "POST" });
498
+ state.runtimeEvents = response.events || [];
499
+ renderRuntimeRun(response.run, state.runtimeEvents);
500
+ await loadRuntimeRuns();
501
+ } catch (error) {
502
+ setStatus("error", error.message);
503
+ }
504
+ });
505
+ window.setInterval(() => {
506
+ if (els.studioRuntimeView && !els.studioRuntimeView.hidden) {
507
+ void loadRuntimeRuns().catch(() => undefined);
508
+ }
509
+ }, 2500);
312
510
  }
313
511
 
314
512
  function connectStudioStream() {
@@ -59,7 +59,13 @@
59
59
  }
60
60
 
61
61
  async function api(path, options) {
62
- const response = await fetch(path, options);
62
+ const requestOptions = options ? { ...options } : {};
63
+ const method = String(requestOptions.method || "GET").toUpperCase();
64
+ if (!["GET", "HEAD", "OPTIONS"].includes(method)) {
65
+ const token = document.querySelector('meta[name="agent-kit-csrf-token"]')?.getAttribute("content") || "";
66
+ requestOptions.headers = { ...(requestOptions.headers || {}), "Content-Type": "application/json", "X-Agent-Kit-CSRF": token };
67
+ }
68
+ const response = await fetch(path, requestOptions);
63
69
  const data = await response.json();
64
70
  if (!response.ok) throw new Error(data.error || "Request failed");
65
71
  return data;