@nanocollective/roster 0.1.0-alpha.6 → 0.1.0-alpha.8
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.
- package/README.md +65 -84
- package/dist/cli.js +4187 -2709
- package/docs/README.md +9 -6
- package/docs/agents.md +24 -20
- package/docs/architecture.md +13 -5
- package/docs/charters/cmo.md +69 -0
- package/docs/charters/cto.md +71 -0
- package/docs/charters/support.md +60 -0
- package/docs/commands.md +81 -5
- package/docs/concepts.md +48 -14
- package/docs/cost.md +36 -1
- package/docs/developing.md +16 -21
- package/docs/doctor-codes.md +8 -2
- package/docs/extending.md +2 -2
- package/docs/getting-started.md +110 -77
- package/docs/manual-steps.md +93 -123
- package/docs/memory.md +21 -3
- package/docs/org-yaml.md +37 -2
- package/docs/portal.md +83 -35
- package/docs/prompts.md +25 -4
- package/docs/security.md +29 -5
- package/docs/session-workflow.md +49 -17
- package/docs/staff-yaml.md +13 -2
- package/docs/troubleshooting.md +8 -8
- package/docs/upgrading.md +6 -0
- package/docs/writing-a-charter.md +15 -0
- package/package.json +1 -1
- package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +6 -0
- package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +6 -0
- package/templates/brain/CHARTER.md +3 -3
- package/templates/brain/README.md +1 -0
- package/templates/brain/log/decisions.md +3 -0
- package/templates/brain/strategy/ideas.md +7 -0
- package/templates/ops/.github/workflows/session.yaml +108 -14
- package/templates/ops/agents.mjs +7 -3
- package/templates/ops/compose.mjs +16 -3
- package/templates/ops/inflight.mjs +157 -0
- package/templates/ops/org/operating.md +21 -1
- package/templates/ops/org/voice.md +9 -0
- package/templates/ops/prompts/_inflight.md +14 -0
- package/templates/ops/prompts/_paths.md +2 -1
- package/templates/ops/prompts/daily.md +16 -7
- package/templates/ops/prompts/mention.md +2 -0
- package/templates/ops/run-record.mjs +144 -0
- package/templates/portal/css/runs.css +13 -0
- package/templates/portal/css/setup.css +2 -0
- package/templates/portal/index.html +12 -1
- package/templates/portal/js/api.js +32 -4
- package/templates/portal/js/app.js +42 -8
- package/templates/portal/js/dialog.js +47 -0
- package/templates/portal/js/state.js +3 -1
- package/templates/portal/js/views/app.js +23 -5
- package/templates/portal/js/views/credential.js +93 -0
- package/templates/portal/js/views/health.js +17 -4
- package/templates/portal/js/views/inbox.js +6 -2
- package/templates/portal/js/views/org.js +23 -3
- package/templates/portal/js/views/paste.js +29 -0
- package/templates/portal/js/views/prompt.js +7 -3
- package/templates/portal/js/views/repos.js +12 -7
- package/templates/portal/js/views/runonce.js +94 -0
- package/templates/portal/js/views/runs.js +165 -0
- package/templates/portal/js/views/setup.js +267 -45
- package/templates/portal/js/views/staff.js +81 -20
|
@@ -10,13 +10,16 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { post } from "../api.js";
|
|
13
|
+
import { askYes } from "../dialog.js";
|
|
13
14
|
import { ago, el, esc } from "../dom.js";
|
|
14
15
|
import { icon } from "../icons.js";
|
|
15
16
|
import { refreshAll } from "../refresh.js";
|
|
16
17
|
import { S } from "../state.js";
|
|
17
18
|
import { appPanel } from "./app.js";
|
|
19
|
+
import { credentialPanel } from "./credential.js";
|
|
18
20
|
import { cronText } from "./health.js";
|
|
19
21
|
import { paste } from "./paste.js";
|
|
22
|
+
import { runOnce } from "./runonce.js";
|
|
20
23
|
|
|
21
24
|
export function viewStaff(m) {
|
|
22
25
|
m.append(el("h1", { textContent: "Staff" }));
|
|
@@ -88,23 +91,48 @@ export function viewStaff(m) {
|
|
|
88
91
|
pane.scrollIntoView?.({ behavior: "smooth", block: "start" });
|
|
89
92
|
};
|
|
90
93
|
|
|
91
|
-
|
|
94
|
+
/* The credential is once for the org, but the moment somebody looks for it is while setting
|
|
95
|
+
up one staff member, so it is reachable from each card. */
|
|
96
|
+
const cred = el("button", { className: "ghbtn", textContent: "Agent credential" });
|
|
97
|
+
cred.onclick = () => {
|
|
98
|
+
pane.replaceChildren(el("div", { className: "card" }, [credentialPanel()]));
|
|
99
|
+
pane.scrollIntoView?.({ behavior: "smooth", block: "start" });
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const run = el("button", { className: "ghbtn", textContent: "Run once now" });
|
|
103
|
+
run.onclick = () => {
|
|
104
|
+
pane.replaceChildren(
|
|
105
|
+
el("div", { className: "card" }, [
|
|
106
|
+
runOnce({ staff: s.handle, name: s.name, onDone: (ok) => ok && refreshAll(true) }),
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
pane.scrollIntoView?.({ behavior: "smooth", block: "start" });
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
d.append(
|
|
113
|
+
el("div", { className: "row", style: "margin-top:10px" }, [write, app, cred, run, go, status]),
|
|
114
|
+
);
|
|
92
115
|
return d;
|
|
93
116
|
}
|
|
94
117
|
|
|
95
118
|
/* ------------------------------- hiring -------------------------------- */
|
|
96
119
|
|
|
97
120
|
function hireForm(host) {
|
|
121
|
+
/* App names are copied from whoever is already here. The first hire has nobody to copy
|
|
122
|
+
from, and the CLI asks for --app and --public-app at that point; these are those two. */
|
|
123
|
+
const first = !S.data.staff.length;
|
|
98
124
|
const box = el("div", { className: "card" });
|
|
99
125
|
box.append(
|
|
100
126
|
el("h3", { textContent: "Hire someone" }),
|
|
101
127
|
el("p", {
|
|
102
128
|
className: "sub",
|
|
103
129
|
style: "margin-bottom:14px",
|
|
104
|
-
textContent:
|
|
105
|
-
"Only the handle is required.
|
|
106
|
-
|
|
107
|
-
"
|
|
130
|
+
textContent: first
|
|
131
|
+
? "Only the handle is required. This is the first hire, so there is nobody to copy " +
|
|
132
|
+
"App names from: name them here, and later hires follow the pattern."
|
|
133
|
+
: "Only the handle is required. Everything else is copied from whoever is already " +
|
|
134
|
+
"here, because app slugs carry a house naming scheme and the public identity is " +
|
|
135
|
+
"genuinely shared.",
|
|
108
136
|
}),
|
|
109
137
|
);
|
|
110
138
|
|
|
@@ -112,7 +140,17 @@ export function viewStaff(m) {
|
|
|
112
140
|
const name = field("name", "Chief Financial Officer", "defaults to the handle, uppercased");
|
|
113
141
|
const dir = field("dir", "finance", "directory and repo name; defaults to the handle");
|
|
114
142
|
const schedule = scheduleField();
|
|
115
|
-
|
|
143
|
+
const fields = [handle, name, dir, schedule];
|
|
144
|
+
const apps = [];
|
|
145
|
+
if (first) {
|
|
146
|
+
const org = S.data.org;
|
|
147
|
+
apps.push(
|
|
148
|
+
["app", field("app", org + "-cfo", "this staff member's GitHub App. Names are unique across GitHub, so prefix it")],
|
|
149
|
+
["publicApp", field("public app", org + "-robot", "the shared identity for public product repos. Optional when they are all private")],
|
|
150
|
+
);
|
|
151
|
+
fields.push(...apps.map(([, f]) => f));
|
|
152
|
+
}
|
|
153
|
+
for (const f of fields) box.append(f.row);
|
|
116
154
|
|
|
117
155
|
const status = el("span", { className: "meta" });
|
|
118
156
|
const plan = el("button", { className: "ghbtn primary", textContent: "Show the plan" });
|
|
@@ -134,7 +172,7 @@ export function viewStaff(m) {
|
|
|
134
172
|
status.className = "meta";
|
|
135
173
|
out.replaceChildren();
|
|
136
174
|
const params = new URLSearchParams({ handle: handle.input.value.trim() });
|
|
137
|
-
for (const [key, f] of [["name", name], ["dir", dir], ["schedule", schedule]]) {
|
|
175
|
+
for (const [key, f] of [["name", name], ["dir", dir], ["schedule", schedule], ...apps]) {
|
|
138
176
|
const value = (f.value ? f.value() : f.input.value).trim();
|
|
139
177
|
if (value) params.set(key, value);
|
|
140
178
|
}
|
|
@@ -163,18 +201,33 @@ export function viewStaff(m) {
|
|
|
163
201
|
line("ok", "runs at " + s.schedule + ", on " + s.model),
|
|
164
202
|
);
|
|
165
203
|
for (const p of plan.peers) {
|
|
166
|
-
box.append(line("ok", p.
|
|
204
|
+
box.append(line("ok", p.brain + " gains a " + p.label + " label"));
|
|
167
205
|
}
|
|
168
|
-
|
|
206
|
+
/* Commits into repos that already exist, as you. Listed because they are writes to
|
|
207
|
+
somebody else's repo, and a plan that hid them would be the silent kind. */
|
|
208
|
+
box.append(el("div", { className: "planhead", textContent: "Commits and pushes, as you" }));
|
|
209
|
+
for (const c of plan.commits ?? []) box.append(line("ok", c.repo + ": " + c.file + ", " + c.why));
|
|
169
210
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
line("
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
211
|
+
const cred = s.agentSecret ?? "the agent credential";
|
|
212
|
+
box.append(
|
|
213
|
+
plan.orgSecret?.visibility === "selected"
|
|
214
|
+
? line("ok", s.brain + " is added to the repos that can read the org secret " + cred)
|
|
215
|
+
: plan.orgSecret
|
|
216
|
+
? line("ok", "the org secret " + cred + " already reaches " + plan.orgSecret.visibility + " repos")
|
|
217
|
+
: line("warn", "no org secret " + cred + " yet: store it once with Agent credential, after this"),
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
box.append(el("div", { className: "planhead", textContent: "Then, on the new card" }));
|
|
221
|
+
box.append(
|
|
222
|
+
line(
|
|
223
|
+
"todo",
|
|
224
|
+
"GitHub App, or roster app " + s.handle + " --apply: creates it, sets its secrets, and " +
|
|
225
|
+
"opens the install page with the repos already ticked",
|
|
226
|
+
),
|
|
227
|
+
line("todo", "Write the charter"),
|
|
228
|
+
line("todo", "Run once now, or roster run " + s.handle + " --apply: the run that proves the wiring"),
|
|
229
|
+
);
|
|
230
|
+
for (const w of plan.warnings ?? []) box.append(line("warn", S.data.staff.length ? w : portalWords(w)));
|
|
178
231
|
|
|
179
232
|
const status = el("span", { className: "meta" });
|
|
180
233
|
const go = el("button", { className: "ghbtn primary", textContent: "Hire " + s.handle });
|
|
@@ -185,7 +238,7 @@ export function viewStaff(m) {
|
|
|
185
238
|
handle: s.handle,
|
|
186
239
|
flags: Object.fromEntries(params),
|
|
187
240
|
},
|
|
188
|
-
"Create " + s.brain + " and wire it up
|
|
241
|
+
{ title: "Create " + s.brain + " and wire it up?", hint: "This creates a repository on GitHub.", confirm: "Hire " + s.handle },
|
|
189
242
|
go,
|
|
190
243
|
status,
|
|
191
244
|
box,
|
|
@@ -240,7 +293,7 @@ export function viewStaff(m) {
|
|
|
240
293
|
go.onclick = () =>
|
|
241
294
|
apply(
|
|
242
295
|
{ action: "retire", handle: plan.handle },
|
|
243
|
-
"Retire " + plan.name + "
|
|
296
|
+
{ title: "Retire " + plan.name + "?", hint: (plan.brain ?? plan.dir) + " is not touched.", confirm: "Retire " + plan.handle },
|
|
244
297
|
go,
|
|
245
298
|
status,
|
|
246
299
|
box,
|
|
@@ -252,7 +305,7 @@ export function viewStaff(m) {
|
|
|
252
305
|
/* -------------------------------- apply -------------------------------- */
|
|
253
306
|
|
|
254
307
|
async function apply(payload, ask, btn, status, box) {
|
|
255
|
-
if (!
|
|
308
|
+
if (!(await askYes(ask))) return;
|
|
256
309
|
btn.disabled = true;
|
|
257
310
|
status.textContent = "working…";
|
|
258
311
|
status.className = "meta";
|
|
@@ -271,6 +324,14 @@ export function viewStaff(m) {
|
|
|
271
324
|
}
|
|
272
325
|
}
|
|
273
326
|
|
|
327
|
+
/* The plan's warnings are the CLI's, and name its flags. Here the same two options are fields
|
|
328
|
+
on the form above, and a flag the page has no box for reads as a dead end. */
|
|
329
|
+
function portalWords(warning) {
|
|
330
|
+
return warning
|
|
331
|
+
.replace(/pass --public-app\b/, "fill in public app above")
|
|
332
|
+
.replace(/pass --app\b/, "fill in app above");
|
|
333
|
+
}
|
|
334
|
+
|
|
274
335
|
function field(label, placeholder, hint) {
|
|
275
336
|
const input = el("input", { type: "search", placeholder, value: "", style: "width:100%" });
|
|
276
337
|
input.dataset.field = label;
|