@nanocollective/roster 0.1.0-alpha.1 → 0.1.0-alpha.10

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 (94) hide show
  1. package/README.md +65 -84
  2. package/dist/cli.js +4687 -2495
  3. package/docs/README.md +19 -11
  4. package/docs/agents.md +328 -13
  5. package/docs/architecture.md +13 -5
  6. package/docs/charters/cmo.md +69 -0
  7. package/docs/charters/cto.md +71 -0
  8. package/docs/charters/support.md +60 -0
  9. package/docs/commands.md +90 -11
  10. package/docs/concepts.md +64 -12
  11. package/docs/cost.md +39 -3
  12. package/docs/developing.md +16 -21
  13. package/docs/doctor-codes.md +21 -6
  14. package/docs/export.md +2 -1
  15. package/docs/extending.md +13 -4
  16. package/docs/getting-started.md +118 -80
  17. package/docs/images/brain.jpg +0 -0
  18. package/docs/images/org.jpg +0 -0
  19. package/docs/images/prompt.jpg +0 -0
  20. package/docs/images/setup-org.jpg +0 -0
  21. package/docs/images/setup-plan.jpg +0 -0
  22. package/docs/images/staff.jpg +0 -0
  23. package/docs/manual-steps.md +94 -101
  24. package/docs/memory.md +29 -8
  25. package/docs/org-yaml.md +74 -11
  26. package/docs/portal.md +261 -47
  27. package/docs/prompts.md +71 -11
  28. package/docs/security.md +43 -7
  29. package/docs/session-workflow.md +51 -21
  30. package/docs/staff-yaml.md +16 -7
  31. package/docs/troubleshooting.md +23 -20
  32. package/docs/upgrading.md +6 -0
  33. package/docs/writing-a-charter.md +33 -17
  34. package/package.json +1 -1
  35. package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
  36. package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
  37. package/templates/brain/CHARTER.md +3 -3
  38. package/templates/brain/README.md +1 -0
  39. package/templates/brain/log/decisions.md +3 -0
  40. package/templates/brain/staff.yaml +0 -1
  41. package/templates/brain/strategy/ideas.md +7 -0
  42. package/templates/ops/.github/workflows/session.yaml +117 -40
  43. package/templates/ops/agents.mjs +127 -8
  44. package/templates/ops/compose.mjs +77 -7
  45. package/templates/ops/inflight.mjs +157 -0
  46. package/templates/ops/org/operating.md +21 -7
  47. package/templates/ops/org/voice.md +9 -0
  48. package/templates/ops/prompts/_identity.md +8 -1
  49. package/templates/ops/prompts/_inflight.md +14 -0
  50. package/templates/ops/prompts/_paths.md +2 -1
  51. package/templates/ops/prompts/daily.md +16 -7
  52. package/templates/ops/prompts/mention.md +18 -2
  53. package/templates/ops/run-record.mjs +144 -0
  54. package/templates/portal/css/base.css +238 -64
  55. package/templates/portal/css/brain.css +30 -20
  56. package/templates/portal/css/diff.css +15 -10
  57. package/templates/portal/css/graph.css +12 -7
  58. package/templates/portal/css/health.css +31 -10
  59. package/templates/portal/css/inbox.css +117 -14
  60. package/templates/portal/css/layout.css +92 -41
  61. package/templates/portal/css/markdown.css +38 -15
  62. package/templates/portal/css/runs.css +13 -0
  63. package/templates/portal/css/setup.css +50 -37
  64. package/templates/portal/index.html +24 -3
  65. package/templates/portal/js/api.js +65 -4
  66. package/templates/portal/js/app.js +112 -12
  67. package/templates/portal/js/dialog.js +94 -4
  68. package/templates/portal/js/dom.js +25 -0
  69. package/templates/portal/js/icons.js +8 -1
  70. package/templates/portal/js/lightbox.js +273 -0
  71. package/templates/portal/js/md.js +23 -6
  72. package/templates/portal/js/mention.js +264 -0
  73. package/templates/portal/js/refresh.js +136 -6
  74. package/templates/portal/js/state.js +55 -8
  75. package/templates/portal/js/views/app.js +23 -5
  76. package/templates/portal/js/views/checklist.js +20 -7
  77. package/templates/portal/js/views/credential.js +93 -0
  78. package/templates/portal/js/views/docs.js +94 -4
  79. package/templates/portal/js/views/files.js +58 -14
  80. package/templates/portal/js/views/graph.js +1 -1
  81. package/templates/portal/js/views/health.js +178 -37
  82. package/templates/portal/js/views/inbox.js +938 -98
  83. package/templates/portal/js/views/memory.js +16 -1
  84. package/templates/portal/js/views/org.js +161 -61
  85. package/templates/portal/js/views/paste.js +29 -0
  86. package/templates/portal/js/views/prompt.js +57 -66
  87. package/templates/portal/js/views/repos.js +12 -7
  88. package/templates/portal/js/views/runonce.js +94 -0
  89. package/templates/portal/js/views/runs.js +165 -0
  90. package/templates/portal/js/views/setup.js +302 -56
  91. package/templates/portal/js/views/staff.js +143 -22
  92. package/templates/portal/js/yaml.js +134 -0
  93. package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
  94. package/templates/ops/prompts/pr-mention.md +0 -57
@@ -0,0 +1,94 @@
1
+ /* One daily run, now, followed to the end.
2
+ *
3
+ * The last step of setting anybody up. Doctor calls a workflow that has never run unproven,
4
+ * because nothing short of a finished run shows the App, its grant, the secrets and the
5
+ * callers all work together. This starts one, and says how it ended. */
6
+
7
+ import { runStatus, startRun } from "../api.js";
8
+ import { askYes } from "../dialog.js";
9
+ import { el } from "../dom.js";
10
+
11
+ /**
12
+ * @param {{staff: string, name: string, onDone?: (ok: boolean) => void}} opts
13
+ */
14
+ export function runOnce(opts) {
15
+ const box = el("div", { className: "manual" });
16
+ box.append(
17
+ el("b", { textContent: "Run " + opts.name + " once, now" }),
18
+ el("p", {
19
+ className: "sub",
20
+ textContent:
21
+ "A real daily run: it does a day's work and spends what a scheduled one would. " +
22
+ "When it finishes you get the outcome and the log.",
23
+ }),
24
+ );
25
+ const go = el("button", { className: "btn primary", textContent: "Run once now" });
26
+ const out = el("div", { style: "margin-top:9px" });
27
+ box.append(el("div", { className: "row" }, [go]), out);
28
+
29
+ go.onclick = async () => {
30
+ const yes = await askYes({
31
+ title: "Start a daily run for " + opts.name + " now?",
32
+ hint: "It is a real run, and it costs what one does.",
33
+ confirm: "Start the run",
34
+ });
35
+ if (!yes) return;
36
+ go.disabled = true;
37
+ const say = (text, cls) => out.replaceChildren(el("p", { className: cls ?? "sub", textContent: text }));
38
+ say("Starting…");
39
+ let run;
40
+ try {
41
+ run = await startRun(opts.staff);
42
+ } catch (err) {
43
+ say(String(err.message || err), "err");
44
+ go.disabled = false;
45
+ return;
46
+ }
47
+ const link = el("a", { href: run.url, target: "_blank", rel: "noopener", textContent: "the log" });
48
+ if (!run.id) {
49
+ out.replaceChildren(el("p", { className: "sub" }, ["Started. It has not shown up yet; follow it at ", link, "."]));
50
+ go.disabled = false;
51
+ return;
52
+ }
53
+
54
+ /* Polled rather than streamed: a run takes minutes, the status changes a handful of times,
55
+ and a clock beside it is what shows the page has not hung. */
56
+ const began = Date.now();
57
+ const tick = async () => {
58
+ let now;
59
+ try {
60
+ now = await runStatus(opts.staff, run.id);
61
+ if (now.error) throw new Error(now.error);
62
+ } catch (err) {
63
+ say(String(err.message || err), "err");
64
+ go.disabled = false;
65
+ return;
66
+ }
67
+ const mins = Math.floor((Date.now() - began) / 60000);
68
+ if (now.status !== "completed") {
69
+ out.replaceChildren(
70
+ el("p", { className: "sub" }, [now.status.replace("_", " ") + ", " + mins + " min so far. ", link]),
71
+ );
72
+ setTimeout(tick, 15000);
73
+ return;
74
+ }
75
+ go.disabled = false;
76
+ const ok = now.conclusion === "success";
77
+ const done = el("div");
78
+ done.append(
79
+ el("p", { className: ok ? "" : "err" }, [
80
+ ok
81
+ ? "It worked. The App, its grant, the secrets and the callers are proven, and Health now says so. "
82
+ : "It ended " + (now.conclusion || "without a conclusion") + ". ",
83
+ el("a", { href: now.url, target: "_blank", rel: "noopener", textContent: "The log" }),
84
+ ]),
85
+ );
86
+ for (const f of now.failedAt ?? []) done.append(el("p", { className: "sub", textContent: "Failed at " + f }));
87
+ if (!ok) done.append(el("p", { className: "sub", textContent: "Troubleshooting, in the docs, has what each failure usually means." }));
88
+ out.replaceChildren(done);
89
+ opts.onDone?.(ok);
90
+ };
91
+ tick();
92
+ };
93
+ return box;
94
+ }
@@ -0,0 +1,165 @@
1
+ /* Every staff member's recent runs, and what the last 30 days cost.
2
+ *
3
+ * The one screen that is only ever on GitHub: runs are not in any repo, so offline it says so
4
+ * rather than drawing an empty table that reads as "nothing ran". */
5
+
6
+ import { getRuns } from "../api.js";
7
+ import { ago, el, esc, skeleton } from "../dom.js";
8
+ import { render } from "../router.js";
9
+ import { S } from "../state.js";
10
+
11
+ /** How many runs a staff member's table shows. The spend covers all of the last 30 days. */
12
+ const SHOWN = 15;
13
+
14
+ export function viewRuns(m) {
15
+ m.append(el("h1", { textContent: "Runs" }));
16
+ m.append(
17
+ el("p", {
18
+ className: "sub",
19
+ textContent:
20
+ "What each staff member ran in the last 30 days, how it ended, and what it cost where " +
21
+ "the agent said. Read from GitHub through your own gh.",
22
+ }),
23
+ );
24
+
25
+ if (!S.runs) {
26
+ const wait = el("div", { className: "runs" }, skeleton("row", 4));
27
+ m.append(wait);
28
+ load(false);
29
+ return;
30
+ }
31
+ paint(m, S.runs);
32
+ }
33
+
34
+ let asking = false;
35
+
36
+ async function load(force) {
37
+ if (asking) return;
38
+ asking = true;
39
+ try {
40
+ S.runs = await getRuns(force);
41
+ } catch (e) {
42
+ S.runs = { online: false, error: e.message, staff: [] };
43
+ } finally {
44
+ asking = false;
45
+ }
46
+ if (S.view === "runs") render();
47
+ }
48
+
49
+ function paint(m, data) {
50
+ const again = el("button", { className: "ghbtn", textContent: "Ask GitHub again" });
51
+ again.onclick = () => {
52
+ S.runs = null;
53
+ load(true);
54
+ render();
55
+ };
56
+
57
+ if (!data.online) {
58
+ const box = el("div", { className: "notice" });
59
+ box.innerHTML =
60
+ "<b>Runs live on GitHub, and it could not be reached.</b><br>" +
61
+ esc(data.error ?? "gh is unavailable") +
62
+ "<br>Everything else in the portal reads from disk and still works.";
63
+ m.append(box, el("div", { className: "row hacts" }, [again]));
64
+ return;
65
+ }
66
+
67
+ const g = el("div", { className: "grid runsum" });
68
+ g.append(tile("Last 30 days", money(data.spend), priced(data.spend), over(data.spend, data.budget)));
69
+ if (data.budget) {
70
+ g.append(tile("Budget", "$" + data.budget, "org.yaml · 30 days, all staff", over(data.spend, data.budget)));
71
+ }
72
+ g.append(tile("Runs", String(data.spend?.runs ?? 0), "skipped mentions are not counted"));
73
+ m.append(g);
74
+
75
+ for (const s of data.staff ?? []) m.append(...staffSection(s));
76
+
77
+ m.append(
78
+ el("div", { className: "row hacts" }, [
79
+ again,
80
+ el("span", { className: "meta", textContent: data.fetchedAt ? "read " + ago(data.fetchedAt) : "" }),
81
+ ]),
82
+ );
83
+ }
84
+
85
+ function staffSection(s) {
86
+ const head = el("div", { className: "hsect" });
87
+ head.append(el("span", { textContent: s.name }));
88
+ const total = el("i", {
89
+ textContent: money(s.spend) + (s.budget ? " of $" + s.budget : "") + " · 30 days",
90
+ });
91
+ if (over(s.spend, s.budget)) total.className = "warn";
92
+ head.append(total);
93
+
94
+ const body = el("div", { className: "hbody runs" });
95
+ for (const e of s.errors ?? []) body.append(el("p", { className: "hnote err", textContent: e }));
96
+ if (!(s.runs ?? []).length) {
97
+ body.append(el("p", { className: "hnote", textContent: "No runs in the last 30 days." }));
98
+ return [head, body];
99
+ }
100
+
101
+ const table = el("table", { className: "runtable" });
102
+ table.innerHTML =
103
+ "<thead><tr><th>When</th><th>Kind</th><th>Outcome</th><th>Took</th><th>Turns</th>" +
104
+ "<th>Cost</th><th></th></tr></thead>";
105
+ const rows = el("tbody");
106
+ for (const r of s.runs.slice(0, SHOWN)) rows.append(row(r));
107
+ table.append(rows);
108
+ body.append(table);
109
+ if (s.runs.length > SHOWN) {
110
+ body.append(
111
+ el("p", {
112
+ className: "hnote",
113
+ textContent: s.runs.length - SHOWN + " older runs count towards the total and are not listed.",
114
+ }),
115
+ );
116
+ }
117
+ return [head, body];
118
+ }
119
+
120
+ function row(r) {
121
+ const tr = el("tr");
122
+ const out = outcome(r);
123
+ const rec = r.record ?? {};
124
+ const mins = rec.duration_s != null ? rec.duration_s / 60 : r.minutes;
125
+ tr.innerHTML =
126
+ '<td title="' + esc(r.createdAt) + '">' + esc(ago(r.createdAt)) + "</td>" +
127
+ "<td>" + esc(r.kind) + "</td>" +
128
+ '<td><span class="chip ' + out.tone + '">' + esc(out.word) + "</span></td>" +
129
+ "<td>" + (mins != null ? Math.round(mins) + "m" : "–") + "</td>" +
130
+ "<td>" + (rec.turns ?? "–") + "</td>" +
131
+ "<td>" + (typeof rec.cost_usd === "number" ? "$" + rec.cost_usd.toFixed(2) : "–") + "</td>" +
132
+ '<td><a class="ref" href="' + esc(r.url) + '" target="_blank" rel="noopener">log</a></td>';
133
+ return tr;
134
+ }
135
+
136
+ /** The record's word when there is one, which knows a setup failure from an agent failure. */
137
+ function outcome(r) {
138
+ if (r.status !== "completed") return { word: r.status.replace("_", " "), tone: "" };
139
+ const word = r.record?.outcome ?? r.conclusion ?? "unknown";
140
+ const tone = word === "success" ? "ok" : word === "cancelled" ? "warm" : "hot";
141
+ return { word, tone };
142
+ }
143
+
144
+ function money(spend) {
145
+ return "$" + (spend?.usd ?? 0).toFixed(2);
146
+ }
147
+
148
+ /** A total is only as good as the runs it could price, so it says how many that was. */
149
+ function priced(spend) {
150
+ if (!spend?.runs) return "no runs";
151
+ if (spend.known === spend.runs) return "every run priced";
152
+ return "cost known for " + spend.known + " of " + spend.runs + " runs";
153
+ }
154
+
155
+ function over(spend, budget) {
156
+ return !!budget && (spend?.usd ?? 0) > budget;
157
+ }
158
+
159
+ function tile(k, v, sub, bad) {
160
+ const d = el("div", { className: "kv" + (bad ? " bad" : "") });
161
+ d.innerHTML =
162
+ '<div class="k">' + esc(k) + '</div><div class="v">' + esc(v) +
163
+ (sub ? "<small>" + esc(sub) + "</small>" : "") + "</div>";
164
+ return d;
165
+ }