@nanocollective/roster 0.1.0-alpha.21 → 0.1.0-alpha.22
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/dist/cli.js
CHANGED
|
@@ -6439,7 +6439,7 @@ async function portalCommand(argv) {
|
|
|
6439
6439
|
const handleSetup = async (url, req, res) => {
|
|
6440
6440
|
const route = url.pathname.slice("/api/setup/".length);
|
|
6441
6441
|
if (route === "status") {
|
|
6442
|
-
json(res, { ...await setupStatus(startedIn), startedIn });
|
|
6442
|
+
json(res, { ...await setupStatus(startedIn), startedIn }, true);
|
|
6443
6443
|
return;
|
|
6444
6444
|
}
|
|
6445
6445
|
if (route === "app") {
|
|
@@ -6820,7 +6820,7 @@ async function portalCommand(argv) {
|
|
|
6820
6820
|
return;
|
|
6821
6821
|
}
|
|
6822
6822
|
if (url.pathname === "/api/doctor") {
|
|
6823
|
-
collect({ ops: ws?.opsDir, offline: url.searchParams.get("offline") === "1" }).then((report3) => json(res, report3 ?? { findings: [], online: false, empty: true })).catch((err) => {
|
|
6823
|
+
collect({ ops: ws?.opsDir, offline: url.searchParams.get("offline") === "1" }).then((report3) => json(res, report3 ?? { findings: [], online: false, empty: true }, true)).catch((err) => {
|
|
6824
6824
|
res.writeHead(200, { "content-type": "application/json" });
|
|
6825
6825
|
res.end(JSON.stringify({ findings: [], error: String(err?.message ?? err) }));
|
|
6826
6826
|
});
|
|
@@ -6901,7 +6901,7 @@ async function portalCommand(argv) {
|
|
|
6901
6901
|
throw new Error(`"${handle}" is not in org.yaml`);
|
|
6902
6902
|
}
|
|
6903
6903
|
const plan3 = buildRetirePlan(w, org, handle, parseYaml);
|
|
6904
|
-
json(res, { action, plan: plan3 });
|
|
6904
|
+
json(res, { action, plan: plan3 }, true);
|
|
6905
6905
|
return;
|
|
6906
6906
|
}
|
|
6907
6907
|
if (!/^[a-z][a-z0-9-]{1,20}$/.test(handle)) {
|
|
@@ -6913,10 +6913,11 @@ async function portalCommand(argv) {
|
|
|
6913
6913
|
const flags = hireFlags(url.searchParams);
|
|
6914
6914
|
const plan2 = buildPlan(w, org, handle, flags, parseYaml);
|
|
6915
6915
|
void readOrgSecret(org.org, plan2.staff.agentSecret).then(
|
|
6916
|
-
(orgSecret) => json(
|
|
6917
|
-
|
|
6918
|
-
plan: { ...plan2, orgSecret, files: [...plan2.files.keys()].sort() }
|
|
6919
|
-
|
|
6916
|
+
(orgSecret) => json(
|
|
6917
|
+
res,
|
|
6918
|
+
{ action, plan: { ...plan2, orgSecret, files: [...plan2.files.keys()].sort() } },
|
|
6919
|
+
true
|
|
6920
|
+
)
|
|
6920
6921
|
);
|
|
6921
6922
|
} catch (err) {
|
|
6922
6923
|
res.writeHead(400, { "content-type": "application/json" });
|
|
@@ -6990,7 +6991,7 @@ async function portalCommand(argv) {
|
|
|
6990
6991
|
return;
|
|
6991
6992
|
}
|
|
6992
6993
|
if (url.pathname === "/api/fix") {
|
|
6993
|
-
gather(w, url.searchParams.get("offline") === "1").then((items) => json(res, { items, text: fixBrief(w, items) })).catch((err) => {
|
|
6994
|
+
gather(w, url.searchParams.get("offline") === "1").then((items) => json(res, { items, text: fixBrief(w, items) }, true)).catch((err) => {
|
|
6994
6995
|
res.writeHead(200, { "content-type": "application/json" });
|
|
6995
6996
|
res.end(JSON.stringify({ items: [], error: String(err?.message ?? err) }));
|
|
6996
6997
|
});
|
|
@@ -7515,12 +7516,13 @@ function refuseWrite(res) {
|
|
|
7515
7516
|
res.writeHead(403, { "content-type": "application/json" });
|
|
7516
7517
|
res.end(JSON.stringify({ error: "write actions need a local POST from the portal" }));
|
|
7517
7518
|
}
|
|
7518
|
-
function json(res, data) {
|
|
7519
|
+
function json(res, data, advice = false) {
|
|
7519
7520
|
res.writeHead(200, {
|
|
7520
7521
|
"content-type": "application/json; charset=utf-8",
|
|
7521
7522
|
"cache-control": "no-store"
|
|
7522
7523
|
});
|
|
7523
|
-
|
|
7524
|
+
const text = JSON.stringify(data);
|
|
7525
|
+
res.end(advice ? withBin(text) : text);
|
|
7524
7526
|
}
|
|
7525
7527
|
function hireFlags(params) {
|
|
7526
7528
|
const num = (k) => params.get(k) ? Number(params.get(k)) : void 0;
|
package/package.json
CHANGED
|
@@ -148,3 +148,13 @@
|
|
|
148
148
|
.mode:hover{color:var(--ink)}
|
|
149
149
|
.mode.on{background:var(--bg-elev);color:var(--ink);box-shadow:var(--shadow-sm)}
|
|
150
150
|
.modes + div > .paste{border-top:0;margin-top:0;padding-top:0}
|
|
151
|
+
|
|
152
|
+
/* ---------- a checked paste ---------- */
|
|
153
|
+
.verdict{margin:0 0 10px;font:600 13.5px var(--sans)}
|
|
154
|
+
.verdict.ok{color:var(--accent)}
|
|
155
|
+
.verdict.bad{color:var(--err)}
|
|
156
|
+
.pastepreview{border:1px solid var(--line);border-radius:var(--r);padding:14px 16px;margin-bottom:12px;background:var(--bg)}
|
|
157
|
+
.pastenotes{margin:0 0 12px}
|
|
158
|
+
.pastenotes summary{cursor:pointer;font:600 12.5px var(--sans);color:var(--ink-dim);margin-bottom:6px}
|
|
159
|
+
.pastenotes .md{border-left:3px solid var(--line);padding-left:12px}
|
|
160
|
+
.pastefile .diff{white-space:pre-wrap;overflow-wrap:anywhere}
|
|
@@ -17,6 +17,10 @@ const BUSINESS_QUESTIONS = [
|
|
|
17
17
|
|
|
18
18
|
const val = (input) => String(input.value ?? "").trim();
|
|
19
19
|
|
|
20
|
+
/* A file still in the shape it shipped in holds the questions, not answers, and pre-filling the
|
|
21
|
+
fields with "Not a segment. The person…" reads as if somebody had already answered them. */
|
|
22
|
+
const isStub = (text) => /This file is a stub|Or answer these by hand/.test(text);
|
|
23
|
+
|
|
20
24
|
/**
|
|
21
25
|
* org/business.md as five questions. With `text`, the answers already in the file fill the
|
|
22
26
|
* fields, so changing one answer does not mean retyping the other four.
|
|
@@ -25,7 +29,7 @@ const val = (input) => String(input.value ?? "").trim();
|
|
|
25
29
|
*/
|
|
26
30
|
export function businessForm(o) {
|
|
27
31
|
const form = el("div");
|
|
28
|
-
const known = sections(o.text ?? "");
|
|
32
|
+
const known = isStub(o.text ?? "") ? new Map() : sections(o.text ?? "");
|
|
29
33
|
const fields = BUSINESS_QUESTIONS.map(([id, label, heading, required]) => {
|
|
30
34
|
const input = id === "line" ? el("input", { type: "text" }) : el("textarea", { className: "pastebox", rows: 3 });
|
|
31
35
|
const had = known.get(heading.toLowerCase());
|
|
@@ -64,7 +68,7 @@ export function businessForm(o) {
|
|
|
64
68
|
/** org/priorities.md as three lines and an out-of-scope list, filled from the file if it has them. */
|
|
65
69
|
export function prioritiesForm(o) {
|
|
66
70
|
const form = el("div");
|
|
67
|
-
const { items, outs } = parsePriorities(o.text ?? "");
|
|
71
|
+
const { items, outs } = isStub(o.text ?? "") ? { items: [], outs: [] } : parsePriorities(o.text ?? "");
|
|
68
72
|
const ranks = [1, 2, 3].map((i) => {
|
|
69
73
|
const input = el("input", { type: "text", placeholder: i === 1 ? "e.g. Tasks can be ticked off and removed" : "" });
|
|
70
74
|
if (items[i - 1]) input.value = items[i - 1];
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Nothing here writes. The panel parses, shows a diff, and waits for a second click. */
|
|
8
8
|
|
|
9
9
|
import { getBrief, parsePaste, saveFile } from "../api.js";
|
|
10
|
+
import { mdlite } from "../md.js";
|
|
10
11
|
import { el, toClipboard } from "../dom.js";
|
|
11
12
|
import { unifiedDiff, diffStat } from "../textdiff.js";
|
|
12
13
|
|
|
@@ -123,23 +124,47 @@ export function paste(opts) {
|
|
|
123
124
|
result.append(note);
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// What the AI said around the file: its caveats and "check these" are worth reading first.
|
|
128
|
+
const notes = String(answer.value ?? "")
|
|
129
|
+
.replace(/<<<ROSTER FILE [^>]+>>>[\s\S]*?<<<ROSTER END>>>/g, "")
|
|
130
|
+
.trim();
|
|
131
|
+
|
|
126
132
|
for (const file of data.files ?? []) {
|
|
127
|
-
if (file.unchanged) continue;
|
|
128
133
|
const card = el("div", { className: "pastefile" });
|
|
129
|
-
const
|
|
134
|
+
const name = file.path.split("/").slice(-2).join("/");
|
|
135
|
+
if (file.unchanged) {
|
|
136
|
+
card.append(el("p", { className: "verdict", textContent: "✓ Same as what's already in " + name + ". Nothing to save." }));
|
|
137
|
+
result.append(card);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
130
140
|
card.append(
|
|
131
|
-
el("
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
141
|
+
el("p", {
|
|
142
|
+
className: "verdict" + (file.writable ? " ok" : " bad"),
|
|
143
|
+
textContent: file.writable
|
|
144
|
+
? "✓ Looks good. Saving replaces " + name + " with this:"
|
|
145
|
+
: "✗ " + file.path + " can't be written from here.",
|
|
135
146
|
}),
|
|
136
147
|
);
|
|
137
|
-
|
|
148
|
+
card.append(el("div", { className: "md doc pastepreview", innerHTML: mdlite(file.text) }));
|
|
149
|
+
|
|
150
|
+
if (notes) {
|
|
151
|
+
const said = el("details", { className: "pastenotes", open: true });
|
|
152
|
+
said.append(el("summary", { textContent: "Your AI also said" }), el("div", { className: "md doc", innerHTML: mdlite(notes) }));
|
|
153
|
+
card.append(said);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const diff = el("pre", { className: "diff", hidden: true });
|
|
138
157
|
diff.textContent = unifiedDiff(file.before, file.text, file.path);
|
|
139
|
-
|
|
158
|
+
const stat = diffStat(file.before, file.text);
|
|
159
|
+
const toggle = el("button", { className: "ghbtn", textContent: `Show changes (+${stat.added} −${stat.removed})` });
|
|
160
|
+
toggle.onclick = () => {
|
|
161
|
+
diff.hidden = !diff.hidden;
|
|
162
|
+
toggle.textContent = (diff.hidden ? "Show changes" : "Hide changes") + ` (+${stat.added} −${stat.removed})`;
|
|
163
|
+
};
|
|
140
164
|
|
|
165
|
+
const row = el("div", { className: "row" });
|
|
141
166
|
if (file.writable) {
|
|
142
|
-
const save = el("button", { className: "btn primary", textContent: "Save
|
|
167
|
+
const save = el("button", { className: "btn primary", textContent: "Save" });
|
|
143
168
|
save.onclick = async () => {
|
|
144
169
|
save.disabled = true;
|
|
145
170
|
save.textContent = "Saving…";
|
|
@@ -150,17 +175,19 @@ export function paste(opts) {
|
|
|
150
175
|
opts.onSaved?.();
|
|
151
176
|
} catch (err) {
|
|
152
177
|
save.disabled = false;
|
|
153
|
-
save.textContent = "Save
|
|
178
|
+
save.textContent = "Save";
|
|
154
179
|
card.append(el("p", { className: "err", textContent: String(err.message || err) }));
|
|
155
180
|
}
|
|
156
181
|
};
|
|
157
|
-
|
|
182
|
+
row.append(save);
|
|
158
183
|
}
|
|
184
|
+
row.append(toggle);
|
|
185
|
+
card.append(row, diff);
|
|
159
186
|
result.append(card);
|
|
160
187
|
}
|
|
161
188
|
|
|
162
189
|
if (!(data.problems ?? []).length && !(data.files ?? []).length) {
|
|
163
|
-
result.append(el("p", { className: "
|
|
190
|
+
result.append(el("p", { className: "verdict bad", textContent: "✗ No file found in that reply." }));
|
|
164
191
|
}
|
|
165
192
|
};
|
|
166
193
|
|