@nanocollective/roster 0.1.0-alpha.11 → 0.1.0-alpha.13
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/package.json
CHANGED
|
@@ -117,3 +117,7 @@
|
|
|
117
117
|
.verdict{background:var(--accent-soft);border:1px solid transparent;border-radius:var(--r);
|
|
118
118
|
padding:9px 12px;font-size:12.5px}
|
|
119
119
|
.verdict p{margin:0}
|
|
120
|
+
|
|
121
|
+
.plansteps{margin:6px 0 12px;padding-left:20px;font-size:13.5px;line-height:1.7}
|
|
122
|
+
.planfiles{margin:0 0 12px}
|
|
123
|
+
.planfiles summary{cursor:pointer;color:var(--ink-dim);font-size:12.5px}
|
|
@@ -10,6 +10,8 @@ import { el, toClipboard } from "../dom.js";
|
|
|
10
10
|
const LEVEL_ORDER = { fail: 0, warn: 1, ok: 2 };
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
+
* @param opts.skip finding ids the caller already shows as a step of its own, so they are not
|
|
14
|
+
* reported twice. With `opts.hideWhenClean`, a list left empty draws nothing at all.
|
|
13
15
|
* @param opts.shell when Health draws this, the section it lives in: `{actions, say}`. The
|
|
14
16
|
* setup screen passes nothing and gets the layout it has always had. Its own screen is a
|
|
15
17
|
* wizard, where a chunky button and a paragraph under it are right; on Health it is one
|
|
@@ -32,9 +34,14 @@ export async function checklist(host, opts = {}) {
|
|
|
32
34
|
const findings = (report.findings ?? []).slice().sort(
|
|
33
35
|
(a, b) => (LEVEL_ORDER[a.level] ?? 3) - (LEVEL_ORDER[b.level] ?? 3),
|
|
34
36
|
);
|
|
35
|
-
const
|
|
37
|
+
const skip = new Set(opts.skip ?? []);
|
|
38
|
+
const bad = findings.filter((f) => f.level !== "ok" && !skip.has(f.id));
|
|
36
39
|
|
|
37
40
|
host.replaceChildren();
|
|
41
|
+
if (!bad.length && opts.hideWhenClean) {
|
|
42
|
+
opts.onEmpty?.();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
38
45
|
opts.shell?.say(bad.length ? String(bad.length) : "clean");
|
|
39
46
|
host.append(
|
|
40
47
|
el("p", {
|
|
@@ -83,7 +90,7 @@ export async function checklist(host, opts = {}) {
|
|
|
83
90
|
const again = el("button", { className: btnClass, textContent: "Check again" });
|
|
84
91
|
again.onclick = () => checklist(host, opts);
|
|
85
92
|
|
|
86
|
-
const feet = opts.shell?.actions ?? host;
|
|
93
|
+
const feet = opts.shell?.actions ?? host.appendChild(el("div", { className: "row" }));
|
|
87
94
|
if (opts.shell) feet.replaceChildren();
|
|
88
95
|
feet.append(copy, again, note);
|
|
89
96
|
if (bad.length) {
|
|
@@ -92,8 +99,7 @@ export async function checklist(host, opts = {}) {
|
|
|
92
99
|
className: noteClass,
|
|
93
100
|
style: "flex-basis:100%",
|
|
94
101
|
textContent:
|
|
95
|
-
"Paste
|
|
96
|
-
"check again. The ids above should be gone.",
|
|
102
|
+
"Paste them into your coding agent, then check again.",
|
|
97
103
|
}),
|
|
98
104
|
);
|
|
99
105
|
}
|
|
@@ -8,7 +8,7 @@ import { el } from "../dom.js";
|
|
|
8
8
|
|
|
9
9
|
export function credentialPanel() {
|
|
10
10
|
const box = el("div", { className: "manual" });
|
|
11
|
-
box.append(el("b", { textContent: "
|
|
11
|
+
box.append(el("b", { textContent: "Agent credential" }));
|
|
12
12
|
const body = el("div");
|
|
13
13
|
box.append(body);
|
|
14
14
|
body.append(el("p", { className: "sub", textContent: "Looking at where it would go…" }));
|
|
@@ -28,8 +28,7 @@ function draw(body, c) {
|
|
|
28
28
|
el("p", {
|
|
29
29
|
className: "sub",
|
|
30
30
|
textContent:
|
|
31
|
-
"
|
|
32
|
-
"member. It is asked for once, not once per hire.",
|
|
31
|
+
"Available after your first hire. You only add it once.",
|
|
33
32
|
}),
|
|
34
33
|
);
|
|
35
34
|
return;
|
|
@@ -40,8 +39,7 @@ function draw(body, c) {
|
|
|
40
39
|
el("p", {
|
|
41
40
|
className: "sub",
|
|
42
41
|
textContent:
|
|
43
|
-
c.name + " is already
|
|
44
|
-
"Each hire adds its brain to it. Paste a new one only to replace it.",
|
|
42
|
+
c.name + " is already set. Paste a new one only to replace it.",
|
|
45
43
|
}),
|
|
46
44
|
);
|
|
47
45
|
}
|
|
@@ -53,7 +51,7 @@ function draw(body, c) {
|
|
|
53
51
|
(c.plan.mode === "org"
|
|
54
52
|
? "Stored as one org secret, " + c.name + ", shared with " + c.brains.join(", ") + ". "
|
|
55
53
|
: "Stored as " + c.name + " on each of " + c.brains.join(", ") + ", because " + c.plan.reason + ". ") +
|
|
56
|
-
"It
|
|
54
|
+
"It's never written to disk.",
|
|
57
55
|
}),
|
|
58
56
|
);
|
|
59
57
|
|
|
@@ -80,8 +78,8 @@ function draw(body, c) {
|
|
|
80
78
|
el("p", {
|
|
81
79
|
textContent:
|
|
82
80
|
r.mode === "org"
|
|
83
|
-
? "
|
|
84
|
-
: "
|
|
81
|
+
? "Saved."
|
|
82
|
+
: "Saved on " + r.repos.join(", ") + ".",
|
|
85
83
|
}),
|
|
86
84
|
);
|
|
87
85
|
} catch (err) {
|
|
@@ -18,9 +18,7 @@ export function repoPicker(opts) {
|
|
|
18
18
|
el("b", { textContent: "Which repos the staff work in" }),
|
|
19
19
|
el("p", {
|
|
20
20
|
textContent:
|
|
21
|
-
"
|
|
22
|
-
"picks them up automatically; somebody already hired keeps the works_in list in their " +
|
|
23
|
-
"own staff.yaml.",
|
|
21
|
+
"Add the repos your staff should work on. New hires get access to them automatically.",
|
|
24
22
|
}),
|
|
25
23
|
);
|
|
26
24
|
|
|
@@ -30,6 +30,12 @@ let S = null; // /api/setup/status, refreshed after anything that changes the wo
|
|
|
30
30
|
/* Where the doctor checklist is drawn on this screen, so a save can re-run it. Left stale, it
|
|
31
31
|
went on saying business.stub after business.md had been saved, until "Check again". */
|
|
32
32
|
let healthHost = null;
|
|
33
|
+
let healthStep = null;
|
|
34
|
+
|
|
35
|
+
/* What the "What is left" cards already ask for. Doctor reporting them as well, right after a
|
|
36
|
+
successful create, read as the create having failed. */
|
|
37
|
+
const COVERED = ["business", "business.stub", "priorities", "priorities.stub", "actions-access", "secrets", "agent"];
|
|
38
|
+
const HEALTH = { skip: COVERED, hideWhenClean: true };
|
|
33
39
|
|
|
34
40
|
/* Each "what is left" step that is a file, by id, so a save can mark it done in place rather
|
|
35
41
|
than repainting the page and throwing away the diff somebody is still reading. */
|
|
@@ -66,8 +72,7 @@ export async function viewGettingStarted(main) {
|
|
|
66
72
|
next.append(
|
|
67
73
|
el("p", {
|
|
68
74
|
textContent:
|
|
69
|
-
"Nothing runs until
|
|
70
|
-
"their pinned issue; the GitHub App and the charter follow on their card.",
|
|
75
|
+
"Nothing runs until you hire someone. You'll set up their GitHub App and charter from their card.",
|
|
71
76
|
}),
|
|
72
77
|
el("div", { className: "row" }, [hire]),
|
|
73
78
|
);
|
|
@@ -76,11 +81,12 @@ export async function viewGettingStarted(main) {
|
|
|
76
81
|
|
|
77
82
|
main.append(afterCreate(App.data?.staff?.length ? 1 : 2));
|
|
78
83
|
|
|
79
|
-
const health = step(App.data?.staff?.length ? 2 : 3, "
|
|
84
|
+
const health = step(App.data?.staff?.length ? 2 : 3, "Other problems", false);
|
|
80
85
|
healthHost = el("div", { style: "margin-top:12px" });
|
|
81
86
|
health.append(healthHost);
|
|
82
87
|
main.append(health);
|
|
83
|
-
|
|
88
|
+
healthStep = health;
|
|
89
|
+
checklist(healthHost, { ...HEALTH, onEmpty: () => health.remove() });
|
|
84
90
|
}
|
|
85
91
|
|
|
86
92
|
/** Whether the sidebar offers Getting started: while anything is left, or while it is open. */
|
|
@@ -94,7 +100,7 @@ export function paintSetupNav() {
|
|
|
94
100
|
again rather than updated by hand. The sidebar's list comes from /api/org, which is the one
|
|
95
101
|
the portal already reads. */
|
|
96
102
|
async function recheck() {
|
|
97
|
-
if (healthHost) checklist(healthHost, {});
|
|
103
|
+
if (healthHost) checklist(healthHost, { ...HEALTH, onEmpty: () => healthStep?.remove() });
|
|
98
104
|
try {
|
|
99
105
|
S = await getSetup();
|
|
100
106
|
for (const [id, mark] of fileSteps) mark(!(S.tenant.left ?? []).includes(id));
|
|
@@ -120,9 +126,7 @@ function render(main) {
|
|
|
120
126
|
el("p", {
|
|
121
127
|
className: "sub",
|
|
122
128
|
textContent:
|
|
123
|
-
"
|
|
124
|
-
"roster tries for you, the agent's credential, the repos your staff work in, and the two " +
|
|
125
|
-
"files only you can write. Nothing is created until you have read the plan.",
|
|
129
|
+
"Choose an organisation and a coding agent. Nothing is created until you press Create it.",
|
|
126
130
|
}),
|
|
127
131
|
);
|
|
128
132
|
|
|
@@ -148,8 +152,7 @@ function stepGh() {
|
|
|
148
152
|
el("p", {
|
|
149
153
|
className: "sub",
|
|
150
154
|
textContent:
|
|
151
|
-
"
|
|
152
|
-
"in a terminal, then reload:",
|
|
155
|
+
"Run this in a terminal, then reload:",
|
|
153
156
|
}),
|
|
154
157
|
el("pre", { className: "cmd", textContent: "gh auth login" }),
|
|
155
158
|
);
|
|
@@ -170,13 +173,9 @@ function stepOrg(main) {
|
|
|
170
173
|
el("p", {
|
|
171
174
|
className: "sub",
|
|
172
175
|
textContent:
|
|
173
|
-
"
|
|
174
|
-
"Getting started in the sidebar until it is.",
|
|
176
|
+
"Finish the steps below. They stay under Getting started in the sidebar until they're done.",
|
|
175
177
|
}),
|
|
176
178
|
);
|
|
177
|
-
healthHost = el("div", { style: "margin-top:12px" });
|
|
178
|
-
card.append(healthHost);
|
|
179
|
-
checklist(healthHost, {});
|
|
180
179
|
return card;
|
|
181
180
|
}
|
|
182
181
|
|
|
@@ -206,8 +205,7 @@ function stepOrg(main) {
|
|
|
206
205
|
orgWrap.append(
|
|
207
206
|
el("small", {
|
|
208
207
|
textContent:
|
|
209
|
-
"
|
|
210
|
-
"github.com first if you need to.",
|
|
208
|
+
"Don't have one? Create it on github.com first.",
|
|
211
209
|
}),
|
|
212
210
|
);
|
|
213
211
|
form.append(orgWrap);
|
|
@@ -250,8 +248,8 @@ function stepOrg(main) {
|
|
|
250
248
|
});
|
|
251
249
|
other.input.addEventListener("change", look);
|
|
252
250
|
|
|
253
|
-
const name = field("name", "What the business is called", "", "
|
|
254
|
-
const human = field("human", "Your GitHub login", S.gh.login ?? "", "The person the
|
|
251
|
+
const name = field("name", "What the business is called", "", "Defaults to the organisation name.");
|
|
252
|
+
const human = field("human", "Your GitHub login", S.gh.login ?? "", "The person the staff report to.");
|
|
255
253
|
|
|
256
254
|
card.append(form);
|
|
257
255
|
const agentHead = el("h3", { textContent: "Which coding agent runs a session" });
|
|
@@ -272,8 +270,7 @@ function stepOrg(main) {
|
|
|
272
270
|
const agentNote = el("p", {
|
|
273
271
|
className: "sub",
|
|
274
272
|
textContent:
|
|
275
|
-
"
|
|
276
|
-
"the secret holding its credential. Write those three into org.yaml afterwards.",
|
|
273
|
+
"Using a different agent? Pick any of these, then set it in org.yaml afterwards.",
|
|
277
274
|
});
|
|
278
275
|
card.append(agentNote);
|
|
279
276
|
|
|
@@ -349,26 +346,20 @@ function stepOrg(main) {
|
|
|
349
346
|
out.replaceChildren(el("p", { className: "sub", textContent: "Working…" }));
|
|
350
347
|
try {
|
|
351
348
|
const result = await planTenant(p);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
el("
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
}),
|
|
360
|
-
);
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
className: "sub",
|
|
367
|
-
textContent:
|
|
368
|
-
"org/business.md arrives as questions, not prose. Answering it is the next step and " +
|
|
369
|
-
"it is the one that decides whether any of this is worth running.",
|
|
370
|
-
}),
|
|
371
|
-
);
|
|
349
|
+
const steps = el("ol", { className: "plansteps" }, [
|
|
350
|
+
el("li", { textContent: `Create a private repo, ${p.org}/roster-ops, to hold the org's shared settings.` }),
|
|
351
|
+
el("li", { textContent: `Put a copy of it in ${result.dir}.` }),
|
|
352
|
+
el("li", { textContent: `Let the other repos in ${p.org} run its workflow.` }),
|
|
353
|
+
]);
|
|
354
|
+
const files = el("details", { className: "planfiles" }, [
|
|
355
|
+
el("summary", { textContent: `The ${result.files.length} files it writes` }),
|
|
356
|
+
el("div", { className: "filelist" }, result.files.map((f) => el("code", { textContent: f }))),
|
|
357
|
+
]);
|
|
358
|
+
const next = el("p", {
|
|
359
|
+
className: "sub",
|
|
360
|
+
textContent: "After that: describe the business, set this month's priorities, and hire your first staff member.",
|
|
361
|
+
});
|
|
362
|
+
out.replaceChildren(el("p", { textContent: "Create it will:" }), steps, files, next);
|
|
372
363
|
go.hidden = false;
|
|
373
364
|
} catch (err) {
|
|
374
365
|
out.replaceChildren(el("p", { className: "err", textContent: String(err.message || err) }));
|
|
@@ -401,13 +392,6 @@ function stepOrg(main) {
|
|
|
401
392
|
function afterCreate(n) {
|
|
402
393
|
fileSteps.clear();
|
|
403
394
|
const card = step(n, "What is left", false);
|
|
404
|
-
card.append(
|
|
405
|
-
el("p", {
|
|
406
|
-
textContent:
|
|
407
|
-
"The first is a setting roster tries for you. The last two are the files that decide " +
|
|
408
|
-
"whether any of this is worth running, and only you can write them.",
|
|
409
|
-
}),
|
|
410
|
-
);
|
|
411
395
|
card.append(accessStep());
|
|
412
396
|
card.append(credentialPanel());
|
|
413
397
|
|
|
@@ -417,9 +401,8 @@ function afterCreate(n) {
|
|
|
417
401
|
|
|
418
402
|
const business = fileStep(
|
|
419
403
|
"business",
|
|
420
|
-
"
|
|
421
|
-
"
|
|
422
|
-
"agent that cannot answer them writes work that is plausible and generic.",
|
|
404
|
+
"Describe the business",
|
|
405
|
+
"Answer the questions in org/business.md. Every staff member reads it before they start work.",
|
|
423
406
|
);
|
|
424
407
|
// The copy-a-prompt loop, which is the whole answer to "how do I write this file".
|
|
425
408
|
business.append(paste({ kind: "discover", title: "Write it with your own AI", onSaved: recheck }));
|
|
@@ -427,9 +410,8 @@ function afterCreate(n) {
|
|
|
427
410
|
|
|
428
411
|
const priorities = fileStep(
|
|
429
412
|
"priorities",
|
|
430
|
-
"
|
|
431
|
-
"
|
|
432
|
-
"daily run reads it; without it each staff member picks its own direction from its charter.",
|
|
413
|
+
"Set this month's priorities",
|
|
414
|
+
"Up to three things, in order, plus what's out of scope. Saved as org/priorities.md.",
|
|
433
415
|
);
|
|
434
416
|
priorities.append(prioritiesEditor());
|
|
435
417
|
card.append(priorities);
|
|
@@ -500,7 +482,7 @@ function prioritiesEditor() {
|
|
|
500
482
|
el("p", {
|
|
501
483
|
className: r.pushed ? "sub" : "err",
|
|
502
484
|
textContent: r.pushed
|
|
503
|
-
? "
|
|
485
|
+
? "Saved and pushed."
|
|
504
486
|
: "Committed, but the push failed: " + (r.note ?? ""),
|
|
505
487
|
}),
|
|
506
488
|
);
|
|
@@ -529,7 +511,7 @@ function opsName() {
|
|
|
529
511
|
*/
|
|
530
512
|
function accessStep() {
|
|
531
513
|
const box = el("div", { className: "manual" });
|
|
532
|
-
box.append(el("b", { textContent: "Let
|
|
514
|
+
box.append(el("b", { textContent: "Let staff repos run the shared workflow" }));
|
|
533
515
|
const out = el("div");
|
|
534
516
|
box.append(out);
|
|
535
517
|
const say = (text, cls) => el("p", { className: cls ?? "sub", textContent: text });
|
|
@@ -539,8 +521,7 @@ function accessStep() {
|
|
|
539
521
|
out.replaceChildren(
|
|
540
522
|
say(
|
|
541
523
|
(why ? why + " " : "") +
|
|
542
|
-
"
|
|
543
|
-
"with “workflow not found”.",
|
|
524
|
+
"Needed so staff repos can run the shared workflow.",
|
|
544
525
|
),
|
|
545
526
|
el("div", { className: "row" }, [go]),
|
|
546
527
|
);
|
|
@@ -549,7 +530,7 @@ function accessStep() {
|
|
|
549
530
|
try {
|
|
550
531
|
const r = await setAccess();
|
|
551
532
|
if (r.ok) {
|
|
552
|
-
out.replaceChildren(say("
|
|
533
|
+
out.replaceChildren(say("Done."));
|
|
553
534
|
return;
|
|
554
535
|
}
|
|
555
536
|
out.replaceChildren(
|
|
@@ -567,7 +548,7 @@ function accessStep() {
|
|
|
567
548
|
out.replaceChildren(say("Checking…"));
|
|
568
549
|
getAccess()
|
|
569
550
|
.then((r) => {
|
|
570
|
-
if (r.ok) out.replaceChildren(say("Done.
|
|
551
|
+
if (r.ok) out.replaceChildren(say("Done."));
|
|
571
552
|
else offer(r.level ? "It is set to “" + r.level + "”." : "");
|
|
572
553
|
})
|
|
573
554
|
.catch(() => offer(""));
|