@nanocollective/roster 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +70 -84
- package/dist/cli.js +4817 -2374
- package/docs/README.md +19 -11
- package/docs/agents.md +328 -13
- package/docs/architecture.md +13 -5
- package/docs/charters/analyst.md +65 -0
- package/docs/charters/cmo.md +69 -0
- package/docs/charters/community.md +63 -0
- package/docs/charters/cto.md +71 -0
- package/docs/charters/designer.md +65 -0
- package/docs/charters/devops.md +65 -0
- package/docs/charters/pm.md +70 -0
- package/docs/charters/qa.md +65 -0
- package/docs/charters/support.md +60 -0
- package/docs/charters/writer.md +63 -0
- package/docs/commands.md +95 -11
- package/docs/concepts.md +64 -12
- package/docs/cost.md +39 -3
- package/docs/developing.md +16 -21
- package/docs/doctor-codes.md +21 -6
- package/docs/export.md +4 -1
- package/docs/extending.md +13 -4
- package/docs/getting-started.md +133 -79
- package/docs/images/brain.jpg +0 -0
- package/docs/images/org.jpg +0 -0
- package/docs/images/prompt.jpg +0 -0
- package/docs/images/setup-org.jpg +0 -0
- package/docs/images/setup-plan.jpg +0 -0
- package/docs/images/staff.jpg +0 -0
- package/docs/manual-steps.md +95 -101
- package/docs/memory.md +29 -8
- package/docs/org-yaml.md +76 -11
- package/docs/portal.md +290 -49
- package/docs/prompts.md +77 -11
- package/docs/security.md +51 -7
- package/docs/session-workflow.md +51 -21
- package/docs/staff-yaml.md +17 -7
- package/docs/troubleshooting.md +23 -20
- package/docs/upgrading.md +9 -3
- package/docs/writing-a-charter.md +46 -17
- package/package.json +1 -1
- package/templates/brain/.github/workflows/%%STAFF%%-daily.yaml +7 -0
- package/templates/brain/.github/workflows/%%STAFF%%-mention.yaml +16 -4
- 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/staff.yaml +0 -1
- package/templates/brain/strategy/ideas.md +7 -0
- package/templates/briefs/priorities.md +46 -0
- package/templates/ops/.github/workflows/session.yaml +117 -40
- package/templates/ops/agents.mjs +127 -8
- package/templates/ops/compose.mjs +77 -7
- package/templates/ops/inflight.mjs +157 -0
- package/templates/ops/org/operating.md +21 -7
- package/templates/ops/org/voice.md +9 -0
- package/templates/ops/prompts/_identity.md +8 -1
- 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 +18 -2
- package/templates/ops/run-record.mjs +144 -0
- package/templates/portal/css/base.css +245 -64
- package/templates/portal/css/brain.css +30 -20
- package/templates/portal/css/diff.css +15 -10
- package/templates/portal/css/graph.css +12 -7
- package/templates/portal/css/health.css +32 -11
- package/templates/portal/css/inbox.css +117 -14
- package/templates/portal/css/layout.css +114 -41
- package/templates/portal/css/markdown.css +57 -15
- package/templates/portal/css/runs.css +13 -0
- package/templates/portal/css/setup.css +126 -39
- package/templates/portal/index.html +25 -3
- package/templates/portal/js/api.js +74 -4
- package/templates/portal/js/app.js +156 -14
- package/templates/portal/js/dialog.js +129 -4
- package/templates/portal/js/dom.js +25 -0
- package/templates/portal/js/icons.js +45 -1
- package/templates/portal/js/inflight.js +18 -0
- package/templates/portal/js/lightbox.js +273 -0
- package/templates/portal/js/md.js +23 -6
- package/templates/portal/js/mdedit.js +84 -0
- package/templates/portal/js/mention.js +264 -0
- package/templates/portal/js/readiness.js +35 -0
- package/templates/portal/js/refresh.js +136 -6
- package/templates/portal/js/state.js +59 -8
- package/templates/portal/js/views/app.js +24 -7
- package/templates/portal/js/views/checklist.js +29 -10
- package/templates/portal/js/views/credential.js +84 -0
- package/templates/portal/js/views/docs.js +94 -4
- package/templates/portal/js/views/files.js +58 -14
- package/templates/portal/js/views/graph.js +1 -1
- package/templates/portal/js/views/health.js +178 -37
- package/templates/portal/js/views/hire.js +583 -0
- package/templates/portal/js/views/inbox.js +957 -126
- package/templates/portal/js/views/memory.js +16 -1
- package/templates/portal/js/views/org.js +124 -104
- package/templates/portal/js/views/orgedit.js +234 -0
- package/templates/portal/js/views/paste.js +87 -21
- package/templates/portal/js/views/prompt.js +61 -67
- package/templates/portal/js/views/repos.js +20 -15
- 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 +257 -75
- package/templates/portal/js/views/staff.js +157 -182
- package/templates/portal/js/views/todo.js +62 -0
- package/templates/portal/js/yaml.js +134 -0
- package/templates/brain/.github/workflows/%%STAFF%%-pr-mention.yaml +0 -50
- package/templates/ops/prompts/pr-mention.md +0 -57
|
@@ -1,53 +1,63 @@
|
|
|
1
1
|
/* The setup screen, and the copy-a-prompt panel it shares with the authoring screens. */
|
|
2
2
|
|
|
3
|
-
.step{background:var(--panel);border:1px solid var(--line);border-radius:
|
|
4
|
-
padding:
|
|
5
|
-
.step.done{border-color:var(--accent
|
|
3
|
+
.step{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);
|
|
4
|
+
padding:20px 22px 22px;margin-bottom:14px;box-shadow:var(--shadow-sm)}
|
|
5
|
+
.step.done{border-color:color-mix(in srgb,var(--accent) 30%,var(--line))}
|
|
6
6
|
.stephead{display:flex;align-items:center;gap:11px;margin-bottom:10px}
|
|
7
|
-
.stephead h2{margin:0;font:600 16px var(--sans);letter-spacing:-.
|
|
8
|
-
.stepn{flex:none;width:
|
|
9
|
-
background:var(--
|
|
7
|
+
.stephead h2{margin:0;font:600 16px var(--sans);letter-spacing:-.018em}
|
|
8
|
+
.stepn{flex:none;width:24px;height:24px;border-radius:50%;display:grid;place-items:center;
|
|
9
|
+
background:var(--surface-2);border:1px solid transparent;color:var(--ink-dim);
|
|
10
10
|
font:600 11px var(--mono)}
|
|
11
|
-
.step.done .stepn{background:var(--accent-
|
|
11
|
+
.step.done .stepn{background:var(--accent-fill);border-color:transparent;color:var(--accent-ink)}
|
|
12
12
|
|
|
13
13
|
/* ---------- the form ---------- */
|
|
14
14
|
|
|
15
15
|
.setupform{display:grid;gap:13px;margin:14px 0 18px;max-width:520px}
|
|
16
16
|
.field{display:grid;gap:4px}
|
|
17
17
|
.field>span{font:600 12px var(--sans);color:var(--ink-dim)}
|
|
18
|
-
.field input,.field select{background:var(--
|
|
19
|
-
border-radius:
|
|
20
|
-
|
|
18
|
+
.field input,.field select{background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
|
|
19
|
+
border-radius:var(--r);padding:8px 11px;font:13px var(--sans);outline:none;width:100%;
|
|
20
|
+
transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
|
|
21
|
+
.field input:focus,.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
|
|
21
22
|
.field small{color:var(--ink-faint);font-size:11.5px;line-height:1.45}
|
|
22
23
|
|
|
23
24
|
.choices{display:grid;gap:8px;margin:10px 0 4px;max-width:640px}
|
|
24
25
|
.choice{display:grid;grid-template-columns:auto 1fr;gap:3px 10px;align-items:start;
|
|
25
|
-
background:var(--
|
|
26
|
-
padding:
|
|
27
|
-
|
|
26
|
+
background:var(--bg-elev);border:1px solid var(--line);border-radius:var(--r);
|
|
27
|
+
padding:12px 14px;cursor:pointer;
|
|
28
|
+
transition:border-color .15s var(--ease),background .15s var(--ease),box-shadow .15s var(--ease)}
|
|
29
|
+
.choice:hover{border-color:var(--border-strong)}
|
|
30
|
+
.choice:has(input:checked){border-color:var(--accent-dim);background:var(--accent-soft)}
|
|
28
31
|
.choice input{grid-row:1/3;margin-top:3px;accent-color:var(--accent)}
|
|
29
32
|
.choice b{font:600 13px var(--sans)}
|
|
30
33
|
.choice small{color:var(--ink-dim);font-size:12px;line-height:1.45}
|
|
31
34
|
.choice code{grid-column:2;font:10.5px var(--mono);color:var(--ink-faint)}
|
|
32
35
|
|
|
33
36
|
.row{display:flex;gap:9px;align-items:center;flex-wrap:wrap;margin-top:6px}
|
|
34
|
-
.btn{background:var(--
|
|
35
|
-
padding:
|
|
36
|
-
.
|
|
37
|
-
.btn
|
|
38
|
-
.btn
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
.btn{background:var(--surface-2);border:1px solid transparent;color:var(--ink);border-radius:8px;
|
|
38
|
+
padding:7px 14px;font:500 13px var(--sans);cursor:pointer;text-decoration:none;display:inline-block;
|
|
39
|
+
transition:background .15s var(--ease),opacity .15s var(--ease)}
|
|
40
|
+
.btn:hover{background:color-mix(in srgb,var(--surface-2) 88%,var(--ink) 12%)}
|
|
41
|
+
.btn:active{opacity:.8}
|
|
42
|
+
/* The full accent, not the dim one. Dim on the page background is pale-green-on-white with
|
|
43
|
+
near-white text: the button that does the main thing on the screen read as disabled. */
|
|
44
|
+
.btn.primary{background:var(--accent-fill);border-color:transparent;color:var(--accent-ink);font-weight:600}
|
|
45
|
+
.btn.primary:hover{background:var(--accent-fill);opacity:.88}
|
|
46
|
+
.btn[disabled]{opacity:.45;cursor:default}
|
|
47
|
+
|
|
48
|
+
.cmd{background:var(--bg);border:1px solid var(--line);border-radius:var(--r);padding:10px 12px;
|
|
41
49
|
font:12px/1.6 var(--mono);color:var(--ink);overflow-x:auto;white-space:pre-wrap;
|
|
42
50
|
word-break:break-word;margin:9px 0}
|
|
43
51
|
|
|
44
52
|
.planout{margin-top:12px}
|
|
45
|
-
.filelist{display:grid;gap:2px;margin:9px 0;
|
|
46
|
-
background:var(--
|
|
53
|
+
.filelist{display:grid;gap:2px;margin:9px 0;
|
|
54
|
+
background:var(--bg);border:1px solid var(--line);border-radius:var(--r);padding:10px 12px}
|
|
47
55
|
.filelist code{font:11px var(--mono);color:var(--ink-dim)}
|
|
48
56
|
|
|
49
|
-
.manual{background:var(--
|
|
57
|
+
.manual{background:var(--bg-elev);border:1px solid var(--line);border-radius:var(--r);
|
|
50
58
|
padding:13px 15px;margin:11px 0}
|
|
59
|
+
/* A file step that is written says so in the border too, the way a finished step does. */
|
|
60
|
+
.manual.done{border-color:color-mix(in srgb,var(--accent) 30%,var(--line))}
|
|
51
61
|
.manual b{display:block;font:600 13px var(--sans);margin-bottom:4px}
|
|
52
62
|
.manual p{margin:0 0 6px;font-size:13px}
|
|
53
63
|
.manual small{display:block;color:var(--ink-faint);font-size:11.5px;line-height:1.5;margin-bottom:9px}
|
|
@@ -56,32 +66,33 @@
|
|
|
56
66
|
|
|
57
67
|
.paste{border-top:1px solid var(--line);margin-top:16px;padding-top:15px}
|
|
58
68
|
.paste h3{margin:0 0 5px;font:600 14px var(--sans)}
|
|
59
|
-
.briefpreview{background:var(--
|
|
69
|
+
.briefpreview{background:var(--bg);border:1px solid var(--line);border-radius:var(--r);
|
|
60
70
|
padding:12px 14px;font:11.5px/1.6 var(--mono);max-height:340px;overflow:auto;
|
|
61
71
|
white-space:pre-wrap;word-break:break-word;margin:10px 0}
|
|
62
|
-
.pastebox{width:100%;background:var(--
|
|
63
|
-
border-radius:
|
|
72
|
+
.pastebox{width:100%;background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
|
|
73
|
+
border-radius:var(--r);padding:10px 12px;font:12px/1.6 var(--mono);outline:none;resize:vertical;
|
|
64
74
|
margin:4px 0}
|
|
65
|
-
.pastebox:focus{border-color:var(--accent-
|
|
75
|
+
.pastebox:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
|
|
66
76
|
|
|
67
77
|
.pasteresult{margin-top:12px}
|
|
68
78
|
/* A model that replied in prose is the common case and not the person's mistake, so this reads
|
|
69
79
|
as a next step rather than as a failure. */
|
|
70
|
-
.problem{background:
|
|
80
|
+
.problem{background:color-mix(in srgb,var(--warn) 9%,transparent);
|
|
81
|
+
border:1px solid transparent;border-radius:var(--r);
|
|
71
82
|
padding:12px 14px;margin-bottom:10px}
|
|
72
83
|
.problem b{display:block;font:600 13px var(--sans);margin-bottom:7px}
|
|
73
84
|
|
|
74
|
-
.pastefile{background:var(--
|
|
85
|
+
.pastefile{background:var(--bg-elev);border:1px solid var(--line);border-radius:var(--r);
|
|
75
86
|
padding:12px 14px;margin-bottom:10px}
|
|
76
87
|
.pastefile.saved{border-color:var(--accent-dim)}
|
|
77
88
|
.pastefile b{font:600 13px var(--mono)}
|
|
78
89
|
.pastefile .meta{margin-left:9px}
|
|
79
|
-
.pastefile .diff{
|
|
90
|
+
.pastefile .diff{margin:9px 0}
|
|
80
91
|
|
|
81
92
|
/* ---------- the doctor checklist ---------- */
|
|
82
93
|
|
|
83
|
-
.check{display:grid;grid-template-columns:auto 1fr;gap:2px
|
|
84
|
-
padding:
|
|
94
|
+
.check{display:grid;grid-template-columns:auto 1fr;gap:2px 12px;align-items:start;
|
|
95
|
+
padding:12px 0;border-top:1px solid var(--line)}
|
|
85
96
|
.check:first-child{border-top:none}
|
|
86
97
|
.checkmark{font:600 13px var(--mono);line-height:1.5}
|
|
87
98
|
.check.fail .checkmark{color:var(--err)}
|
|
@@ -92,15 +103,91 @@
|
|
|
92
103
|
/* ---------- the repo picker ---------- */
|
|
93
104
|
|
|
94
105
|
.repolist{display:grid;gap:1px;max-height:280px;overflow-y:auto;
|
|
95
|
-
border:1px solid var(--line);border-radius:
|
|
96
|
-
.repo
|
|
106
|
+
border:1px solid var(--line);border-radius:var(--r);background:var(--line)}
|
|
107
|
+
/* Scoped to the picker. Bare `.repo` also matched the project label on every inbox row,
|
|
108
|
+
which is where the white box and the stray padding behind MARKETING came from. */
|
|
109
|
+
.repolist .repo{display:flex;align-items:center;justify-content:space-between;gap:12px;
|
|
97
110
|
background:var(--panel);padding:9px 12px}
|
|
98
|
-
.repo.added{background:var(--panel-2)}
|
|
99
|
-
.repo b{font:600 12.5px var(--mono)}
|
|
100
|
-
.repo .meta{margin-left:8px}
|
|
101
|
-
.repo .sub{margin:2px 0 0;font-size:11.5px}
|
|
111
|
+
.repolist .repo.added{background:var(--panel-2)}
|
|
112
|
+
.repolist .repo b{font:600 12.5px var(--mono)}
|
|
113
|
+
.repolist .repo .meta{margin-left:8px}
|
|
114
|
+
.repolist .repo .sub{margin:2px 0 0;font-size:11.5px}
|
|
102
115
|
|
|
103
116
|
/* Which of the two things this is: a new org, or one that already runs roster. */
|
|
104
|
-
.verdict{background:var(--
|
|
117
|
+
.verdict{background:var(--accent-soft);border:1px solid transparent;border-radius:var(--r);
|
|
105
118
|
padding:9px 12px;font-size:12.5px}
|
|
106
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}
|
|
124
|
+
|
|
125
|
+
/* ---------- the numbered to-do list ---------- */
|
|
126
|
+
|
|
127
|
+
.todos{display:grid;gap:10px;margin-bottom:14px}
|
|
128
|
+
.todo{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:16px 18px}
|
|
129
|
+
.todo.done{padding:11px 18px;border-color:color-mix(in srgb,var(--accent) 30%,var(--line))}
|
|
130
|
+
.todohead{display:flex;align-items:center;gap:11px}
|
|
131
|
+
.todohead h3{margin:0;font:600 14.5px var(--sans);flex:1}
|
|
132
|
+
.todon{flex:none;width:24px;height:24px;border-radius:50%;display:grid;place-items:center;
|
|
133
|
+
background:var(--surface-2);color:var(--ink-dim);font:600 11px var(--mono)}
|
|
134
|
+
.todo.done .todon{background:var(--accent-fill);color:var(--accent-ink)}
|
|
135
|
+
.todopill{font:600 11px var(--sans);padding:2px 8px;border-radius:99px;background:var(--surface-2);color:var(--ink-dim)}
|
|
136
|
+
.todo.done .todopill{background:var(--accent-soft);color:var(--accent)}
|
|
137
|
+
.todohint{margin:8px 0 0 35px}
|
|
138
|
+
.todobody{margin:12px 0 0 35px}
|
|
139
|
+
.todobody .row{margin-top:10px}
|
|
140
|
+
.todo.locked{background:transparent;border-style:dashed}
|
|
141
|
+
.todo.locked h3{color:var(--ink-dim)}
|
|
142
|
+
.todo.locked .todopill{background:none;border:1px solid var(--line);color:var(--ink-faint)}
|
|
143
|
+
|
|
144
|
+
/* ---------- hiring on the Staff screen ---------- */
|
|
145
|
+
|
|
146
|
+
.hirepane > h3{margin:0 0 10px;font:600 14.5px var(--sans)}
|
|
147
|
+
.roles{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px;max-width:920px}
|
|
148
|
+
.role{display:grid;gap:5px;align-content:start;text-align:left;cursor:pointer;
|
|
149
|
+
background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:14px 16px;
|
|
150
|
+
color:var(--ink);font:inherit;
|
|
151
|
+
transition:border-color .15s var(--ease),box-shadow .15s var(--ease),transform .15s var(--ease)}
|
|
152
|
+
.role:hover{border-color:var(--accent-dim);box-shadow:var(--shadow-sm);transform:translateY(-1px)}
|
|
153
|
+
.role:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
|
|
154
|
+
.role b{font:600 14px var(--sans);letter-spacing:-.01em}
|
|
155
|
+
.role span{color:var(--ink-dim);font:12.5px/1.5 var(--sans)}
|
|
156
|
+
.roleform{max-width:560px;margin:14px 0 0}
|
|
157
|
+
.roleform textarea{width:100%}
|
|
158
|
+
.hireflow{max-width:920px}
|
|
159
|
+
.hireflowhead{justify-content:space-between;margin:0 0 12px}
|
|
160
|
+
.hireflowhead h2{margin:0;font:600 17px var(--sans);letter-spacing:-.015em}
|
|
161
|
+
.hiresummary{margin:0;font:13.5px/1.55 var(--sans);color:var(--ink)}
|
|
162
|
+
.hiresummary.err{color:var(--err)}
|
|
163
|
+
.hireadvanced{margin-top:12px;padding:12px 14px;border:1px solid var(--line);border-radius:var(--r);max-width:560px}
|
|
164
|
+
.qfield{display:grid;gap:5px;margin:0 0 12px}
|
|
165
|
+
.qfield span{font:600 12.5px var(--sans)}
|
|
166
|
+
.qfield input{background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
|
|
167
|
+
border-radius:var(--r);padding:8px 10px;font:13px var(--sans)}
|
|
168
|
+
.modes{display:inline-flex;gap:2px;padding:3px;margin:0 0 14px;background:var(--surface-2);
|
|
169
|
+
border-radius:var(--r);flex-wrap:wrap}
|
|
170
|
+
.mode{border:0;background:none;color:var(--ink-dim);font:600 12.5px var(--sans);padding:6px 12px;
|
|
171
|
+
border-radius:calc(var(--r) - 2px);cursor:pointer}
|
|
172
|
+
.mode:hover{color:var(--ink)}
|
|
173
|
+
.mode.on{background:var(--bg-elev);color:var(--ink);box-shadow:var(--shadow-sm)}
|
|
174
|
+
.modes + div > .paste{border-top:0;margin-top:0;padding-top:0}
|
|
175
|
+
|
|
176
|
+
/* ---------- a checked paste ---------- */
|
|
177
|
+
.verdict{margin:0 0 10px;font:600 13.5px var(--sans)}
|
|
178
|
+
.verdict.ok{color:var(--accent)}
|
|
179
|
+
.verdict.bad{color:var(--err)}
|
|
180
|
+
.pastepreview{border:1px solid var(--line);border-radius:var(--r);padding:14px 16px;margin-bottom:12px;background:var(--bg)}
|
|
181
|
+
.pastenotes{margin:0 0 12px}
|
|
182
|
+
.pastenotes summary{cursor:pointer;font:600 12.5px var(--sans);color:var(--ink-dim);margin-bottom:6px}
|
|
183
|
+
.pastenotes .md{border-left:3px solid var(--line);padding-left:12px}
|
|
184
|
+
.pastefile .diff{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
185
|
+
.warnline{margin:10px 0 0;padding:8px 11px;border-radius:var(--r);background:color-mix(in srgb,var(--warn) 12%,transparent);color:var(--ink);font-size:13px}
|
|
186
|
+
|
|
187
|
+
/* A single-line text box, matching the question fields. */
|
|
188
|
+
.textfield{width:100%;background:var(--bg-elev);border:1px solid var(--border-strong);color:var(--ink);
|
|
189
|
+
border-radius:var(--r);padding:8px 10px;font:13px var(--sans);outline:none}
|
|
190
|
+
.textfield:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}
|
|
191
|
+
.credform{display:grid;gap:10px}
|
|
192
|
+
.credform .row{margin:0}
|
|
193
|
+
.okline{margin:0 0 8px;color:var(--accent);font:600 13px var(--sans)}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>roster</title>
|
|
7
7
|
<!-- Inline, so a local page with no network still has a tab icon and the console stays clean. -->
|
|
8
|
-
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0
|
|
8
|
+
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='8' fill='%2334c759'/%3E%3C/svg%3E">
|
|
9
9
|
<!-- Stylesheets and modules are served from /assets by `roster portal`. No build step:
|
|
10
10
|
editing one of these files and reloading is the whole edit loop. -->
|
|
11
11
|
<link rel="stylesheet" href="/assets/css/base.css">
|
|
@@ -16,20 +16,35 @@
|
|
|
16
16
|
<link rel="stylesheet" href="/assets/css/graph.css">
|
|
17
17
|
<link rel="stylesheet" href="/assets/css/diff.css">
|
|
18
18
|
<link rel="stylesheet" href="/assets/css/health.css">
|
|
19
|
+
<link rel="stylesheet" href="/assets/css/runs.css">
|
|
19
20
|
<link rel="stylesheet" href="/assets/css/setup.css">
|
|
20
21
|
</head>
|
|
21
22
|
<body>
|
|
22
23
|
<div class="app">
|
|
23
24
|
<aside class="side">
|
|
24
|
-
<div class="brand">roster<small id="orgname">loading…</small></div>
|
|
25
|
+
<div class="brand"><span class="mark"></span>roster<small id="orgname">loading…</small></div>
|
|
25
26
|
<button class="refreshall" id="refreshall" title="Reload everything from disk (r)">
|
|
26
27
|
<span class="sync" data-icon="refresh"></span><span id="loaded">data now</span>
|
|
27
28
|
</button>
|
|
28
29
|
|
|
30
|
+
<!-- Only while something is left to set up; app.js decides. -->
|
|
31
|
+
<button class="nav top" id="setupnav" data-view="setup" hidden>
|
|
32
|
+
<span class="ic" data-icon="task-done"></span> Getting started
|
|
33
|
+
</button>
|
|
29
34
|
<button class="nav top" id="inboxnav" data-view="inbox">
|
|
30
35
|
<span class="ic" data-icon="inbox"></span> Inbox
|
|
31
36
|
<span class="n" id="inboxcount"></span>
|
|
32
37
|
</button>
|
|
38
|
+
<div class="staffviews" id="inboxsub" hidden></div>
|
|
39
|
+
<!-- "Pending work", not "Pull requests": what is behind it is finished work waiting on you,
|
|
40
|
+
and the word for how it is delivered is not the word for what it is. -->
|
|
41
|
+
<button class="nav" id="prsnav" data-view="prs" title="Finished work waiting on you to merge">
|
|
42
|
+
<span class="ic" data-icon="merged"></span> Pending work
|
|
43
|
+
<span class="n" id="prcount"></span>
|
|
44
|
+
</button>
|
|
45
|
+
<button class="nav" id="runsnav" data-view="runs" title="What ran, how it ended, and what it cost">
|
|
46
|
+
<span class="ic" data-icon="play"></span> Runs
|
|
47
|
+
</button>
|
|
33
48
|
<button class="nav" id="orgnav" data-view="org">
|
|
34
49
|
<span class="ic" data-icon="file"></span> Org
|
|
35
50
|
</button>
|
|
@@ -40,7 +55,14 @@
|
|
|
40
55
|
<span class="ic" data-icon="docs"></span> Docs
|
|
41
56
|
</button>
|
|
42
57
|
|
|
43
|
-
|
|
58
|
+
<!-- The heading is the fold. Collapsed, it carries the count, because a hidden list still
|
|
59
|
+
has to say how much it is hiding. -->
|
|
60
|
+
<!-- Named apart from the Staff screen above it: two controls both called "Staff" is how a
|
|
61
|
+
click meant for the screen landed on the fold, for people and for automation alike. -->
|
|
62
|
+
<button class="sect sectfold" id="stafffold" aria-expanded="false" aria-controls="stafflist"
|
|
63
|
+
aria-label="Staff list" title="Show or hide each staff member's own screens">
|
|
64
|
+
<span class="caret" data-icon="chevron"></span>Staff<span class="n" id="staffcount" hidden></span>
|
|
65
|
+
</button>
|
|
44
66
|
<div id="stafflist"></div>
|
|
45
67
|
|
|
46
68
|
<div class="sidefoot">
|
|
@@ -7,6 +7,21 @@ export const getDocs = () => json("/api/docs");
|
|
|
7
7
|
export const getInbox = (force) => json("/api/inbox" + (force ? "?refresh=1" : ""));
|
|
8
8
|
export const getSync = () => json("/api/sync").catch(() => null);
|
|
9
9
|
|
|
10
|
+
/** Every staff member's recent runs and 30-day spend. Online only; offline it says so. */
|
|
11
|
+
export const getRuns = (force) => json("/api/runs" + (force ? "?refresh=1" : ""));
|
|
12
|
+
|
|
13
|
+
/** The repos org.yaml lists. Off disk, so the new-issue form does not wait on the inbox. */
|
|
14
|
+
export const getRepos = () => json("/api/repos");
|
|
15
|
+
|
|
16
|
+
/** What one repo's labels actually are, so they can be offered rather than typed. */
|
|
17
|
+
export const getLabels = (repo) => json("/api/labels?repo=" + encodeURIComponent(repo));
|
|
18
|
+
|
|
19
|
+
/** Full-text across the docs. Titles are not enough: what you want is usually a paragraph. */
|
|
20
|
+
export const searchDocs = (q) => json("/api/docsearch?q=" + encodeURIComponent(q));
|
|
21
|
+
|
|
22
|
+
/** Every file in the org layer that the portal may write, off disk rather than hardcoded. */
|
|
23
|
+
export const getOrgLayer = () => json("/api/orglayer");
|
|
24
|
+
|
|
10
25
|
export const getDoc = (page) =>
|
|
11
26
|
fetch("/api/doc?page=" + encodeURIComponent(page), { cache: "no-store" }).then((r) => r.text());
|
|
12
27
|
|
|
@@ -18,6 +33,10 @@ export const getThread = (repo, number, kind) =>
|
|
|
18
33
|
"/api/thread?repo=" + encodeURIComponent(repo) + "&number=" + number + "&kind=" + kind,
|
|
19
34
|
);
|
|
20
35
|
|
|
36
|
+
/** A pull request's commits and its diff. Asked for on the Files or Commits tab, not before. */
|
|
37
|
+
export const getPr = (repo, number) =>
|
|
38
|
+
json("/api/pr?repo=" + encodeURIComponent(repo) + "&number=" + number);
|
|
39
|
+
|
|
21
40
|
export const diffUrl = (dir, sha, path) =>
|
|
22
41
|
"/api/diff?dir=" + encodeURIComponent(dir) + "&sha=" + encodeURIComponent(sha) +
|
|
23
42
|
(path ? "&path=" + encodeURIComponent(path) : "");
|
|
@@ -31,10 +50,32 @@ export async function post(payload, url = "/api/act") {
|
|
|
31
50
|
body: JSON.stringify(payload),
|
|
32
51
|
});
|
|
33
52
|
const data = await res.json().catch(() => ({}));
|
|
34
|
-
if (!res.ok || data.error)
|
|
53
|
+
if (!res.ok || data.error) {
|
|
54
|
+
// The body travels with the error: a refusal often carries the next step, like a link.
|
|
55
|
+
const err = new Error(data.error || "failed with " + res.status);
|
|
56
|
+
err.data = data;
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
35
59
|
return data;
|
|
36
60
|
}
|
|
37
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Put a file in the repo an issue lives in, and get back the path and the link.
|
|
64
|
+
*
|
|
65
|
+
* Read here rather than posted as a form because everything else on this server speaks JSON,
|
|
66
|
+
* and a file small enough to belong in a repo is small enough to base64.
|
|
67
|
+
*/
|
|
68
|
+
export async function upload(repo, file) {
|
|
69
|
+
const data = await new Promise((ok, no) => {
|
|
70
|
+
const r = new FileReader();
|
|
71
|
+
r.onerror = () => no(new Error("could not read " + file.name));
|
|
72
|
+
// A data URL is "data:<type>;base64,<payload>"; only the payload travels.
|
|
73
|
+
r.onload = () => ok(String(r.result).split(",")[1] ?? "");
|
|
74
|
+
r.readAsDataURL(file);
|
|
75
|
+
});
|
|
76
|
+
return post({ repo, name: file.name, data }, "/api/upload");
|
|
77
|
+
}
|
|
78
|
+
|
|
38
79
|
/* ------------------------------- setup and authoring ------------------------------- */
|
|
39
80
|
|
|
40
81
|
export const getSetup = () => json("/api/setup/status");
|
|
@@ -45,8 +86,21 @@ export const planTenant = (params) =>
|
|
|
45
86
|
export const createTenant = (params) => post(params, "/api/setup/apply");
|
|
46
87
|
|
|
47
88
|
/** The copyable prompt, with every file it refers to carried inside it. */
|
|
48
|
-
export const getBrief = (kind, staff) =>
|
|
49
|
-
json(
|
|
89
|
+
export const getBrief = (kind, staff, example, about) =>
|
|
90
|
+
json(
|
|
91
|
+
"/api/brief?kind=" + encodeURIComponent(kind) +
|
|
92
|
+
(staff ? "&staff=" + encodeURIComponent(staff) : "") +
|
|
93
|
+
(example ? "&example=" + encodeURIComponent(example) : "") +
|
|
94
|
+
(about ? "&about=" + encodeURIComponent(about) : ""),
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
/** The worked charter that matches a staff member, with the business's name in it. */
|
|
98
|
+
export const getCharterExample = (staff) =>
|
|
99
|
+
json("/api/charter-example?staff=" + encodeURIComponent(staff));
|
|
100
|
+
|
|
101
|
+
/** Whether a staff member's App secrets are set and a daily run has succeeded. Online only. */
|
|
102
|
+
export const getStaffProgress = (staff, fresh) =>
|
|
103
|
+
json("/api/staff/progress?staff=" + encodeURIComponent(staff) + (fresh ? "&fresh=1" : ""));
|
|
50
104
|
|
|
51
105
|
/** Parse what came back from the model. Reads only: saving is a second, deliberate step. */
|
|
52
106
|
export const parsePaste = (kind, staff, answer) =>
|
|
@@ -65,10 +119,26 @@ export const getDoctor = (offline) => json("/api/doctor" + (offline ? "?offline=
|
|
|
65
119
|
export const getFix = (offline) => json("/api/fix" + (offline ? "?offline=1" : ""));
|
|
66
120
|
|
|
67
121
|
export const listRepos = (org) => json("/api/setup/repos?org=" + encodeURIComponent(org));
|
|
68
|
-
|
|
122
|
+
/** Commits org.yaml and pushes, as every other write here does. */
|
|
123
|
+
export const addRepo = (name, role, visibility) =>
|
|
124
|
+
post({ name, role, visibility }, "/api/setup/add-repo");
|
|
69
125
|
export const startApp = (staff, scope) => post({ staff, scope }, "/api/setup/app");
|
|
70
126
|
export const appResult = (state) => json("/api/setup/app-result?state=" + encodeURIComponent(state));
|
|
71
127
|
|
|
72
128
|
/** Does this org already run roster. "Create" and "join" are different answers. */
|
|
73
129
|
export const checkOrg = (org) => json("/api/setup/check-org?org=" + encodeURIComponent(org));
|
|
74
130
|
export const joinOrg = (org) => post({ org }, "/api/setup/join");
|
|
131
|
+
|
|
132
|
+
/** The ops repo's Actions access: read it, or ask the server to set it. */
|
|
133
|
+
export const getAccess = () => json("/api/setup/access");
|
|
134
|
+
export const setAccess = () => post({}, "/api/setup/access");
|
|
135
|
+
|
|
136
|
+
/** Where the agent credential goes, and storing it. The value only ever travels in a POST. */
|
|
137
|
+
export const getCredential = () => json("/api/setup/credential");
|
|
138
|
+
export const storeCredential = (value, repoSecrets) =>
|
|
139
|
+
post({ value, repoSecrets }, "/api/setup/credential");
|
|
140
|
+
|
|
141
|
+
/** One daily run, now, and where it has got to. */
|
|
142
|
+
export const startRun = (staff) => post({ staff }, "/api/run/start");
|
|
143
|
+
export const runStatus = (staff, id) =>
|
|
144
|
+
json("/api/run/state?staff=" + encodeURIComponent(staff) + "&id=" + encodeURIComponent(id));
|