austere-axi 0.3.0 → 0.4.0
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 +41 -9
- package/dist/austere.js +2 -2
- package/dist/catalog.json +1 -1
- package/dist/chrome/chrome.css +1 -1
- package/dist/chrome/chrome.js +4 -4
- package/dist/cli.mjs +765 -296
- package/dist/packs/mermaid/catalog.json +2 -2
- package/dist/packs/mermaid/pack.js +1 -1
- package/dist/packs/shadcn/catalog.json +429 -0
- package/dist/packs/shadcn/pack.js +69 -0
- package/dist/prompt.md +5 -4
- package/dist/spec.json +1 -1
- package/package.json +19 -3
- package/skills/austere-setup/SKILL.md +3 -4
- package/types/catalog.d.ts +82 -0
- package/types/pack-kit.d.ts +46 -0
- package/types/pack.d.ts +104 -0
package/dist/cli.mjs
CHANGED
|
@@ -608,7 +608,7 @@ var init_paths = __esm({
|
|
|
608
608
|
HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
609
609
|
BUNDLED = true;
|
|
610
610
|
ASSET_DIR = BUNDLED ? HERE : path.resolve(HERE, "../../dist");
|
|
611
|
-
VERSION = true ? "0.
|
|
611
|
+
VERSION = true ? "0.4.0" : JSON.parse(fs.readFileSync(path.resolve(HERE, "../../package.json"), "utf8")).version;
|
|
612
612
|
CLI_ENTRY = BUNDLED ? fileURLToPath(import.meta.url) : path.resolve(HERE, "main.js");
|
|
613
613
|
}
|
|
614
614
|
});
|
|
@@ -650,7 +650,10 @@ var init_packs_meta = __esm({
|
|
|
650
650
|
});
|
|
651
651
|
|
|
652
652
|
// src/shared/catalog.js
|
|
653
|
-
function searchCatalog(entries, query, { notesOf = (
|
|
653
|
+
function searchCatalog(entries, query, { notesOf = (
|
|
654
|
+
/** @type {(entry: any) => string[]} */
|
|
655
|
+
(() => [])
|
|
656
|
+
), limit = 8 } = {}) {
|
|
654
657
|
const q2 = String(query || "").trim().toLowerCase();
|
|
655
658
|
const terms = words(q2);
|
|
656
659
|
if (!terms.length) return [];
|
|
@@ -871,12 +874,6 @@ function setHubEnabled(enabled) {
|
|
|
871
874
|
function hubDbFile() {
|
|
872
875
|
return path3.join(stateDir(), "hub.db");
|
|
873
876
|
}
|
|
874
|
-
function hubUnsupported(version2 = process.versions.node) {
|
|
875
|
-
const [a, b] = version2.split(".").map(Number);
|
|
876
|
-
const [x, y] = HUB_MIN_NODE.split(".").map(Number);
|
|
877
|
-
if (a > x || a === x && b >= y) return null;
|
|
878
|
-
return `the hub needs Node ${HUB_MIN_NODE} or newer (for node:sqlite); this is ${version2}`;
|
|
879
|
-
}
|
|
880
877
|
async function loadSqlite() {
|
|
881
878
|
if (DatabaseSync) return DatabaseSync;
|
|
882
879
|
const emit2 = process.emitWarning;
|
|
@@ -901,6 +898,8 @@ function projectOf(file2) {
|
|
|
901
898
|
}
|
|
902
899
|
}
|
|
903
900
|
function itemStatus(entry, pending) {
|
|
901
|
+
if (entry.discarded) return "discarded";
|
|
902
|
+
if (entry.held) return "held";
|
|
904
903
|
if (pending) return "open";
|
|
905
904
|
if (entry.doneAt) return "addressed";
|
|
906
905
|
return "received";
|
|
@@ -912,7 +911,7 @@ function itemText(it2) {
|
|
|
912
911
|
return it2.text ?? it2.note ?? "";
|
|
913
912
|
}
|
|
914
913
|
async function registerWithHub(file2, title, id) {
|
|
915
|
-
if (!hubEnabled()
|
|
914
|
+
if (!hubEnabled()) return;
|
|
916
915
|
try {
|
|
917
916
|
const hub = await Hub.open();
|
|
918
917
|
try {
|
|
@@ -923,11 +922,10 @@ async function registerWithHub(file2, title, id) {
|
|
|
923
922
|
} catch {
|
|
924
923
|
}
|
|
925
924
|
}
|
|
926
|
-
var
|
|
925
|
+
var DatabaseSync, SCHEMA, Hub;
|
|
927
926
|
var init_hub = __esm({
|
|
928
927
|
"src/cli/hub.js"() {
|
|
929
928
|
init_paths();
|
|
930
|
-
HUB_MIN_NODE = "22.13.0";
|
|
931
929
|
DatabaseSync = null;
|
|
932
930
|
SCHEMA = `
|
|
933
931
|
PRAGMA journal_mode = WAL;
|
|
@@ -953,7 +951,8 @@ CREATE TABLE IF NOT EXISTS items (
|
|
|
953
951
|
target TEXT,
|
|
954
952
|
text TEXT,
|
|
955
953
|
status TEXT NOT NULL,
|
|
956
|
-
data TEXT NOT NULL
|
|
954
|
+
data TEXT NOT NULL,
|
|
955
|
+
author TEXT NOT NULL DEFAULT ''
|
|
957
956
|
);
|
|
958
957
|
CREATE INDEX IF NOT EXISTS items_artifact ON items(artifact_id, at);
|
|
959
958
|
CREATE TABLE IF NOT EXISTS replies (
|
|
@@ -971,6 +970,9 @@ CREATE TABLE IF NOT EXISTS replies (
|
|
|
971
970
|
constructor(db) {
|
|
972
971
|
this.db = db;
|
|
973
972
|
db.exec(SCHEMA);
|
|
973
|
+
if (!db.prepare("PRAGMA table_info(items)").all().some((c) => c.name === "author")) {
|
|
974
|
+
db.exec("ALTER TABLE items ADD COLUMN author TEXT NOT NULL DEFAULT ''");
|
|
975
|
+
}
|
|
974
976
|
this.q = {
|
|
975
977
|
upsertArtifact: db.prepare(`
|
|
976
978
|
INSERT INTO artifacts (id, file, project, title, created_at, updated_at, reviewed, status, ended_by)
|
|
@@ -982,9 +984,9 @@ CREATE TABLE IF NOT EXISTS replies (
|
|
|
982
984
|
status = COALESCE(excluded.status, artifacts.status),
|
|
983
985
|
ended_by = CASE WHEN excluded.status IS NULL THEN artifacts.ended_by ELSE excluded.ended_by END`),
|
|
984
986
|
upsertItem: db.prepare(`
|
|
985
|
-
INSERT INTO items (id, artifact_id, entry_id, at, kind, target, text, status, data)
|
|
986
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
987
|
-
ON CONFLICT(id) DO UPDATE SET status = excluded.status, text = excluded.text, data = excluded.data`),
|
|
987
|
+
INSERT INTO items (id, artifact_id, entry_id, at, kind, target, text, status, data, author)
|
|
988
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
989
|
+
ON CONFLICT(id) DO UPDATE SET status = excluded.status, text = excluded.text, data = excluded.data, author = excluded.author`),
|
|
988
990
|
upsertReply: db.prepare(`INSERT INTO replies (id, artifact_id, at, text) VALUES (?, ?, ?, ?) ON CONFLICT(id) DO NOTHING`),
|
|
989
991
|
artifact: db.prepare(`SELECT * FROM artifacts WHERE id = ?`)
|
|
990
992
|
};
|
|
@@ -1011,7 +1013,7 @@ CREATE TABLE IF NOT EXISTS replies (
|
|
|
1011
1013
|
const status = itemStatus(e, pending.has(e.id));
|
|
1012
1014
|
(e.items || []).forEach((it2, i) => {
|
|
1013
1015
|
const id = it2.id || `${e.id}.${i}`;
|
|
1014
|
-
this.q.upsertItem.run(id, s.id, e.id, e.at, it2.kind || "message", it2.target || it2.statement || null, itemText(it2), status, JSON.stringify(it2));
|
|
1016
|
+
this.q.upsertItem.run(id, s.id, e.id, e.at, it2.kind || "message", it2.target || it2.statement || null, itemText(it2), status, JSON.stringify(it2), e.author || "");
|
|
1015
1017
|
});
|
|
1016
1018
|
}
|
|
1017
1019
|
this.db.exec("COMMIT");
|
|
@@ -1030,29 +1032,154 @@ CREATE TABLE IF NOT EXISTS replies (
|
|
|
1030
1032
|
`SELECT a.*,
|
|
1031
1033
|
SUM(CASE WHEN i.status = 'open' THEN 1 ELSE 0 END) AS open,
|
|
1032
1034
|
SUM(CASE WHEN i.status = 'received' THEN 1 ELSE 0 END) AS received,
|
|
1033
|
-
SUM(CASE WHEN i.status = 'addressed' THEN 1 ELSE 0 END) AS addressed
|
|
1035
|
+
SUM(CASE WHEN i.status = 'addressed' THEN 1 ELSE 0 END) AS addressed,
|
|
1036
|
+
SUM(CASE WHEN i.status = 'held' THEN 1 ELSE 0 END) AS held
|
|
1034
1037
|
FROM artifacts a LEFT JOIN items i ON i.artifact_id = a.id
|
|
1035
1038
|
WHERE (? = '' OR a.title LIKE ? ESCAPE '\\' OR a.file LIKE ? ESCAPE '\\' OR a.project LIKE ? ESCAPE '\\')
|
|
1036
1039
|
AND (? = '' OR a.project = ?)
|
|
1037
1040
|
GROUP BY a.id ORDER BY a.updated_at DESC LIMIT ?`
|
|
1038
|
-
).all(q2, like, like, like, project, project, limit).map((r) => ({ ...r, open: r.open || 0, received: r.received || 0, addressed: r.addressed || 0 }));
|
|
1041
|
+
).all(q2, like, like, like, project, project, limit).map((r) => ({ ...r, open: r.open || 0, received: r.received || 0, addressed: r.addressed || 0, held: r.held || 0 }));
|
|
1042
|
+
}
|
|
1043
|
+
/**
|
|
1044
|
+
* Notes (annotations and messages) across all pages, newest first.
|
|
1045
|
+
* `q` matches note text, target and page title. `status`: "unanswered" (held, open and received), "answered", or "" for all.
|
|
1046
|
+
* `author`: a reviewer's name, "owner" for the owner's own notes, or "" for everyone. Discarded notes are left out.
|
|
1047
|
+
* @param {{ q?: string, status?: string, project?: string, author?: string, limit?: number }} [o]
|
|
1048
|
+
*/
|
|
1049
|
+
notes({ q: q2 = "", status = "", project = "", author = "", limit = 200 } = {}) {
|
|
1050
|
+
const anyAuthor = author === "" ? "all" : "one";
|
|
1051
|
+
const who = author === "owner" ? "" : author;
|
|
1052
|
+
const like = `%${q2.replace(/[%_\\]/g, (c) => `\\${c}`)}%`;
|
|
1053
|
+
return this.db.prepare(
|
|
1054
|
+
`SELECT i.id, i.artifact_id, i.at, i.kind, i.target, i.text, i.status, i.author,
|
|
1055
|
+
a.title, a.file, a.project
|
|
1056
|
+
FROM items i JOIN artifacts a ON a.id = i.artifact_id
|
|
1057
|
+
WHERE i.kind IN ('annotation', 'message') AND i.status != 'discarded'
|
|
1058
|
+
AND (? = 'all' OR i.author = ?)
|
|
1059
|
+
AND (? = '' OR i.text LIKE ? ESCAPE '\\' OR i.target LIKE ? ESCAPE '\\' OR a.title LIKE ? ESCAPE '\\')
|
|
1060
|
+
AND (? = '' OR a.project = ?)
|
|
1061
|
+
AND (? = '' OR (? = 'unanswered' AND i.status != 'addressed') OR (? = 'answered' AND i.status = 'addressed'))
|
|
1062
|
+
ORDER BY i.at DESC, i.id LIMIT ?`
|
|
1063
|
+
).all(anyAuthor, who, q2, like, like, like, project, project, status, status, status, limit);
|
|
1064
|
+
}
|
|
1065
|
+
/** Reviewer names with notes in the index (the owner's own notes are not listed). */
|
|
1066
|
+
authors() {
|
|
1067
|
+
return this.db.prepare(`SELECT DISTINCT author FROM items WHERE author != '' AND status != 'discarded' ORDER BY author COLLATE NOCASE`).all().map((r) => String(r.author));
|
|
1039
1068
|
}
|
|
1040
1069
|
stats() {
|
|
1041
1070
|
return this.db.prepare(
|
|
1042
1071
|
`SELECT (SELECT COUNT(*) FROM artifacts) AS artifacts, (SELECT COUNT(DISTINCT project) FROM artifacts) AS projects,
|
|
1043
|
-
(SELECT COUNT(*) FROM items WHERE status
|
|
1072
|
+
(SELECT COUNT(*) FROM items WHERE status IN ('open', 'received', 'held')) AS unanswered`
|
|
1044
1073
|
).get();
|
|
1045
1074
|
}
|
|
1046
1075
|
};
|
|
1047
1076
|
}
|
|
1048
1077
|
});
|
|
1049
1078
|
|
|
1050
|
-
// src/cli/
|
|
1079
|
+
// src/cli/invites.js
|
|
1051
1080
|
import crypto from "node:crypto";
|
|
1052
1081
|
import fs6 from "node:fs";
|
|
1053
1082
|
import path5 from "node:path";
|
|
1083
|
+
function safeEqual(a, b) {
|
|
1084
|
+
const x = crypto.createHash("sha256").update(String(a)).digest();
|
|
1085
|
+
const y = crypto.createHash("sha256").update(String(b)).digest();
|
|
1086
|
+
return crypto.timingSafeEqual(x, y);
|
|
1087
|
+
}
|
|
1088
|
+
function cleanName(raw) {
|
|
1089
|
+
const name = String(raw ?? "").replace(/[\u0000-\u001f\u007f]/g, " ").replace(/\s+/g, " ").trim();
|
|
1090
|
+
if (!name) throw new Error('a reviewer name is required: --as "<name>"');
|
|
1091
|
+
if (name.length > 40) throw new Error("the reviewer name is longer than 40 characters");
|
|
1092
|
+
if (RESERVED.has(name.toLowerCase())) throw new Error(`"${name}" is reserved; pick another name`);
|
|
1093
|
+
return name;
|
|
1094
|
+
}
|
|
1095
|
+
function tokenFromCookie(header, name = COOKIE) {
|
|
1096
|
+
for (const part of String(header || "").split(";")) {
|
|
1097
|
+
const i = part.indexOf("=");
|
|
1098
|
+
if (i > 0 && part.slice(0, i).trim() === name) return part.slice(i + 1).trim();
|
|
1099
|
+
}
|
|
1100
|
+
return null;
|
|
1101
|
+
}
|
|
1102
|
+
function inviteId(inv) {
|
|
1103
|
+
return inv.token.slice(0, 8);
|
|
1104
|
+
}
|
|
1105
|
+
function ownerKey(dir) {
|
|
1106
|
+
const file2 = path5.join(dir, "owner.key");
|
|
1107
|
+
try {
|
|
1108
|
+
const key2 = fs6.readFileSync(file2, "utf8").trim();
|
|
1109
|
+
if (key2.length >= 22) return key2;
|
|
1110
|
+
} catch {
|
|
1111
|
+
}
|
|
1112
|
+
const key = crypto.randomBytes(24).toString("base64url");
|
|
1113
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
1114
|
+
fs6.writeFileSync(file2, key + "\n", { mode: 384 });
|
|
1115
|
+
return key;
|
|
1116
|
+
}
|
|
1117
|
+
var COOKIE, RESERVED, InviteStore, OWNER_COOKIE;
|
|
1118
|
+
var init_invites = __esm({
|
|
1119
|
+
"src/cli/invites.js"() {
|
|
1120
|
+
COOKIE = "austere_rv";
|
|
1121
|
+
RESERVED = /* @__PURE__ */ new Set(["owner", "you", "agent", "me"]);
|
|
1122
|
+
InviteStore = class {
|
|
1123
|
+
/** @param {string} dir state dir */
|
|
1124
|
+
constructor(dir) {
|
|
1125
|
+
this.file = path5.join(dir, "invites.json");
|
|
1126
|
+
this.invites = [];
|
|
1127
|
+
try {
|
|
1128
|
+
const raw = JSON.parse(fs6.readFileSync(this.file, "utf8"));
|
|
1129
|
+
if (Array.isArray(raw.invites)) {
|
|
1130
|
+
this.invites = raw.invites.filter((i) => i && typeof i.token === "string" && i.token.length >= 22 && i.name && i.sessionId);
|
|
1131
|
+
}
|
|
1132
|
+
} catch {
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
save() {
|
|
1136
|
+
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
1137
|
+
fs6.writeFileSync(tmp, JSON.stringify({ invites: this.invites }, null, 2) + "\n", { mode: 384 });
|
|
1138
|
+
fs6.renameSync(tmp, this.file);
|
|
1139
|
+
}
|
|
1140
|
+
/** @returns {Invite} */
|
|
1141
|
+
add({ name, sessionId: sessionId2, file: file2, auto = false }) {
|
|
1142
|
+
const clean = cleanName(name);
|
|
1143
|
+
if (this.invites.some((i) => i.sessionId === sessionId2 && i.name.toLowerCase() === clean.toLowerCase())) {
|
|
1144
|
+
throw new Error(`this page is already shared with "${clean}"; revoke that invite first (share revoke "${clean}")`);
|
|
1145
|
+
}
|
|
1146
|
+
const inv = { token: crypto.randomBytes(24).toString("base64url"), name: clean, sessionId: sessionId2, file: file2, auto: Boolean(auto), createdAt: Date.now() };
|
|
1147
|
+
this.invites.push(inv);
|
|
1148
|
+
this.save();
|
|
1149
|
+
return inv;
|
|
1150
|
+
}
|
|
1151
|
+
/** The invite for a token, or null. Compares every token, so timing does not show a near miss. */
|
|
1152
|
+
find(token) {
|
|
1153
|
+
if (typeof token !== "string" || !token) return null;
|
|
1154
|
+
let hit = null;
|
|
1155
|
+
for (const inv of this.invites) if (safeEqual(inv.token, token) && !hit) hit = inv;
|
|
1156
|
+
return hit;
|
|
1157
|
+
}
|
|
1158
|
+
list() {
|
|
1159
|
+
return [...this.invites];
|
|
1160
|
+
}
|
|
1161
|
+
/** Remove the invites a key names: a full token, its short id, or a reviewer name. Returns what was removed. */
|
|
1162
|
+
revoke(key) {
|
|
1163
|
+
const k = String(key || "").trim();
|
|
1164
|
+
if (!k) return [];
|
|
1165
|
+
const gone = this.invites.filter((i) => safeEqual(i.token, k) || k.length === 8 && inviteId(i) === k || i.name.toLowerCase() === k.toLowerCase());
|
|
1166
|
+
if (gone.length) {
|
|
1167
|
+
this.invites = this.invites.filter((i) => !gone.includes(i));
|
|
1168
|
+
this.save();
|
|
1169
|
+
}
|
|
1170
|
+
return gone;
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
1173
|
+
OWNER_COOKIE = "austere_owner";
|
|
1174
|
+
}
|
|
1175
|
+
});
|
|
1176
|
+
|
|
1177
|
+
// src/cli/sessions.js
|
|
1178
|
+
import crypto2 from "node:crypto";
|
|
1179
|
+
import fs7 from "node:fs";
|
|
1180
|
+
import path6 from "node:path";
|
|
1054
1181
|
function sessionId(file2) {
|
|
1055
|
-
return
|
|
1182
|
+
return crypto2.createHash("sha1").update(file2).digest("hex").slice(0, 12);
|
|
1056
1183
|
}
|
|
1057
1184
|
function newId(prefix) {
|
|
1058
1185
|
return `${prefix}${Date.now().toString(36)}${(++seq).toString(36)}`;
|
|
@@ -1061,7 +1188,7 @@ function hydrate(s) {
|
|
|
1061
1188
|
return {
|
|
1062
1189
|
id: s.id,
|
|
1063
1190
|
file: s.file,
|
|
1064
|
-
title: s.title ||
|
|
1191
|
+
title: s.title || path6.basename(s.file),
|
|
1065
1192
|
createdAt: s.createdAt || Date.now(),
|
|
1066
1193
|
updatedAt: s.updatedAt || Date.now(),
|
|
1067
1194
|
status: s.status || "open",
|
|
@@ -1082,17 +1209,20 @@ var init_sessions = __esm({
|
|
|
1082
1209
|
MAX_TRANSCRIPT = 400;
|
|
1083
1210
|
seq = 0;
|
|
1084
1211
|
SessionStore = class {
|
|
1085
|
-
/**
|
|
1212
|
+
/**
|
|
1213
|
+
* @param {string} dir
|
|
1214
|
+
* @param {{ onFlush?: (session) => void }} [options] onFlush runs after a session is written (the hub mirrors it)
|
|
1215
|
+
*/
|
|
1086
1216
|
constructor(dir, { onFlush } = {}) {
|
|
1087
1217
|
this.onFlush = onFlush || null;
|
|
1088
|
-
this.dir =
|
|
1089
|
-
|
|
1218
|
+
this.dir = path6.join(dir, "sessions");
|
|
1219
|
+
fs7.mkdirSync(this.dir, { recursive: true });
|
|
1090
1220
|
this.sessions = /* @__PURE__ */ new Map();
|
|
1091
1221
|
this.timers = /* @__PURE__ */ new Map();
|
|
1092
|
-
for (const f of
|
|
1222
|
+
for (const f of fs7.readdirSync(this.dir)) {
|
|
1093
1223
|
if (!f.endsWith(".json")) continue;
|
|
1094
1224
|
try {
|
|
1095
|
-
const s = JSON.parse(
|
|
1225
|
+
const s = JSON.parse(fs7.readFileSync(path6.join(this.dir, f), "utf8"));
|
|
1096
1226
|
if (s && s.id && s.file) this.sessions.set(s.id, hydrate(s));
|
|
1097
1227
|
} catch {
|
|
1098
1228
|
}
|
|
@@ -1139,11 +1269,11 @@ var init_sessions = __esm({
|
|
|
1139
1269
|
pending: s.pending,
|
|
1140
1270
|
transcript: s.transcript.slice(-MAX_TRANSCRIPT)
|
|
1141
1271
|
};
|
|
1142
|
-
const file2 =
|
|
1272
|
+
const file2 = path6.join(this.dir, `${s.id}.json`);
|
|
1143
1273
|
const tmp = `${file2}.${process.pid}.tmp`;
|
|
1144
1274
|
try {
|
|
1145
|
-
|
|
1146
|
-
|
|
1275
|
+
fs7.writeFileSync(tmp, JSON.stringify(data));
|
|
1276
|
+
fs7.renameSync(tmp, file2);
|
|
1147
1277
|
} catch {
|
|
1148
1278
|
}
|
|
1149
1279
|
if (this.onFlush) {
|
|
@@ -1157,14 +1287,46 @@ var init_sessions = __esm({
|
|
|
1157
1287
|
for (const s of this.sessions.values()) if (this.timers.has(s.id)) this.flush(s);
|
|
1158
1288
|
}
|
|
1159
1289
|
// ── conversation ──
|
|
1160
|
-
/**
|
|
1161
|
-
|
|
1290
|
+
/**
|
|
1291
|
+
* Reviewer sent a batch of items. Returns the transcript entry.
|
|
1292
|
+
* `author` is a team reviewer's name (empty: the owner). `hold` keeps the entry out of
|
|
1293
|
+
* `pending` until the owner releases it.
|
|
1294
|
+
*/
|
|
1295
|
+
addUserBatch(s, items, { end = false, author = "", hold = false } = {}) {
|
|
1162
1296
|
const entry = { id: newId("u"), role: "user", at: Date.now(), items, end: end || void 0, seenAt: null, doneAt: null };
|
|
1297
|
+
if (author) entry.author = author;
|
|
1298
|
+
if (hold) entry.held = true;
|
|
1163
1299
|
s.transcript.push(entry);
|
|
1164
|
-
s.pending.push(entry.id);
|
|
1300
|
+
if (!hold) s.pending.push(entry.id);
|
|
1165
1301
|
this.touch(s);
|
|
1166
1302
|
return entry;
|
|
1167
1303
|
}
|
|
1304
|
+
/** Held entries, oldest first. */
|
|
1305
|
+
heldEntries(s) {
|
|
1306
|
+
return s.transcript.filter((e) => e.role === "user" && e.held);
|
|
1307
|
+
}
|
|
1308
|
+
/** The owner sends held notes on to the agent (`ids` omitted: all of them). Returns the entries released. */
|
|
1309
|
+
release(s, ids) {
|
|
1310
|
+
const want = ids ? new Set(ids) : null;
|
|
1311
|
+
const out = this.heldEntries(s).filter((e) => !want || want.has(e.id));
|
|
1312
|
+
for (const e of out) {
|
|
1313
|
+
e.held = false;
|
|
1314
|
+
s.pending.push(e.id);
|
|
1315
|
+
}
|
|
1316
|
+
if (out.length) this.touch(s);
|
|
1317
|
+
return out;
|
|
1318
|
+
}
|
|
1319
|
+
/** The owner drops held notes. They stay on record, marked discarded, and never reach the agent. */
|
|
1320
|
+
discard(s, ids) {
|
|
1321
|
+
const want = ids ? new Set(ids) : null;
|
|
1322
|
+
const out = this.heldEntries(s).filter((e) => !want || want.has(e.id));
|
|
1323
|
+
for (const e of out) {
|
|
1324
|
+
e.held = false;
|
|
1325
|
+
e.discarded = true;
|
|
1326
|
+
}
|
|
1327
|
+
if (out.length) this.touch(s);
|
|
1328
|
+
return out;
|
|
1329
|
+
}
|
|
1168
1330
|
addAgentReply(s, text) {
|
|
1169
1331
|
const now = Date.now();
|
|
1170
1332
|
for (const e of s.transcript) if (e.role === "user" && e.seenAt && !e.doneAt) e.doneAt = now;
|
|
@@ -1182,10 +1344,13 @@ var init_sessions = __esm({
|
|
|
1182
1344
|
*/
|
|
1183
1345
|
feedback(s, { all = false, peek = false } = {}) {
|
|
1184
1346
|
const pending = new Set(s.pending);
|
|
1185
|
-
const picked = s.transcript.filter(
|
|
1347
|
+
const picked = s.transcript.filter(
|
|
1348
|
+
(e) => e.role === "user" && !e.held && !e.discarded && (e.items || []).length && (all || pending.has(e.id) || !e.doneAt)
|
|
1349
|
+
);
|
|
1186
1350
|
const entries = picked.map((e) => ({
|
|
1187
1351
|
id: e.id,
|
|
1188
1352
|
at: e.at,
|
|
1353
|
+
author: e.author || void 0,
|
|
1189
1354
|
items: e.items,
|
|
1190
1355
|
status: pending.has(e.id) ? "open" : e.doneAt ? "addressed" : "received"
|
|
1191
1356
|
}));
|
|
@@ -1223,8 +1388,8 @@ var init_sessions = __esm({
|
|
|
1223
1388
|
});
|
|
1224
1389
|
|
|
1225
1390
|
// src/cli/export.js
|
|
1226
|
-
import
|
|
1227
|
-
import
|
|
1391
|
+
import fs8 from "node:fs";
|
|
1392
|
+
import path7 from "node:path";
|
|
1228
1393
|
function inlineScript(code) {
|
|
1229
1394
|
return `<script>${code.replace(/<\/script/gi, "<\\/script")}</script>`;
|
|
1230
1395
|
}
|
|
@@ -1234,12 +1399,12 @@ function exportHtml(html) {
|
|
|
1234
1399
|
if (art.program == null) throw new Error('no <script type="text/openui"> block');
|
|
1235
1400
|
let tags = "";
|
|
1236
1401
|
if (/\bMermaid\s*\(/.test(art.program)) {
|
|
1237
|
-
tags += inlineScript(
|
|
1402
|
+
tags += inlineScript(fs8.readFileSync(assetPath("mermaid.min.js"), "utf8")) + "\n";
|
|
1238
1403
|
}
|
|
1239
|
-
tags += inlineScript(
|
|
1240
|
-
for (const p of implicitPacks(art.program)) tags += "\n" + inlineScript(
|
|
1404
|
+
tags += inlineScript(fs8.readFileSync(assetPath("austere.js"), "utf8"));
|
|
1405
|
+
for (const p of implicitPacks(art.program)) tags += "\n" + inlineScript(fs8.readFileSync(path7.join(p.dir, "pack.js"), "utf8"));
|
|
1241
1406
|
const packs = pagePacks(html);
|
|
1242
|
-
for (const p of packs.installed) tags += "\n" + inlineScript(
|
|
1407
|
+
for (const p of packs.installed) tags += "\n" + inlineScript(fs8.readFileSync(path7.join(p.dir, "pack.js"), "utf8"));
|
|
1243
1408
|
for (const p of [...packs.missing, ...packs.mismatched]) {
|
|
1244
1409
|
notices.push(`Pack ${p.name}@${p.version} is not installed here, so the exported page loads it from the CDN (needs network).`);
|
|
1245
1410
|
}
|
|
@@ -1267,10 +1432,10 @@ __export(install_exports, {
|
|
|
1267
1432
|
removePack: () => removePack,
|
|
1268
1433
|
untar: () => untar
|
|
1269
1434
|
});
|
|
1270
|
-
import
|
|
1271
|
-
import
|
|
1435
|
+
import fs9 from "node:fs";
|
|
1436
|
+
import path8 from "node:path";
|
|
1272
1437
|
import zlib from "node:zlib";
|
|
1273
|
-
import
|
|
1438
|
+
import crypto3 from "node:crypto";
|
|
1274
1439
|
function registryUrl() {
|
|
1275
1440
|
const r = process.env.AUSTERE_AXI_NPM_REGISTRY || process.env.npm_config_registry || "https://registry.npmjs.org/";
|
|
1276
1441
|
return r.endsWith("/") ? r : r + "/";
|
|
@@ -1279,7 +1444,7 @@ function looksLikePath(spec) {
|
|
|
1279
1444
|
return /^(\.{1,2}([\\/]|$)|[\\/]|~[\\/]|[A-Za-z]:[\\/])/.test(spec) || /\.t(ar\.)?gz$/i.test(spec) || spec.includes("\\") || spec.includes("/") && !spec.startsWith("@");
|
|
1280
1445
|
}
|
|
1281
1446
|
function sri(alg, buf) {
|
|
1282
|
-
return `${alg}-${
|
|
1447
|
+
return `${alg}-${crypto3.createHash(alg).update(buf).digest("base64")}`;
|
|
1283
1448
|
}
|
|
1284
1449
|
function untar(buf) {
|
|
1285
1450
|
const files = /* @__PURE__ */ new Map();
|
|
@@ -1350,13 +1515,11 @@ async function fetchBuffer(url2, accept) {
|
|
|
1350
1515
|
try {
|
|
1351
1516
|
res = await fetch(url2, { headers: accept ? { accept } : {}, redirect: "follow" });
|
|
1352
1517
|
} catch (err) {
|
|
1353
|
-
const e = new Error(`could not reach ${url2} (${err.cause?.code || err.message})`);
|
|
1354
|
-
e.network = true;
|
|
1518
|
+
const e = Object.assign(new Error(`could not reach ${url2} (${err.cause?.code || err.message})`), { network: true });
|
|
1355
1519
|
throw e;
|
|
1356
1520
|
}
|
|
1357
1521
|
if (!res.ok) {
|
|
1358
|
-
const e = new Error(`${url2}: HTTP ${res.status}`);
|
|
1359
|
-
e.status = res.status;
|
|
1522
|
+
const e = Object.assign(new Error(`${url2}: HTTP ${res.status}`), { status: res.status });
|
|
1360
1523
|
throw e;
|
|
1361
1524
|
}
|
|
1362
1525
|
const len = Number(res.headers.get("content-length") || 0);
|
|
@@ -1386,7 +1549,7 @@ async function fromRegistry(spec) {
|
|
|
1386
1549
|
if (!["sha512", "sha384", "sha256"].includes(alg) || sri(alg, tgz) !== dist.integrity) {
|
|
1387
1550
|
throw new Error(`${name}@${version2}: the tarball does not match the registry's integrity hash`);
|
|
1388
1551
|
}
|
|
1389
|
-
} else if (dist.shasum &&
|
|
1552
|
+
} else if (dist.shasum && crypto3.createHash("sha1").update(tgz).digest("hex") !== dist.shasum) {
|
|
1390
1553
|
throw new Error(`${name}@${version2}: the tarball does not match the registry's shasum`);
|
|
1391
1554
|
}
|
|
1392
1555
|
return { ...readTarball(tgz, `${name}@${version2}`), expect: { name, version: version2 }, source: "npm", resolved: dist.tarball };
|
|
@@ -1406,15 +1569,15 @@ function readTarball(tgz, label) {
|
|
|
1406
1569
|
return { packJs, catalog, packageJson: files.get("package.json") || null };
|
|
1407
1570
|
}
|
|
1408
1571
|
function fromTarball(file2) {
|
|
1409
|
-
const abs =
|
|
1410
|
-
if (!
|
|
1411
|
-
return { ...readTarball(
|
|
1572
|
+
const abs = path8.resolve(file2.replace(/^~(?=[\\/])/, process.env.HOME || ""));
|
|
1573
|
+
if (!fs9.existsSync(abs)) throw new Error(`no such file: ${abs}`);
|
|
1574
|
+
return { ...readTarball(fs9.readFileSync(abs), path8.basename(abs)), source: "tarball", resolved: abs };
|
|
1412
1575
|
}
|
|
1413
1576
|
function fromFolder(dir) {
|
|
1414
|
-
const abs =
|
|
1415
|
-
if (!
|
|
1416
|
-
if (
|
|
1417
|
-
const read = (rel) =>
|
|
1577
|
+
const abs = path8.resolve(dir.replace(/^~(?=[\\/])/, process.env.HOME || ""));
|
|
1578
|
+
if (!fs9.existsSync(abs)) throw new Error(`no such folder: ${abs}`);
|
|
1579
|
+
if (fs9.statSync(abs).isFile()) return fromTarball(abs);
|
|
1580
|
+
const read = (rel) => fs9.existsSync(path8.join(abs, rel)) ? fs9.readFileSync(path8.join(abs, rel)) : null;
|
|
1418
1581
|
const packJs = read("dist/pack.js");
|
|
1419
1582
|
const catalog = read("dist/catalog.json");
|
|
1420
1583
|
if (!packJs || !catalog) throw new Error(`${abs} has no dist/pack.js and dist/catalog.json: run \`austere-axi components build ${dir}\` first`);
|
|
@@ -1444,21 +1607,23 @@ async function addPack(spec, { trust = false } = {}) {
|
|
|
1444
1607
|
if (!local) {
|
|
1445
1608
|
const { name } = spec.startsWith("@") || spec.includes("@") ? splitSpec(spec) : { name: spec };
|
|
1446
1609
|
if (!isFirstParty(name) && !trust) {
|
|
1447
|
-
|
|
1448
|
-
|
|
1610
|
+
throw Object.assign(
|
|
1611
|
+
new Error(
|
|
1612
|
+
`${name} is a third-party pack: its code would run inside your review pages, where it can read the page and add items to the feedback queue. Look at its source first (npm view ${name} repository), then rerun with --trust.`
|
|
1613
|
+
),
|
|
1614
|
+
{ code: "untrusted" }
|
|
1449
1615
|
);
|
|
1450
|
-
e.code = "untrusted";
|
|
1451
|
-
throw e;
|
|
1452
1616
|
}
|
|
1453
1617
|
}
|
|
1454
1618
|
const got = local ? fromFolder(spec) : await fromRegistry(spec);
|
|
1455
1619
|
if (got.source === "tarball" && !trust) {
|
|
1456
|
-
const peek = parseCatalog(got.catalog,
|
|
1457
|
-
|
|
1458
|
-
|
|
1620
|
+
const peek = parseCatalog(got.catalog, path8.basename(got.resolved));
|
|
1621
|
+
throw Object.assign(
|
|
1622
|
+
new Error(
|
|
1623
|
+
`${path8.basename(got.resolved)} (${peek.pack}) comes from a file, not the registry: its code would run inside your review pages. Rerun with --trust once you have looked at it.`
|
|
1624
|
+
),
|
|
1625
|
+
{ code: "untrusted" }
|
|
1459
1626
|
);
|
|
1460
|
-
e.code = "untrusted";
|
|
1461
|
-
throw e;
|
|
1462
1627
|
}
|
|
1463
1628
|
const label = got.expect ? `${got.expect.name}@${got.expect.version}` : spec;
|
|
1464
1629
|
const cat = parseCatalog(got.catalog, label);
|
|
@@ -1480,14 +1645,14 @@ async function addPack(spec, { trust = false } = {}) {
|
|
|
1480
1645
|
}
|
|
1481
1646
|
const lock = readLock();
|
|
1482
1647
|
const previous = lock.packs[cat.pack];
|
|
1483
|
-
const dir =
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
if (got.packageJson)
|
|
1648
|
+
const dir = path8.join(packsDir(), packDirName(cat.pack, cat.version));
|
|
1649
|
+
fs9.rmSync(dir, { recursive: true, force: true });
|
|
1650
|
+
fs9.mkdirSync(dir, { recursive: true });
|
|
1651
|
+
fs9.writeFileSync(path8.join(dir, "pack.js"), got.packJs);
|
|
1652
|
+
fs9.writeFileSync(path8.join(dir, "catalog.json"), got.catalog);
|
|
1653
|
+
if (got.packageJson) fs9.writeFileSync(path8.join(dir, "package.json"), got.packageJson);
|
|
1489
1654
|
if (previous && previous.version !== cat.version) {
|
|
1490
|
-
|
|
1655
|
+
fs9.rmSync(path8.join(packsDir(), packDirName(cat.pack, previous.version)), { recursive: true, force: true });
|
|
1491
1656
|
}
|
|
1492
1657
|
lock.packs[cat.pack] = {
|
|
1493
1658
|
version: cat.version,
|
|
@@ -1507,7 +1672,7 @@ function removePack(name) {
|
|
|
1507
1672
|
if (!entry) return null;
|
|
1508
1673
|
delete lock.packs[name];
|
|
1509
1674
|
writeLock(lock);
|
|
1510
|
-
|
|
1675
|
+
fs9.rmSync(path8.join(packsDir(), packDirName(name, entry.version)), { recursive: true, force: true });
|
|
1511
1676
|
return { name, version: entry.version };
|
|
1512
1677
|
}
|
|
1513
1678
|
var MAX_TARBALL, MAX_PACK_JS;
|
|
@@ -1527,8 +1692,8 @@ __export(pack_dev_exports, {
|
|
|
1527
1692
|
buildPackDir: () => buildPackDir,
|
|
1528
1693
|
newPack: () => newPack
|
|
1529
1694
|
});
|
|
1530
|
-
import
|
|
1531
|
-
import
|
|
1695
|
+
import fs10 from "node:fs";
|
|
1696
|
+
import path9 from "node:path";
|
|
1532
1697
|
import { createRequire } from "node:module";
|
|
1533
1698
|
import { pathToFileURL } from "node:url";
|
|
1534
1699
|
function packageJson(name) {
|
|
@@ -1553,7 +1718,7 @@ Components for [austere-axi](https://github.com/MandalorianBatman/austere-axi) r
|
|
|
1553
1718
|
npm install
|
|
1554
1719
|
npm run build # dist/pack.js + dist/catalog.json
|
|
1555
1720
|
npx austere-axi components add . # use it on this machine
|
|
1556
|
-
npx austere-axi components show
|
|
1721
|
+
npx austere-axi components show Milestones
|
|
1557
1722
|
\`\`\`
|
|
1558
1723
|
|
|
1559
1724
|
A page that uses it must declare it: \`austere-axi check --fix page.html\` writes the
|
|
@@ -1564,10 +1729,10 @@ Publish with \`npm publish\` after \`npm run build\`. Others install it with
|
|
|
1564
1729
|
`;
|
|
1565
1730
|
}
|
|
1566
1731
|
function newPack(dir, { name } = {}) {
|
|
1567
|
-
if (!dir) throw new Error("new needs a folder: austere-axi components new austere-pack-
|
|
1568
|
-
const abs =
|
|
1569
|
-
if (
|
|
1570
|
-
const pkgName = name ||
|
|
1732
|
+
if (!dir) throw new Error("new needs a folder: austere-axi components new austere-pack-milestones");
|
|
1733
|
+
const abs = path9.resolve(dir);
|
|
1734
|
+
if (fs10.existsSync(abs) && fs10.readdirSync(abs).length) throw new Error(`${abs} is not empty`);
|
|
1735
|
+
const pkgName = name || path9.basename(abs).toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
|
|
1571
1736
|
if (!isValidPackName(pkgName)) throw new Error(`${JSON.stringify(pkgName)} is not an npm package name; pass --name`);
|
|
1572
1737
|
const files = {
|
|
1573
1738
|
"package.json": JSON.stringify(packageJson(pkgName), null, 2) + "\n",
|
|
@@ -1576,13 +1741,13 @@ function newPack(dir, { name } = {}) {
|
|
|
1576
1741
|
...TEMPLATE
|
|
1577
1742
|
};
|
|
1578
1743
|
for (const [rel, text] of Object.entries(files)) {
|
|
1579
|
-
|
|
1580
|
-
|
|
1744
|
+
fs10.mkdirSync(path9.dirname(path9.join(abs, rel)), { recursive: true });
|
|
1745
|
+
fs10.writeFileSync(path9.join(abs, rel), text);
|
|
1581
1746
|
}
|
|
1582
1747
|
return { dir: abs, name: pkgName, files: Object.keys(files) };
|
|
1583
1748
|
}
|
|
1584
1749
|
async function findEsbuild(dir) {
|
|
1585
|
-
for (const base of [
|
|
1750
|
+
for (const base of [path9.join(dir, "package.json"), path9.join(process.cwd(), "package.json"), import.meta.url]) {
|
|
1586
1751
|
try {
|
|
1587
1752
|
const resolved = createRequire(base).resolve("esbuild");
|
|
1588
1753
|
return await import(pathToFileURL(resolved).href);
|
|
@@ -1592,8 +1757,8 @@ async function findEsbuild(dir) {
|
|
|
1592
1757
|
return null;
|
|
1593
1758
|
}
|
|
1594
1759
|
async function buildPackDir(dir = ".") {
|
|
1595
|
-
const abs =
|
|
1596
|
-
if (!
|
|
1760
|
+
const abs = path9.resolve(dir);
|
|
1761
|
+
if (!fs10.existsSync(path9.join(abs, "package.json"))) throw new Error(`${abs} has no package.json (\`austere-axi components new <dir>\` makes a pack)`);
|
|
1597
1762
|
const esbuild = await findEsbuild(abs);
|
|
1598
1763
|
if (!esbuild) throw new Error(`components build needs esbuild: run \`npm install -D esbuild\` in ${abs}`);
|
|
1599
1764
|
process.env.OPENUI_TELEMETRY_DISABLED ||= "1";
|
|
@@ -1620,12 +1785,12 @@ var init_pack_dev = __esm({
|
|
|
1620
1785
|
init_packs_meta();
|
|
1621
1786
|
TEMPLATE = {
|
|
1622
1787
|
"src/index.js": `// The pack: every component it adds, plus CSS the runtime puts on the page once.
|
|
1623
|
-
import
|
|
1788
|
+
import Milestones from "./Milestones.jsx";
|
|
1624
1789
|
import css from "./style.css";
|
|
1625
1790
|
|
|
1626
|
-
export default { components: [
|
|
1791
|
+
export default { components: [Milestones], css };
|
|
1627
1792
|
`,
|
|
1628
|
-
"src/
|
|
1793
|
+
"src/Milestones.jsx": `// One component per file. austereComponent is the same contract the core
|
|
1629
1794
|
// components follow: PascalCase name, zod props in positional order (append
|
|
1630
1795
|
// only once published), a one-line description, an example program.
|
|
1631
1796
|
// React, zod and "austere-axi/pack" come from the page's runtime; do not
|
|
@@ -1646,10 +1811,10 @@ function parseEvents(lines) {
|
|
|
1646
1811
|
});
|
|
1647
1812
|
}
|
|
1648
1813
|
|
|
1649
|
-
function
|
|
1814
|
+
function MilestonesView({ props }) {
|
|
1650
1815
|
const events = parseEvents(props.events);
|
|
1651
1816
|
return (
|
|
1652
|
-
<ol className="pack-
|
|
1817
|
+
<ol className="pack-milestones">
|
|
1653
1818
|
{events.map((e, i) => (
|
|
1654
1819
|
// data-austere-part makes each event its own annotation target.
|
|
1655
1820
|
<li key={i} data-austere-part={e.what || e.when}>
|
|
@@ -1663,20 +1828,20 @@ function TimelineView({ props }) {
|
|
|
1663
1828
|
}
|
|
1664
1829
|
|
|
1665
1830
|
export default austereComponent({
|
|
1666
|
-
name: "
|
|
1831
|
+
name: "Milestones",
|
|
1667
1832
|
// A core group (Layout Content Data Charts Forms Buttons) or a category of your own.
|
|
1668
1833
|
group: "Content",
|
|
1669
1834
|
props: z.object({
|
|
1670
1835
|
events: z.array(z.string()),
|
|
1671
1836
|
}),
|
|
1672
|
-
description: 'Vertical
|
|
1673
|
-
renderer:
|
|
1674
|
-
example: \`launch =
|
|
1837
|
+
description: 'Vertical milestones. events are "when | what | detail" strings (detail optional), oldest first. A click reaches the agent as the event.',
|
|
1838
|
+
renderer: MilestonesView,
|
|
1839
|
+
example: \`launch = Milestones(["Mon | Freeze | branch cut", "Wed | Canary | 5% of traffic", "Fri | GA"])\`,
|
|
1675
1840
|
});
|
|
1676
1841
|
`,
|
|
1677
1842
|
"src/style.css": `/* Use the page's theme variables (--austere-*, --openui-*) so the pack fits the
|
|
1678
1843
|
dark theme, and prefix every class with the pack's name. */
|
|
1679
|
-
.pack-
|
|
1844
|
+
.pack-milestones {
|
|
1680
1845
|
list-style: none;
|
|
1681
1846
|
margin: 0;
|
|
1682
1847
|
padding: 0 0 0 18px;
|
|
@@ -1686,13 +1851,13 @@ export default austereComponent({
|
|
|
1686
1851
|
font-size: 14px;
|
|
1687
1852
|
min-width: 0;
|
|
1688
1853
|
}
|
|
1689
|
-
.pack-
|
|
1854
|
+
.pack-milestones li {
|
|
1690
1855
|
position: relative;
|
|
1691
1856
|
display: grid;
|
|
1692
1857
|
gap: 2px;
|
|
1693
1858
|
overflow-wrap: anywhere;
|
|
1694
1859
|
}
|
|
1695
|
-
.pack-
|
|
1860
|
+
.pack-milestones li::before {
|
|
1696
1861
|
content: "";
|
|
1697
1862
|
position: absolute;
|
|
1698
1863
|
left: -24px;
|
|
@@ -1702,11 +1867,11 @@ export default austereComponent({
|
|
|
1702
1867
|
border-radius: 50%;
|
|
1703
1868
|
background: var(--austere-accent);
|
|
1704
1869
|
}
|
|
1705
|
-
.pack-
|
|
1870
|
+
.pack-milestones time {
|
|
1706
1871
|
font-size: 12px;
|
|
1707
1872
|
color: var(--austere-muted);
|
|
1708
1873
|
}
|
|
1709
|
-
.pack-
|
|
1874
|
+
.pack-milestones span {
|
|
1710
1875
|
color: var(--austere-muted);
|
|
1711
1876
|
}
|
|
1712
1877
|
`
|
|
@@ -1716,7 +1881,7 @@ export default austereComponent({
|
|
|
1716
1881
|
|
|
1717
1882
|
// src/cli/net.js
|
|
1718
1883
|
import { execFile } from "node:child_process";
|
|
1719
|
-
import
|
|
1884
|
+
import fs11 from "node:fs";
|
|
1720
1885
|
function run(bin, args) {
|
|
1721
1886
|
return new Promise((resolve) => {
|
|
1722
1887
|
execFile(bin, args, { timeout: 2500, windowsHide: true }, (err, stdout) => resolve(err ? null : stdout));
|
|
@@ -1727,7 +1892,7 @@ async function detectTailscale({ fresh = false } = {}) {
|
|
|
1727
1892
|
if (!fresh && Date.now() - cache.at < 3e4) return cache.value;
|
|
1728
1893
|
let value = null;
|
|
1729
1894
|
for (const bin of CANDIDATES) {
|
|
1730
|
-
if (bin.startsWith("/") && !
|
|
1895
|
+
if (bin.startsWith("/") && !fs11.existsSync(bin)) continue;
|
|
1731
1896
|
const out = await run(bin, ["status", "--json"]);
|
|
1732
1897
|
if (!out) continue;
|
|
1733
1898
|
try {
|
|
@@ -1775,7 +1940,7 @@ var init_net = __esm({
|
|
|
1775
1940
|
|
|
1776
1941
|
// src/cli/hub-page.js
|
|
1777
1942
|
import os2 from "node:os";
|
|
1778
|
-
import
|
|
1943
|
+
import path10 from "node:path";
|
|
1779
1944
|
function day(ms) {
|
|
1780
1945
|
const d = new Date(ms);
|
|
1781
1946
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -1790,7 +1955,7 @@ function time3(ms) {
|
|
|
1790
1955
|
}
|
|
1791
1956
|
function tilde(p) {
|
|
1792
1957
|
const home = os2.homedir();
|
|
1793
|
-
return p === home || p.startsWith(home +
|
|
1958
|
+
return p === home || p.startsWith(home + path10.sep) ? "~" + p.slice(home.length) : p;
|
|
1794
1959
|
}
|
|
1795
1960
|
function counts(a) {
|
|
1796
1961
|
const waiting = a.open + a.received;
|
|
@@ -1824,13 +1989,13 @@ function hubPage(rows, { version: version2, q: q2, project, stats }) {
|
|
|
1824
1989
|
const body = [...days.entries()].map(
|
|
1825
1990
|
([d, items]) => `<h3>${escapeHtml(d)}</h3><ul>${items.map(
|
|
1826
1991
|
(a) => `<li><a href="/hub/open/${a.id}" title="${escapeHtml(a.file)}">
|
|
1827
|
-
<strong>${escapeHtml(a.title ||
|
|
1828
|
-
<span class="meta">${escapeHtml(
|
|
1992
|
+
<strong>${escapeHtml(a.title || path10.basename(a.file))}</strong>
|
|
1993
|
+
<span class="meta">${escapeHtml(path10.relative(proj, a.file) || path10.basename(a.file))} \xB7 ${time3(a.updated_at)} \xB7 ${state(a)}${counts(a) ? ` \xB7 ${counts(a)}` : ""}</span>
|
|
1829
1994
|
</a></li>`
|
|
1830
1995
|
).join("")}</ul>`
|
|
1831
1996
|
).join("");
|
|
1832
1997
|
const unanswered = list.reduce((n, a) => n + a.open + a.received, 0);
|
|
1833
|
-
return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(
|
|
1998
|
+
return `<section><h2><a href="/?project=${encodeURIComponent(proj)}">${escapeHtml(path10.basename(proj) || proj)}</a> <small title="${escapeHtml(proj)}">${escapeHtml(
|
|
1834
1999
|
shortPath(proj)
|
|
1835
2000
|
)}</small>${unanswered ? ` <b class="open">${unanswered} unanswered</b>` : ""}</h2>${body}</section>`;
|
|
1836
2001
|
}).join("");
|
|
@@ -1839,7 +2004,42 @@ function hubPage(rows, { version: version2, q: q2, project, stats }) {
|
|
|
1839
2004
|
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
1840
2005
|
<title>austere-axi hub</title><meta name="color-scheme" content="dark">
|
|
1841
2006
|
<style>
|
|
1842
|
-
|
|
2007
|
+
${STYLE}
|
|
2008
|
+
</style></head>
|
|
2009
|
+
<body><main>
|
|
2010
|
+
<header><h1>austere-axi hub<small>${escapeHtml(version2)}</small></h1><nav class="nav"><a href="/">Pages</a><a href="/notes">Notes</a></nav><span class="stats">${stats.artifacts} artifacts \xB7 ${stats.projects} projects${stats.unanswered ? ` \xB7 ${stats.unanswered} unanswered` : ""}</span></header>
|
|
2011
|
+
<form method="get" action="/">${project ? `<input type="hidden" name="project" value="${escapeHtml(project)}">` : ""}<input type="search" name="q" value="${escapeHtml(q2)}" placeholder="Search titles, files, projects" aria-label="Search"></form>
|
|
2012
|
+
${filter}${sections || `<p class="empty">${empty}</p>`}
|
|
2013
|
+
</main></body></html>`;
|
|
2014
|
+
}
|
|
2015
|
+
function notesPage(rows, { version: version2, q: q2, project, status, author = "", authors = [], stats }) {
|
|
2016
|
+
const items = rows.map((n) => {
|
|
2017
|
+
const waiting = n.status !== "addressed";
|
|
2018
|
+
const when = `${day(n.at)} ${time3(n.at)}`;
|
|
2019
|
+
return `<li class="n"><a href="/hub/open/${escapeHtml(n.artifact_id)}" title="${escapeHtml(n.file)}">
|
|
2020
|
+
<p class="note">${escapeHtml(n.text || "(no text)")}</p>
|
|
2021
|
+
<span class="note-t">${n.author ? `<b class="by">${escapeHtml(n.author)}</b> \xB7 ` : ""}${n.target ? `on ${escapeHtml(n.target)} \xB7 ` : ""}<strong>${escapeHtml(n.title || path10.basename(n.file))}</strong> \xB7 ${escapeHtml(path10.basename(n.project) || n.project)} \xB7 ${escapeHtml(when)} \xB7 <span class="st ${waiting ? "waiting" : "done"}">${STATUS_LABEL[n.status] || escapeHtml(n.status)}</span></span>
|
|
2022
|
+
</a></li>`;
|
|
2023
|
+
}).join("");
|
|
2024
|
+
const opt = (v, label) => `<option value="${v}"${status === v ? " selected" : ""}>${label}</option>`;
|
|
2025
|
+
const people = authors.length ? `<select name="author" aria-label="Author"><option value="">Everyone</option><option value="owner"${author === "owner" ? " selected" : ""}>Owner</option>${authors.map((a) => `<option value="${escapeHtml(a)}"${author === a ? " selected" : ""}>${escapeHtml(a)}</option>`).join("")}</select>` : "";
|
|
2026
|
+
const filter = project ? `<p class="filter">Project ${escapeHtml(tilde(project))} \xB7 <a href="/notes">all projects</a></p>` : "";
|
|
2027
|
+
const empty = q2 || project || author || status !== "all" ? "No notes match." : "No notes yet.";
|
|
2028
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2029
|
+
<title>Notes \xB7 austere-axi hub</title><meta name="color-scheme" content="dark">
|
|
2030
|
+
<style>
|
|
2031
|
+
${STYLE}</style></head>
|
|
2032
|
+
<body><main>
|
|
2033
|
+
<header><h1>austere-axi hub<small>${escapeHtml(version2)}</small></h1><nav class="nav"><a href="/">Pages</a><a href="/notes">Notes</a></nav><span class="stats">${rows.length} notes${stats.unanswered ? ` \xB7 ${stats.unanswered} unanswered` : ""}</span></header>
|
|
2034
|
+
<form class="f${authors.length ? " p" : ""}" method="get" action="/notes">${project ? `<input type="hidden" name="project" value="${escapeHtml(project)}">` : ""}<input type="search" name="q" value="${escapeHtml(q2)}" placeholder="Search notes, targets, page titles" aria-label="Search notes"><select name="status" aria-label="Status">${opt("all", "All")}${opt("unanswered", "Unanswered")}${opt("answered", "Answered")}</select>${people}<button type="submit">Filter</button></form>
|
|
2035
|
+
${filter}${items ? `<ul>${items}</ul>` : `<p class="empty">${empty}</p>`}
|
|
2036
|
+
</main></body></html>`;
|
|
2037
|
+
}
|
|
2038
|
+
var STYLE, STATUS_LABEL;
|
|
2039
|
+
var init_hub_page = __esm({
|
|
2040
|
+
"src/cli/hub-page.js"() {
|
|
2041
|
+
init_artifact();
|
|
2042
|
+
STYLE = `body{margin:0;background:#0b0b0d;color:#e8e8ec;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif}
|
|
1843
2043
|
main{max-width:860px;margin:0 auto;padding:32px 16px 64px}
|
|
1844
2044
|
header{display:flex;flex-wrap:wrap;gap:12px;align-items:baseline;justify-content:space-between}
|
|
1845
2045
|
h1{font-size:18px;font-weight:600;margin:0}h1 small{color:#77777f;font-weight:400;font-size:13px;margin-left:6px}
|
|
@@ -1855,16 +2055,14 @@ li strong{color:#a9c1ff;font-weight:600;overflow-wrap:anywhere}li>a:hover strong
|
|
|
1855
2055
|
.meta{color:#8a8a94;font-size:12px;overflow-wrap:anywhere}em{font-style:normal}em.live{color:#8fd19e}
|
|
1856
2056
|
b{font-weight:600}b.open{color:#f2c46d;font-size:12px}
|
|
1857
2057
|
.filter{color:#8a8a94;font-size:12.5px}.filter a{color:#a9c1ff}.empty{color:#8a8a94;margin-top:28px}
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
"src/cli/hub-page.js"() {
|
|
1867
|
-
init_artifact();
|
|
2058
|
+
.nav{display:flex;gap:14px;font-size:13px}.nav a{color:#a9c1ff;text-decoration:none}
|
|
2059
|
+
form.f{display:grid;grid-template-columns:minmax(0,1fr);gap:8px}button{background:#1c1c20;border:1px solid #2a2a30;border-radius:8px;color:#e8e8ec;padding:9px 12px;font:inherit}select{background:#141417;border:1px solid #2a2a30;border-radius:8px;color:#e8e8ec;padding:9px 12px;font:inherit;width:100%;box-sizing:border-box}
|
|
2060
|
+
.note{margin:0;overflow-wrap:anywhere;white-space:pre-wrap}.note-t{color:#8a8a94;font-size:12px}.st{font-size:12px}.st.waiting{color:#f2c46d}.st.done{color:#8fd19e}
|
|
2061
|
+
li.n{padding:12px 0}li.n>a{padding:0}
|
|
2062
|
+
b.by{color:#e8e8ec}
|
|
2063
|
+
@media(min-width:600px){form.f{grid-template-columns:minmax(0,1fr) 150px auto}form.f.p{grid-template-columns:minmax(0,1fr) 150px 150px auto}}
|
|
2064
|
+
`;
|
|
2065
|
+
STATUS_LABEL = { open: "unanswered", received: "unanswered", addressed: "answered", held: "held for the owner" };
|
|
1868
2066
|
}
|
|
1869
2067
|
});
|
|
1870
2068
|
|
|
@@ -1873,30 +2071,26 @@ var server_exports = {};
|
|
|
1873
2071
|
__export(server_exports, {
|
|
1874
2072
|
runServer: () => runServer
|
|
1875
2073
|
});
|
|
1876
|
-
import
|
|
2074
|
+
import fs12 from "node:fs";
|
|
1877
2075
|
import http2 from "node:http";
|
|
1878
|
-
import
|
|
1879
|
-
import
|
|
2076
|
+
import path11 from "node:path";
|
|
2077
|
+
import crypto4 from "node:crypto";
|
|
1880
2078
|
async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
|
|
1881
2079
|
const log = (...a) => {
|
|
1882
2080
|
const line = `${(/* @__PURE__ */ new Date()).toISOString()} ${a.join(" ")}`;
|
|
1883
2081
|
if (verbose) process.stderr.write(line + "\n");
|
|
1884
2082
|
try {
|
|
1885
|
-
|
|
2083
|
+
fs12.appendFileSync(path11.join(stateDir(), "server.log"), line + "\n");
|
|
1886
2084
|
} catch {
|
|
1887
2085
|
}
|
|
1888
2086
|
};
|
|
1889
2087
|
let hub = null;
|
|
1890
2088
|
const hubWanted = hubEnabled();
|
|
1891
2089
|
if (hubWanted) {
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
hub = await Hub.open();
|
|
1897
|
-
} catch (err) {
|
|
1898
|
-
log("hub-unavailable", String(err?.message || err));
|
|
1899
|
-
}
|
|
2090
|
+
try {
|
|
2091
|
+
hub = await Hub.open();
|
|
2092
|
+
} catch (err) {
|
|
2093
|
+
log("hub-unavailable", String(err?.message || err));
|
|
1900
2094
|
}
|
|
1901
2095
|
}
|
|
1902
2096
|
const store = new SessionStore(stateDir(), { onFlush: hub ? (s) => hub.syncSession(s) : void 0 });
|
|
@@ -1909,6 +2103,8 @@ async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
|
|
|
1909
2103
|
}
|
|
1910
2104
|
}
|
|
1911
2105
|
}
|
|
2106
|
+
const invites = new InviteStore(stateDir());
|
|
2107
|
+
const ownerToken = ownerKey(stateDir());
|
|
1912
2108
|
const clients = /* @__PURE__ */ new Map();
|
|
1913
2109
|
const polls = /* @__PURE__ */ new Map();
|
|
1914
2110
|
const watchers = /* @__PURE__ */ new Map();
|
|
@@ -1929,14 +2125,44 @@ async function runServer({ port: port2, verbose = false, alsoListen = [] }) {
|
|
|
1929
2125
|
for (const h of listeners.keys()) extra.push(h);
|
|
1930
2126
|
return allowedHosts(extra);
|
|
1931
2127
|
}
|
|
1932
|
-
function
|
|
1933
|
-
const local = `http://127.0.0.1:${port2}/s/${id}`;
|
|
1934
|
-
const out = { local };
|
|
1935
|
-
if (tailnet && listeners.has(tailnet.ip)) out.remote = `http://${tailnet.dnsName || tailnet.ip}:${port2}/s/${id}`;
|
|
2128
|
+
function remoteHost() {
|
|
1936
2129
|
const linkHost = process.env.AUSTERE_AXI_LINK_HOST;
|
|
1937
|
-
if (linkHost)
|
|
2130
|
+
if (linkHost) return linkHost;
|
|
2131
|
+
if (tailnet && listeners.has(tailnet.ip)) return tailnet.dnsName || tailnet.ip;
|
|
2132
|
+
const explicit = process.env.AUSTERE_AXI_HOST;
|
|
2133
|
+
if (explicit && !/^(0\.0\.0\.0|::|\*)$/.test(explicit) && listeners.has(explicit)) return explicit;
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
function remoteUrl(id) {
|
|
2137
|
+
const host = remoteHost();
|
|
2138
|
+
return host ? `http://${host}:${port2}/s/${id}` : null;
|
|
2139
|
+
}
|
|
2140
|
+
function links(id) {
|
|
2141
|
+
const out = { local: `http://127.0.0.1:${port2}/s/${id}` };
|
|
2142
|
+
const remote = remoteUrl(id);
|
|
2143
|
+
if (remote) out.remote = `${remote}?key=${ownerToken}`;
|
|
1938
2144
|
return out;
|
|
1939
2145
|
}
|
|
2146
|
+
function inviteLink(inv) {
|
|
2147
|
+
return `${remoteUrl(inv.sessionId)}?invite=${inv.token}`;
|
|
2148
|
+
}
|
|
2149
|
+
function isOwner(req) {
|
|
2150
|
+
if (isLoopback(req.socket.remoteAddress)) return true;
|
|
2151
|
+
const key = tokenFromCookie(req.headers.cookie, OWNER_COOKIE);
|
|
2152
|
+
return key != null && safeEqual(key, ownerToken);
|
|
2153
|
+
}
|
|
2154
|
+
function identify(req, s) {
|
|
2155
|
+
const token = tokenFromCookie(req.headers.cookie);
|
|
2156
|
+
if (token == null) return isOwner(req) ? OWNER : null;
|
|
2157
|
+
const inv = invites.find(token);
|
|
2158
|
+
if (!inv || inv.sessionId !== s.id) return null;
|
|
2159
|
+
return { role: "reviewer", name: inv.name, auto: inv.auto, token: inv.token };
|
|
2160
|
+
}
|
|
2161
|
+
function canSee(e, viewer) {
|
|
2162
|
+
if (viewer.role === "owner") return true;
|
|
2163
|
+
if (e.role === "user" && (e.held || e.discarded)) return e.author === viewer.name;
|
|
2164
|
+
return true;
|
|
2165
|
+
}
|
|
1940
2166
|
function send(res, status, body, headers = {}) {
|
|
1941
2167
|
const isText = typeof body === "string" || Buffer.isBuffer(body);
|
|
1942
2168
|
const payload = isText ? body : JSON.stringify(body);
|
|
@@ -1985,11 +2211,32 @@ data: ${JSON.stringify(data)}
|
|
|
1985
2211
|
broadcast(s.id, "presence", { presence: presence(s), status: s.status, endedBy: s.endedBy });
|
|
1986
2212
|
}
|
|
1987
2213
|
function broadcastTranscript(s, entries) {
|
|
1988
|
-
|
|
2214
|
+
const set2 = clients.get(s.id);
|
|
2215
|
+
if (!set2) return;
|
|
2216
|
+
for (const c of set2) {
|
|
2217
|
+
const visible = entries.filter((e) => canSee(e, c.viewer));
|
|
2218
|
+
if (visible.length) c.res.write(`event: transcript
|
|
2219
|
+
data: ${JSON.stringify({ entries: visible })}
|
|
2220
|
+
|
|
2221
|
+
`);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
function dropReviewers(tokens) {
|
|
2225
|
+
for (const set2 of clients.values()) {
|
|
2226
|
+
for (const c of Array.from(set2)) {
|
|
2227
|
+
if (c.viewer.role !== "reviewer" || !tokens.has(c.viewer.token)) continue;
|
|
2228
|
+
c.res.write(`event: server
|
|
2229
|
+
data: ${JSON.stringify({ state: "revoked" })}
|
|
2230
|
+
|
|
2231
|
+
`);
|
|
2232
|
+
c.res.end();
|
|
2233
|
+
set2.delete(c);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
1989
2236
|
}
|
|
1990
2237
|
function readFileSafe(file2) {
|
|
1991
2238
|
try {
|
|
1992
|
-
return
|
|
2239
|
+
return fs12.readFileSync(file2, "utf8");
|
|
1993
2240
|
} catch {
|
|
1994
2241
|
return null;
|
|
1995
2242
|
}
|
|
@@ -2001,14 +2248,14 @@ data: ${JSON.stringify(data)}
|
|
|
2001
2248
|
const w = { html, program: art?.program ?? null, shell: art?.shell ?? null, timer: null };
|
|
2002
2249
|
watchers.set(s.id, w);
|
|
2003
2250
|
if (art?.title) s.title = art.title;
|
|
2004
|
-
|
|
2251
|
+
fs12.watchFile(s.file, { interval: 300 }, () => {
|
|
2005
2252
|
clearTimeout(w.timer);
|
|
2006
2253
|
w.timer = setTimeout(() => onFileChange(s, w), 120);
|
|
2007
2254
|
});
|
|
2008
2255
|
}
|
|
2009
2256
|
function unwatch(id) {
|
|
2010
2257
|
const s = store.get(id);
|
|
2011
|
-
if (s)
|
|
2258
|
+
if (s) fs12.unwatchFile(s.file);
|
|
2012
2259
|
watchers.delete(id);
|
|
2013
2260
|
}
|
|
2014
2261
|
function onFileChange(s, w) {
|
|
@@ -2050,7 +2297,7 @@ data: ${JSON.stringify(data)}
|
|
|
2050
2297
|
const items = [];
|
|
2051
2298
|
let end = false;
|
|
2052
2299
|
for (const e of entries) {
|
|
2053
|
-
for (const it2 of e.items || []) items.push(it2);
|
|
2300
|
+
for (const it2 of e.items || []) items.push(e.author ? { ...it2, author: e.author } : it2);
|
|
2054
2301
|
if (e.end) end = true;
|
|
2055
2302
|
}
|
|
2056
2303
|
const body = {
|
|
@@ -2122,6 +2369,7 @@ data: ${JSON.stringify(data)}
|
|
|
2122
2369
|
presence: presence(s2),
|
|
2123
2370
|
browsers: clientCount(s2.id),
|
|
2124
2371
|
pending: s2.pending.length,
|
|
2372
|
+
held: store.heldEntries(s2).length,
|
|
2125
2373
|
links: links(s2.id)
|
|
2126
2374
|
}))
|
|
2127
2375
|
});
|
|
@@ -2131,12 +2379,52 @@ data: ${JSON.stringify(data)}
|
|
|
2131
2379
|
setTimeout(() => shutdown("requested"), 50);
|
|
2132
2380
|
return;
|
|
2133
2381
|
}
|
|
2382
|
+
if (route === "share/list") {
|
|
2383
|
+
const withLinks = url2.searchParams.get("links") === "1";
|
|
2384
|
+
return send(res, 200, {
|
|
2385
|
+
invites: invites.list().map((i) => ({
|
|
2386
|
+
id: inviteId(i),
|
|
2387
|
+
name: i.name,
|
|
2388
|
+
file: i.file,
|
|
2389
|
+
auto: i.auto,
|
|
2390
|
+
createdAt: i.createdAt,
|
|
2391
|
+
link: withLinks ? inviteLink(i) : void 0
|
|
2392
|
+
}))
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2134
2395
|
if (req.method !== "POST") return send(res, 405, { error: "POST expected" });
|
|
2135
2396
|
const body = await readBody(req);
|
|
2397
|
+
if (route === "share/revoke") {
|
|
2398
|
+
const gone = invites.revoke(String(body.key || ""));
|
|
2399
|
+
if (!gone.length) return send(res, 404, { error: "no invite matches that name or id" });
|
|
2400
|
+
dropReviewers(new Set(gone.map((i) => i.token)));
|
|
2401
|
+
log("share-revoke", gone.map((i) => `${i.name}(${inviteId(i)})`).join(","));
|
|
2402
|
+
return send(res, 200, { revoked: gone.map((i) => ({ id: inviteId(i), name: i.name, file: i.file })) });
|
|
2403
|
+
}
|
|
2136
2404
|
const file2 = typeof body.file === "string" ? body.file : "";
|
|
2137
2405
|
if (!file2) return send(res, 400, { error: "file is required" });
|
|
2406
|
+
if (route === "share") {
|
|
2407
|
+
if (!fs12.existsSync(file2)) return send(res, 404, { error: `no such file: ${file2}` });
|
|
2408
|
+
await ensureTailnetListener();
|
|
2409
|
+
if (!remoteHost()) {
|
|
2410
|
+
return send(res, 409, {
|
|
2411
|
+
error: "no tailnet address: Tailscale is not running here and AUSTERE_AXI_HOST is not set, so a teammate could not open the link"
|
|
2412
|
+
});
|
|
2413
|
+
}
|
|
2414
|
+
let inv;
|
|
2415
|
+
try {
|
|
2416
|
+
const s2 = store.ensure(file2);
|
|
2417
|
+
inv = invites.add({ name: body.name, sessionId: s2.id, file: s2.file, auto: !!body.auto });
|
|
2418
|
+
watch(s2);
|
|
2419
|
+
store.touch(s2);
|
|
2420
|
+
} catch (err) {
|
|
2421
|
+
return send(res, 400, { error: String(err.message || err) });
|
|
2422
|
+
}
|
|
2423
|
+
log("share", inv.name, inviteId(inv), inv.auto ? "auto" : "hold");
|
|
2424
|
+
return send(res, 200, { id: inviteId(inv), name: inv.name, auto: inv.auto, link: inviteLink(inv), file: inv.file });
|
|
2425
|
+
}
|
|
2138
2426
|
if (route === "open") {
|
|
2139
|
-
if (!
|
|
2427
|
+
if (!fs12.existsSync(file2)) return send(res, 404, { error: `no such file: ${file2}` });
|
|
2140
2428
|
await ensureTailnetListener();
|
|
2141
2429
|
const s2 = store.ensure(file2);
|
|
2142
2430
|
if (s2.status === "ended") {
|
|
@@ -2153,7 +2441,8 @@ data: ${JSON.stringify(data)}
|
|
|
2153
2441
|
session: publicSession(s2),
|
|
2154
2442
|
links: links(s2.id),
|
|
2155
2443
|
browsers: clientCount(s2.id),
|
|
2156
|
-
pending: s2.pending.length
|
|
2444
|
+
pending: s2.pending.length,
|
|
2445
|
+
held: store.heldEntries(s2).length
|
|
2157
2446
|
});
|
|
2158
2447
|
}
|
|
2159
2448
|
const s = store.byFile(file2);
|
|
@@ -2246,9 +2535,9 @@ data: ${JSON.stringify(data)}
|
|
|
2246
2535
|
}
|
|
2247
2536
|
const file2 = installedPackFile(spec.name, spec.version);
|
|
2248
2537
|
if (!file2) return send(res, 404, `pack ${spec.name}@${spec.version} is not installed (austere-axi components add ${spec.name}@${spec.version})`);
|
|
2249
|
-
|
|
2538
|
+
fs12.readFile(file2, (err, buf) => {
|
|
2250
2539
|
if (err) return send(res, 404, "not found");
|
|
2251
|
-
const actual = `sha384-${
|
|
2540
|
+
const actual = `sha384-${crypto4.createHash("sha384").update(buf).digest("base64")}`;
|
|
2252
2541
|
const locked = readLock().packs[spec.name]?.integrity;
|
|
2253
2542
|
const wanted = url2.searchParams.get("integrity");
|
|
2254
2543
|
if (locked && locked !== actual) {
|
|
@@ -2270,23 +2559,23 @@ data: ${JSON.stringify(data)}
|
|
|
2270
2559
|
}
|
|
2271
2560
|
function serveAsset(res, rel, url2) {
|
|
2272
2561
|
if (rel.startsWith("installed-packs/")) return serveInstalledPack(res, rel, url2);
|
|
2273
|
-
const clean =
|
|
2274
|
-
const file2 =
|
|
2275
|
-
if (!file2.startsWith(ASSET_DIR +
|
|
2276
|
-
|
|
2562
|
+
const clean = path11.normalize(rel).replace(/^(\.\.[/\\])+/, "");
|
|
2563
|
+
const file2 = path11.join(ASSET_DIR, clean);
|
|
2564
|
+
if (!file2.startsWith(ASSET_DIR + path11.sep)) return send(res, 404, "not found");
|
|
2565
|
+
fs12.stat(file2, (err, st2) => {
|
|
2277
2566
|
if (err || !st2.isFile()) return send(res, 404, "not found");
|
|
2278
2567
|
res.writeHead(200, {
|
|
2279
|
-
"content-type": MIME[
|
|
2568
|
+
"content-type": MIME[path11.extname(file2)] || "application/octet-stream",
|
|
2280
2569
|
"content-length": st2.size,
|
|
2281
2570
|
"cache-control": "no-cache",
|
|
2282
2571
|
"x-content-type-options": "nosniff"
|
|
2283
2572
|
});
|
|
2284
|
-
|
|
2573
|
+
fs12.createReadStream(file2).pipe(res);
|
|
2285
2574
|
});
|
|
2286
2575
|
}
|
|
2287
2576
|
function serveArtifact(req, res, s, rest) {
|
|
2288
|
-
const dir =
|
|
2289
|
-
const base =
|
|
2577
|
+
const dir = path11.dirname(s.file);
|
|
2578
|
+
const base = path11.basename(s.file);
|
|
2290
2579
|
if (rest === base || rest === "") {
|
|
2291
2580
|
const html = readFileSafe(s.file);
|
|
2292
2581
|
if (html == null) return send(res, 404, "artifact file is missing");
|
|
@@ -2299,28 +2588,29 @@ data: ${JSON.stringify(data)}
|
|
|
2299
2588
|
} catch {
|
|
2300
2589
|
return send(res, 400, "bad path");
|
|
2301
2590
|
}
|
|
2302
|
-
const file2 =
|
|
2303
|
-
if (!file2.startsWith(dir +
|
|
2591
|
+
const file2 = path11.resolve(dir, decoded);
|
|
2592
|
+
if (!file2.startsWith(dir + path11.sep) || decoded.split(/[/\\]/).some((p) => p.startsWith("."))) {
|
|
2304
2593
|
return send(res, 404, "not found");
|
|
2305
2594
|
}
|
|
2306
|
-
|
|
2595
|
+
fs12.stat(file2, (err, st2) => {
|
|
2307
2596
|
if (err || !st2.isFile() || st2.size > 64 * 1024 * 1024) return send(res, 404, "not found");
|
|
2308
2597
|
res.writeHead(200, {
|
|
2309
|
-
"content-type": MIME[
|
|
2598
|
+
"content-type": MIME[path11.extname(file2).toLowerCase()] || "application/octet-stream",
|
|
2310
2599
|
"content-length": st2.size,
|
|
2311
2600
|
"cache-control": "no-cache",
|
|
2312
2601
|
"x-content-type-options": "nosniff"
|
|
2313
2602
|
});
|
|
2314
|
-
|
|
2603
|
+
fs12.createReadStream(file2).pipe(res);
|
|
2315
2604
|
});
|
|
2316
2605
|
}
|
|
2317
|
-
function chromePage(s) {
|
|
2606
|
+
function chromePage(s, viewer) {
|
|
2318
2607
|
const boot = {
|
|
2319
2608
|
id: s.id,
|
|
2320
2609
|
title: s.title,
|
|
2321
|
-
file: s.file,
|
|
2322
|
-
name:
|
|
2323
|
-
|
|
2610
|
+
file: viewer.role === "owner" ? s.file : "",
|
|
2611
|
+
viewer: { role: viewer.role, name: viewer.name, auto: viewer.role === "reviewer" ? viewer.auto : void 0 },
|
|
2612
|
+
name: path11.basename(s.file),
|
|
2613
|
+
artifactUrl: `/s/${s.id}/a/${encodeURIComponent(path11.basename(s.file))}`,
|
|
2324
2614
|
version: VERSION
|
|
2325
2615
|
};
|
|
2326
2616
|
return `<!doctype html>
|
|
@@ -2343,10 +2633,20 @@ data: ${JSON.stringify(data)}
|
|
|
2343
2633
|
const project = (url2.searchParams.get("project") || "").slice(0, 1e3);
|
|
2344
2634
|
return hubPage(hub.list({ q: q2, project }), { version: VERSION, q: q2, project, stats: hub.stats() });
|
|
2345
2635
|
}
|
|
2636
|
+
function hubNotes(url2) {
|
|
2637
|
+
store.flushAll();
|
|
2638
|
+
const q2 = (url2.searchParams.get("q") || "").slice(0, 200);
|
|
2639
|
+
const project = (url2.searchParams.get("project") || "").slice(0, 1e3);
|
|
2640
|
+
const st2 = url2.searchParams.get("status");
|
|
2641
|
+
const status = st2 === "unanswered" || st2 === "answered" ? st2 : "all";
|
|
2642
|
+
const author = (url2.searchParams.get("author") || "").slice(0, 80);
|
|
2643
|
+
const rows = hub.notes({ q: q2, project, author, status: status === "all" ? "" : status });
|
|
2644
|
+
return notesPage(rows, { version: VERSION, q: q2, project, status, author, authors: hub.authors(), stats: hub.stats() });
|
|
2645
|
+
}
|
|
2346
2646
|
function hubOpen(res, id) {
|
|
2347
2647
|
const a = /^[a-f0-9]{12}$/.test(id) ? hub.artifact(id) : null;
|
|
2348
2648
|
if (!a) return send(res, 404, "not in the hub");
|
|
2349
|
-
if (!
|
|
2649
|
+
if (!fs12.existsSync(a.file)) return send(res, 410, `${a.file} no longer exists`);
|
|
2350
2650
|
const existed = store.byFile(a.file);
|
|
2351
2651
|
const s = store.ensure(a.file);
|
|
2352
2652
|
if (!existed) store.touch(s);
|
|
@@ -2369,9 +2669,23 @@ data: ${JSON.stringify(data)}
|
|
|
2369
2669
|
}
|
|
2370
2670
|
if (url2.pathname.startsWith("/api/")) return handleApi(req, res, url2);
|
|
2371
2671
|
if (url2.pathname.startsWith("/__austere/")) return serveAsset(res, url2.pathname.slice("/__austere/".length), url2);
|
|
2672
|
+
const key = url2.searchParams.get("key");
|
|
2673
|
+
if (key != null) {
|
|
2674
|
+
if (!safeEqual(key, ownerToken)) return send(res, 403, "This link is not valid.");
|
|
2675
|
+
url2.searchParams.delete("key");
|
|
2676
|
+
res.writeHead(302, {
|
|
2677
|
+
location: url2.pathname + url2.search,
|
|
2678
|
+
"set-cookie": `${OWNER_COOKIE}=${ownerToken}; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000`,
|
|
2679
|
+
"cache-control": "no-store"
|
|
2680
|
+
});
|
|
2681
|
+
return res.end();
|
|
2682
|
+
}
|
|
2683
|
+
const ownerOnly = url2.pathname === "/" || url2.pathname === "/notes" || url2.pathname.startsWith("/hub/");
|
|
2684
|
+
if (ownerOnly && !isOwner(req)) return send(res, 403, "Only the owner can open this. Ask the owner for a link to the page.");
|
|
2372
2685
|
if (url2.pathname === "/") {
|
|
2373
2686
|
return send(res, 200, hub ? hubHome(url2) : homePage(), { "content-type": "text/html; charset=utf-8" });
|
|
2374
2687
|
}
|
|
2688
|
+
if (hub && url2.pathname === "/notes") return send(res, 200, hubNotes(url2), { "content-type": "text/html; charset=utf-8" });
|
|
2375
2689
|
if (hub && url2.pathname.startsWith("/hub/open/")) return hubOpen(res, url2.pathname.slice("/hub/open/".length));
|
|
2376
2690
|
if (url2.pathname === "/favicon.ico") return send(res, 204, "");
|
|
2377
2691
|
const m = /^\/s\/([a-f0-9]{12})(\/.*)?$/.exec(url2.pathname);
|
|
@@ -2380,9 +2694,31 @@ data: ${JSON.stringify(data)}
|
|
|
2380
2694
|
if (!s) return send(res, 404, "no such session");
|
|
2381
2695
|
const sub = m[2] || "";
|
|
2382
2696
|
activity();
|
|
2697
|
+
const invite = url2.searchParams.get("invite");
|
|
2698
|
+
if (invite != null && (sub === "" || sub === "/")) {
|
|
2699
|
+
const inv = invites.find(invite);
|
|
2700
|
+
if (!inv || inv.sessionId !== s.id) return send(res, 403, "This invite link is not valid. It may have been revoked; ask for a new one.");
|
|
2701
|
+
res.writeHead(302, {
|
|
2702
|
+
location: `/s/${s.id}`,
|
|
2703
|
+
"set-cookie": `${COOKIE}=${inv.token}; Path=/s/${s.id}; HttpOnly; SameSite=Lax; Max-Age=2592000`,
|
|
2704
|
+
"cache-control": "no-store"
|
|
2705
|
+
});
|
|
2706
|
+
return res.end();
|
|
2707
|
+
}
|
|
2708
|
+
const who = identify(req, s);
|
|
2709
|
+
if (!who) {
|
|
2710
|
+
if (tokenFromCookie(req.headers.cookie) == null) return send(res, 403, "Ask the owner for a link to this page.");
|
|
2711
|
+
return send(res, 403, "This invite is no longer valid. Ask for a new link.", {
|
|
2712
|
+
"set-cookie": `${COOKIE}=; Path=/s/${s.id}; HttpOnly; SameSite=Lax; Max-Age=0`
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
const reviewer = who.role === "reviewer";
|
|
2716
|
+
if (reviewer && !(sub === "" || sub === "/" || sub.startsWith("/a/") || sub === "/events" || sub === "/send" || sub === "/bye")) {
|
|
2717
|
+
return send(res, 403, { error: "reviewers can only read the page and add notes" });
|
|
2718
|
+
}
|
|
2383
2719
|
if (sub === "" || sub === "/") {
|
|
2384
2720
|
watch(s);
|
|
2385
|
-
return send(res, 200, chromePage(s), { "content-type": "text/html; charset=utf-8" });
|
|
2721
|
+
return send(res, 200, chromePage(s, who), { "content-type": "text/html; charset=utf-8" });
|
|
2386
2722
|
}
|
|
2387
2723
|
if (sub.startsWith("/a/")) return serveArtifact(req, res, s, sub.slice(3));
|
|
2388
2724
|
if (sub === "/events") {
|
|
@@ -2396,20 +2732,20 @@ data: ${JSON.stringify(data)}
|
|
|
2396
2732
|
res.write("retry: 2000\n\n");
|
|
2397
2733
|
const w = watchers.get(s.id);
|
|
2398
2734
|
const snapshot = {
|
|
2399
|
-
session: publicSession(s),
|
|
2735
|
+
session: reviewer ? { ...publicSession(s), file: "" } : publicSession(s),
|
|
2400
2736
|
presence: presence(s),
|
|
2401
|
-
transcript: s.transcript.slice(-200),
|
|
2737
|
+
transcript: s.transcript.filter((e) => !e.discarded && canSee(e, who)).slice(-200),
|
|
2402
2738
|
revision: s.revision,
|
|
2403
2739
|
program: w?.program ?? null,
|
|
2404
2740
|
thirdPartyPacks: thirdPartyPacks(w?.html),
|
|
2405
2741
|
version: VERSION,
|
|
2406
|
-
links: links(s.id)
|
|
2742
|
+
links: reviewer ? null : links(s.id)
|
|
2407
2743
|
};
|
|
2408
2744
|
res.write(`event: snapshot
|
|
2409
2745
|
data: ${JSON.stringify(snapshot)}
|
|
2410
2746
|
|
|
2411
2747
|
`);
|
|
2412
|
-
const client = { res };
|
|
2748
|
+
const client = { res, viewer: who };
|
|
2413
2749
|
if (!clients.has(s.id)) clients.set(s.id, /* @__PURE__ */ new Set());
|
|
2414
2750
|
clients.get(s.id).add(client);
|
|
2415
2751
|
browserSeen.add(s.id);
|
|
@@ -2426,13 +2762,19 @@ data: ${JSON.stringify(snapshot)}
|
|
|
2426
2762
|
if (req.method === "POST" && !checkOrigin(req)) return send(res, 403, { error: "cross-origin request refused" });
|
|
2427
2763
|
if (sub === "/send" && req.method === "POST") {
|
|
2428
2764
|
const body = await readBody(req, 4 * 1024 * 1024);
|
|
2429
|
-
const
|
|
2765
|
+
const end = !reviewer && !!body.end;
|
|
2766
|
+
let items = Array.isArray(body.items) ? body.items.filter((x) => x && typeof x === "object").slice(0, reviewer ? 50 : 200) : [];
|
|
2767
|
+
if (reviewer) items = items.filter((x) => REVIEWER_KINDS.has(x.kind));
|
|
2430
2768
|
if (s.status === "ended") return send(res, 409, { error: "session has ended" });
|
|
2431
|
-
if (!items.length && !
|
|
2432
|
-
for (const it2 of items)
|
|
2769
|
+
if (!items.length && !end) return send(res, 400, { error: "nothing to send" });
|
|
2770
|
+
for (const it2 of items) {
|
|
2771
|
+
delete it2.author;
|
|
2772
|
+
it2.id = reviewer || !it2.id ? newId("i") : it2.id;
|
|
2773
|
+
}
|
|
2774
|
+
const hold = reviewer && !who.auto;
|
|
2433
2775
|
let entry = null;
|
|
2434
|
-
if (items.length) entry = store.addUserBatch(s, items, { end:
|
|
2435
|
-
if (
|
|
2776
|
+
if (items.length) entry = store.addUserBatch(s, items, { end, author: who.name, hold });
|
|
2777
|
+
if (end) {
|
|
2436
2778
|
store.end(s, "user");
|
|
2437
2779
|
broadcast(s.id, "ended", { by: "user" });
|
|
2438
2780
|
}
|
|
@@ -2440,11 +2782,21 @@ data: ${JSON.stringify(snapshot)}
|
|
|
2440
2782
|
const poll = polls.get(s.id);
|
|
2441
2783
|
if (poll && !poll.done) {
|
|
2442
2784
|
if (s.pending.length) deliver(s, poll);
|
|
2443
|
-
else if (
|
|
2785
|
+
else if (end) finishPoll(s, poll, { status: "ended", endedBy: "user" });
|
|
2444
2786
|
}
|
|
2445
2787
|
broadcastPresence(s);
|
|
2446
2788
|
return send(res, 200, { ok: true, entry });
|
|
2447
2789
|
}
|
|
2790
|
+
if ((sub === "/release" || sub === "/discard") && req.method === "POST") {
|
|
2791
|
+
const body = await readBody(req);
|
|
2792
|
+
const ids = Array.isArray(body.ids) ? body.ids.map(String) : body.all ? null : [];
|
|
2793
|
+
const done = sub === "/release" ? store.release(s, ids) : store.discard(s, ids);
|
|
2794
|
+
if (done.length) broadcastTranscript(s, done);
|
|
2795
|
+
const poll = polls.get(s.id);
|
|
2796
|
+
if (sub === "/release" && poll && !poll.done && s.pending.length) deliver(s, poll);
|
|
2797
|
+
broadcastPresence(s);
|
|
2798
|
+
return send(res, 200, { ok: true, count: done.length });
|
|
2799
|
+
}
|
|
2448
2800
|
if (sub === "/end" && req.method === "POST") {
|
|
2449
2801
|
if (s.status !== "ended") {
|
|
2450
2802
|
store.end(s, "user");
|
|
@@ -2467,7 +2819,7 @@ data: ${JSON.stringify(snapshot)}
|
|
|
2467
2819
|
if (html == null) return send(res, 404, "artifact file is missing");
|
|
2468
2820
|
try {
|
|
2469
2821
|
const out = exportHtml(html);
|
|
2470
|
-
const name =
|
|
2822
|
+
const name = path11.basename(s.file).replace(/\.html?$/i, "") + ".export.html";
|
|
2471
2823
|
return send(res, 200, out.html, {
|
|
2472
2824
|
"content-type": "text/html; charset=utf-8",
|
|
2473
2825
|
"content-disposition": `attachment; filename="${name.replace(/"/g, "")}"`
|
|
@@ -2483,7 +2835,7 @@ data: ${JSON.stringify(snapshot)}
|
|
|
2483
2835
|
if (listeners.has(host)) return resolve(true);
|
|
2484
2836
|
const server = http2.createServer((req, res) => {
|
|
2485
2837
|
handle(req, res).catch((err) => {
|
|
2486
|
-
log("error", req.method, req.url, String(err?.stack || err));
|
|
2838
|
+
log("error", req.method, req.url.split("?")[0], String(err?.stack || err));
|
|
2487
2839
|
if (!res.headersSent) send(res, err.status || 500, { error: String(err.message || err) });
|
|
2488
2840
|
else res.end();
|
|
2489
2841
|
});
|
|
@@ -2492,7 +2844,12 @@ data: ${JSON.stringify(snapshot)}
|
|
|
2492
2844
|
server.requestTimeout = 0;
|
|
2493
2845
|
server.headersTimeout = 6e4;
|
|
2494
2846
|
server.once("error", (err) => {
|
|
2495
|
-
log(
|
|
2847
|
+
log(
|
|
2848
|
+
"listen-failed",
|
|
2849
|
+
host,
|
|
2850
|
+
/** @type {NodeJS.ErrnoException} */
|
|
2851
|
+
err.code || err.message
|
|
2852
|
+
);
|
|
2496
2853
|
resolve(false);
|
|
2497
2854
|
});
|
|
2498
2855
|
server.listen(port2, host, () => {
|
|
@@ -2551,7 +2908,7 @@ data: ${JSON.stringify({ state: "stopped", reason })}
|
|
|
2551
2908
|
log("started", `v${VERSION}`, `pid=${process.pid}`);
|
|
2552
2909
|
return { port: port2, shutdown };
|
|
2553
2910
|
}
|
|
2554
|
-
var MIME, SHORT_GRACE_MS;
|
|
2911
|
+
var MIME, SHORT_GRACE_MS, OWNER, REVIEWER_KINDS;
|
|
2555
2912
|
var init_server = __esm({
|
|
2556
2913
|
"src/cli/server.js"() {
|
|
2557
2914
|
init_artifact();
|
|
@@ -2561,6 +2918,7 @@ var init_server = __esm({
|
|
|
2561
2918
|
init_sessions();
|
|
2562
2919
|
init_packs();
|
|
2563
2920
|
init_hub();
|
|
2921
|
+
init_invites();
|
|
2564
2922
|
init_hub_page();
|
|
2565
2923
|
init_packs_meta();
|
|
2566
2924
|
MIME = {
|
|
@@ -2590,12 +2948,14 @@ var init_server = __esm({
|
|
|
2590
2948
|
".webm": "video/webm"
|
|
2591
2949
|
};
|
|
2592
2950
|
SHORT_GRACE_MS = 1e4;
|
|
2951
|
+
OWNER = Object.freeze({ role: "owner", name: "" });
|
|
2952
|
+
REVIEWER_KINDS = /* @__PURE__ */ new Set(["annotation", "message"]);
|
|
2593
2953
|
}
|
|
2594
2954
|
});
|
|
2595
2955
|
|
|
2596
2956
|
// src/cli/main.js
|
|
2597
|
-
import
|
|
2598
|
-
import
|
|
2957
|
+
import fs13 from "node:fs";
|
|
2958
|
+
import path12 from "node:path";
|
|
2599
2959
|
|
|
2600
2960
|
// node_modules/@openuidev/lang-core/dist/shared-Dzgc28Wo.mjs
|
|
2601
2961
|
var TELEMETRY_REQUEST_TIMEOUT_MS = 2e3;
|
|
@@ -3456,10 +3816,10 @@ function mergeDefs(...defs) {
|
|
|
3456
3816
|
function cloneDef(schema) {
|
|
3457
3817
|
return mergeDefs(schema._zod.def);
|
|
3458
3818
|
}
|
|
3459
|
-
function getElementAtPath(obj,
|
|
3460
|
-
if (!
|
|
3819
|
+
function getElementAtPath(obj, path13) {
|
|
3820
|
+
if (!path13)
|
|
3461
3821
|
return obj;
|
|
3462
|
-
return
|
|
3822
|
+
return path13.reduce((acc, key) => acc?.[key], obj);
|
|
3463
3823
|
}
|
|
3464
3824
|
function promiseAllObject(promisesObj) {
|
|
3465
3825
|
const keys = Object.keys(promisesObj);
|
|
@@ -3799,11 +4159,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
3799
4159
|
}
|
|
3800
4160
|
return false;
|
|
3801
4161
|
}
|
|
3802
|
-
function prefixIssues(
|
|
4162
|
+
function prefixIssues(path13, issues) {
|
|
3803
4163
|
return issues.map((iss) => {
|
|
3804
4164
|
var _a3;
|
|
3805
4165
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
3806
|
-
iss.path.unshift(
|
|
4166
|
+
iss.path.unshift(path13);
|
|
3807
4167
|
return iss;
|
|
3808
4168
|
});
|
|
3809
4169
|
}
|
|
@@ -4265,16 +4625,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
4265
4625
|
}
|
|
4266
4626
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
4267
4627
|
const fieldErrors = { _errors: [] };
|
|
4268
|
-
const processError = (error63,
|
|
4628
|
+
const processError = (error63, path13 = []) => {
|
|
4269
4629
|
for (const issue2 of error63.issues) {
|
|
4270
4630
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
4271
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4631
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
4272
4632
|
} else if (issue2.code === "invalid_key") {
|
|
4273
|
-
processError({ issues: issue2.issues }, [...
|
|
4633
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
4274
4634
|
} else if (issue2.code === "invalid_element") {
|
|
4275
|
-
processError({ issues: issue2.issues }, [...
|
|
4635
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
4276
4636
|
} else {
|
|
4277
|
-
const fullpath = [...
|
|
4637
|
+
const fullpath = [...path13, ...issue2.path];
|
|
4278
4638
|
if (fullpath.length === 0) {
|
|
4279
4639
|
fieldErrors._errors.push(mapper(issue2));
|
|
4280
4640
|
} else {
|
|
@@ -4313,17 +4673,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
4313
4673
|
}
|
|
4314
4674
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
4315
4675
|
const result = { errors: [] };
|
|
4316
|
-
const processError = (error63,
|
|
4676
|
+
const processError = (error63, path13 = []) => {
|
|
4317
4677
|
var _a3;
|
|
4318
4678
|
for (const issue2 of error63.issues) {
|
|
4319
4679
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
4320
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
4680
|
+
issue2.errors.map((issues) => processError({ issues }, [...path13, ...issue2.path]));
|
|
4321
4681
|
} else if (issue2.code === "invalid_key") {
|
|
4322
|
-
processError({ issues: issue2.issues }, [...
|
|
4682
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
4323
4683
|
} else if (issue2.code === "invalid_element") {
|
|
4324
|
-
processError({ issues: issue2.issues }, [...
|
|
4684
|
+
processError({ issues: issue2.issues }, [...path13, ...issue2.path]);
|
|
4325
4685
|
} else {
|
|
4326
|
-
const fullpath = [...
|
|
4686
|
+
const fullpath = [...path13, ...issue2.path];
|
|
4327
4687
|
if (fullpath.length === 0) {
|
|
4328
4688
|
result.errors.push(mapper(issue2));
|
|
4329
4689
|
continue;
|
|
@@ -4362,8 +4722,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
4362
4722
|
}
|
|
4363
4723
|
function toDotPath(_path) {
|
|
4364
4724
|
const segs = [];
|
|
4365
|
-
const
|
|
4366
|
-
for (const seg of
|
|
4725
|
+
const path13 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
4726
|
+
for (const seg of path13) {
|
|
4367
4727
|
if (typeof seg === "number")
|
|
4368
4728
|
segs.push(`[${seg}]`);
|
|
4369
4729
|
else if (typeof seg === "symbol")
|
|
@@ -21501,13 +21861,13 @@ function resolveRef(ref, ctx) {
|
|
|
21501
21861
|
if (!ref.startsWith("#")) {
|
|
21502
21862
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
21503
21863
|
}
|
|
21504
|
-
const
|
|
21505
|
-
if (
|
|
21864
|
+
const path13 = ref.slice(1).split("/").filter(Boolean);
|
|
21865
|
+
if (path13.length === 0) {
|
|
21506
21866
|
return ctx.rootSchema;
|
|
21507
21867
|
}
|
|
21508
21868
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
21509
|
-
if (
|
|
21510
|
-
const key =
|
|
21869
|
+
if (path13[0] === defsKey) {
|
|
21870
|
+
const key = path13[1] === void 0 ? void 0 : decodeJSONPointerSegment(path13[1]);
|
|
21511
21871
|
if (!key || !ctx.defs[key]) {
|
|
21512
21872
|
throw new Error(`Reference not found: ${ref}`);
|
|
21513
21873
|
}
|
|
@@ -22389,11 +22749,11 @@ function defineAction(config2) {
|
|
|
22389
22749
|
ref: actionRef(config2.name, data)
|
|
22390
22750
|
};
|
|
22391
22751
|
}
|
|
22392
|
-
function resolveField(obj,
|
|
22393
|
-
if (!
|
|
22394
|
-
if (!
|
|
22752
|
+
function resolveField(obj, path13) {
|
|
22753
|
+
if (!path13 || obj == null) return void 0;
|
|
22754
|
+
if (!path13.includes(".")) return obj[path13];
|
|
22395
22755
|
let cur = obj;
|
|
22396
|
-
for (const p of
|
|
22756
|
+
for (const p of path13.split(".")) {
|
|
22397
22757
|
if (cur == null) return void 0;
|
|
22398
22758
|
cur = cur[p];
|
|
22399
22759
|
}
|
|
@@ -23769,14 +24129,14 @@ function checkTypeMismatch(value, info) {
|
|
|
23769
24129
|
};
|
|
23770
24130
|
return null;
|
|
23771
24131
|
}
|
|
23772
|
-
function validationMessage(component,
|
|
24132
|
+
function validationMessage(component, path13, issue2) {
|
|
23773
24133
|
switch (issue2.code) {
|
|
23774
24134
|
case "type-mismatch":
|
|
23775
|
-
return `field "${
|
|
24135
|
+
return `field "${path13}" expects ${issue2.expected} but got ${issue2.actual}`;
|
|
23776
24136
|
case "missing-required":
|
|
23777
|
-
return `missing required field "${
|
|
24137
|
+
return `missing required field "${path13}"${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
|
|
23778
24138
|
case "null-required":
|
|
23779
|
-
return `required field "${
|
|
24139
|
+
return `required field "${path13}" cannot be null${issue2.signature ? ` \u2014 signature: ${issue2.signature}` : ""}`;
|
|
23780
24140
|
case "unknown-component":
|
|
23781
24141
|
return `Unknown component "${component}" \u2014 not found in catalog or builtins${issue2.available?.length ? `. Available components: ${issue2.available.join(", ")}` : ""}`;
|
|
23782
24142
|
case "inline-reserved":
|
|
@@ -23791,12 +24151,12 @@ function validationMessage(component, path12, issue2) {
|
|
|
23791
24151
|
return component ? `No root statement, and no statement calls the root component ${component}` : "No root statement";
|
|
23792
24152
|
}
|
|
23793
24153
|
}
|
|
23794
|
-
function pushValidationIssue(ctx, component,
|
|
24154
|
+
function pushValidationIssue(ctx, component, path13, issue2) {
|
|
23795
24155
|
ctx.errors.push({
|
|
23796
24156
|
code: issue2.code,
|
|
23797
24157
|
component,
|
|
23798
|
-
path:
|
|
23799
|
-
message: validationMessage(component,
|
|
24158
|
+
path: path13,
|
|
24159
|
+
message: validationMessage(component, path13, issue2),
|
|
23800
24160
|
statementId: ctx.currentStatementId,
|
|
23801
24161
|
...issue2.code === "no-root" && issue2.reason === "fallback" && { severity: "warning" }
|
|
23802
24162
|
});
|
|
@@ -23806,10 +24166,10 @@ function resolveInvalidValue(required2, defaultValue) {
|
|
|
23806
24166
|
if (defaultValue !== void 0) return defaultValue;
|
|
23807
24167
|
return required2 ? INVALID2 : void 0;
|
|
23808
24168
|
}
|
|
23809
|
-
function validateElementPosition(element, s, component,
|
|
24169
|
+
function validateElementPosition(element, s, component, path13, ctx) {
|
|
23810
24170
|
if (isCompositeSchema(s)) return false;
|
|
23811
24171
|
if (typeof s["type"] === "string" || Array.isArray(s["enum"]) || "const" in s) {
|
|
23812
|
-
pushValidationIssue(ctx, component,
|
|
24172
|
+
pushValidationIssue(ctx, component, path13, {
|
|
23813
24173
|
code: "type-mismatch",
|
|
23814
24174
|
expected: typeof s["type"] === "string" ? s["type"] : "a literal value",
|
|
23815
24175
|
actual: `component "${element.typeName}"`
|
|
@@ -23825,9 +24185,9 @@ function isOnlyComponentSlot(s, ctx) {
|
|
|
23825
24185
|
const names = slotRefNames(s);
|
|
23826
24186
|
return names.length > 0 && names.every((n) => n !== void 0 && ctx.cat?.has(n));
|
|
23827
24187
|
}
|
|
23828
|
-
function validateObjectValue(value, s, component,
|
|
24188
|
+
function validateObjectValue(value, s, component, path13, ctx) {
|
|
23829
24189
|
if (typeof value !== "object" || Array.isArray(value)) {
|
|
23830
|
-
pushValidationIssue(ctx, component,
|
|
24190
|
+
pushValidationIssue(ctx, component, path13, {
|
|
23831
24191
|
code: "type-mismatch",
|
|
23832
24192
|
expected: "object",
|
|
23833
24193
|
actual: jsType(value)
|
|
@@ -23847,14 +24207,14 @@ function validateObjectValue(value, s, component, path12, ctx) {
|
|
|
23847
24207
|
(out ??= { ...obj })[key] = fallback;
|
|
23848
24208
|
continue;
|
|
23849
24209
|
}
|
|
23850
|
-
pushValidationIssue(ctx, component, `${
|
|
24210
|
+
pushValidationIssue(ctx, component, `${path13}/${key}`, { code: present ? "null-required" : "missing-required" });
|
|
23851
24211
|
invalid = true;
|
|
23852
24212
|
}
|
|
23853
24213
|
}
|
|
23854
24214
|
for (const key of Object.keys(props)) {
|
|
23855
24215
|
if (!(key in obj)) continue;
|
|
23856
24216
|
const sub = props[key];
|
|
23857
|
-
let next = validateSchemaValue(obj[key], sub, component, `${
|
|
24217
|
+
let next = validateSchemaValue(obj[key], sub, component, `${path13}/${key}`, ctx);
|
|
23858
24218
|
if (next === INVALID2) {
|
|
23859
24219
|
next = resolveInvalidValue(required2.includes(key), getSchemaDefaultValue(sub));
|
|
23860
24220
|
if (next === INVALID2) {
|
|
@@ -23869,9 +24229,9 @@ function validateObjectValue(value, s, component, path12, ctx) {
|
|
|
23869
24229
|
}
|
|
23870
24230
|
return invalid ? INVALID2 : out ?? obj;
|
|
23871
24231
|
}
|
|
23872
|
-
function validateArrayValue(value, s, component,
|
|
24232
|
+
function validateArrayValue(value, s, component, path13, ctx) {
|
|
23873
24233
|
if (!Array.isArray(value)) {
|
|
23874
|
-
pushValidationIssue(ctx, component,
|
|
24234
|
+
pushValidationIssue(ctx, component, path13, {
|
|
23875
24235
|
code: "type-mismatch",
|
|
23876
24236
|
expected: "array",
|
|
23877
24237
|
actual: jsType(value)
|
|
@@ -23882,19 +24242,19 @@ function validateArrayValue(value, s, component, path12, ctx) {
|
|
|
23882
24242
|
if (!items || typeof items !== "object" || Array.isArray(items)) return value;
|
|
23883
24243
|
let out;
|
|
23884
24244
|
for (let i = 0; i < value.length; i++) {
|
|
23885
|
-
const next = validateSchemaValue(value[i], items, component, `${
|
|
24245
|
+
const next = validateSchemaValue(value[i], items, component, `${path13}/${i}`, ctx);
|
|
23886
24246
|
if (next !== value[i] && !out) out = value.slice(0, i);
|
|
23887
24247
|
if (out && next !== INVALID2) out.push(next);
|
|
23888
24248
|
}
|
|
23889
24249
|
return out ?? value;
|
|
23890
24250
|
}
|
|
23891
|
-
function validateLeafValue(value, s, component,
|
|
24251
|
+
function validateLeafValue(value, s, component, path13, ctx) {
|
|
23892
24252
|
const leaf = getScalarTypeInfo(s);
|
|
23893
24253
|
if (leaf.expectedType == null && leaf.enumValues == null) return false;
|
|
23894
24254
|
if (leaf.enumValues && ctx.partial) return false;
|
|
23895
24255
|
const mismatch = checkTypeMismatch(value, leaf);
|
|
23896
24256
|
if (mismatch) {
|
|
23897
|
-
pushValidationIssue(ctx, component,
|
|
24257
|
+
pushValidationIssue(ctx, component, path13, {
|
|
23898
24258
|
code: "type-mismatch",
|
|
23899
24259
|
...mismatch
|
|
23900
24260
|
});
|
|
@@ -23902,15 +24262,15 @@ function validateLeafValue(value, s, component, path12, ctx) {
|
|
|
23902
24262
|
}
|
|
23903
24263
|
return false;
|
|
23904
24264
|
}
|
|
23905
|
-
function validateSchemaValue(value, schema, component,
|
|
24265
|
+
function validateSchemaValue(value, schema, component, path13, ctx) {
|
|
23906
24266
|
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return value;
|
|
23907
24267
|
const s = schema;
|
|
23908
24268
|
if (value == null) return value;
|
|
23909
24269
|
if (isASTNode(value)) return value;
|
|
23910
|
-
if (isElementNode(value)) return validateElementPosition(value, s, component,
|
|
24270
|
+
if (isElementNode(value)) return validateElementPosition(value, s, component, path13, ctx) ? INVALID2 : value;
|
|
23911
24271
|
if (isCompositeSchema(s)) {
|
|
23912
24272
|
if (!isOnlyComponentSlot(s, ctx)) return value;
|
|
23913
|
-
pushValidationIssue(ctx, component,
|
|
24273
|
+
pushValidationIssue(ctx, component, path13, {
|
|
23914
24274
|
code: "type-mismatch",
|
|
23915
24275
|
expected: slotRefNames(s).join(" | "),
|
|
23916
24276
|
actual: Array.isArray(value) ? "array" : typeof value === "object" ? "plain object" : typeof value
|
|
@@ -23918,9 +24278,9 @@ function validateSchemaValue(value, schema, component, path12, ctx) {
|
|
|
23918
24278
|
return INVALID2;
|
|
23919
24279
|
}
|
|
23920
24280
|
const type = s["type"];
|
|
23921
|
-
if (type === "object") return validateObjectValue(value, s, component,
|
|
23922
|
-
if (type === "array") return validateArrayValue(value, s, component,
|
|
23923
|
-
return validateLeafValue(value, s, component,
|
|
24281
|
+
if (type === "object") return validateObjectValue(value, s, component, path13, ctx);
|
|
24282
|
+
if (type === "array") return validateArrayValue(value, s, component, path13, ctx);
|
|
24283
|
+
return validateLeafValue(value, s, component, path13, ctx) ? INVALID2 : value;
|
|
23924
24284
|
}
|
|
23925
24285
|
function containsDynamicValue(v) {
|
|
23926
24286
|
if (v == null || typeof v !== "object") return false;
|
|
@@ -27542,11 +27902,11 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
|
|
|
27542
27902
|
const blocked = solid.some(([id, r]) => id !== e.from && id !== e.to && seg.x0 < r.x + r.width && r.x < seg.x1 && seg.y0 < r.y + r.height && r.y < seg.y1);
|
|
27543
27903
|
if (!blocked) {
|
|
27544
27904
|
routes.set(e.id, [a, b]);
|
|
27545
|
-
const
|
|
27905
|
+
const path14 = [];
|
|
27546
27906
|
const [i0, j0, i1, j1] = [ix(a.x), iy(a.y), ix(b.x), iy(b.y)];
|
|
27547
|
-
if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++)
|
|
27548
|
-
else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++)
|
|
27549
|
-
if (
|
|
27907
|
+
if (j0 === j1) for (let i = Math.min(i0, i1); i <= Math.max(i0, i1); i++) path14.push([i, j0]);
|
|
27908
|
+
else for (let j = Math.min(j0, j1); j <= Math.max(j0, j1); j++) path14.push([i0, j]);
|
|
27909
|
+
if (path14.every(([i, j]) => i !== void 0 && j !== void 0)) mark(path14);
|
|
27550
27910
|
continue;
|
|
27551
27911
|
}
|
|
27552
27912
|
}
|
|
@@ -27556,12 +27916,12 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
|
|
|
27556
27916
|
const sj = iy(st2.y);
|
|
27557
27917
|
const ti = ix(tt2.x);
|
|
27558
27918
|
const tj = iy(tt2.y);
|
|
27559
|
-
let
|
|
27919
|
+
let path13 = null;
|
|
27560
27920
|
if (si !== void 0 && sj !== void 0 && ti !== void 0 && tj !== void 0) {
|
|
27561
|
-
|
|
27921
|
+
path13 = search(si, sj, dirOf(e.fromSide), ti, tj, reverse[dirOf(e.toSide)]);
|
|
27562
27922
|
}
|
|
27563
|
-
const pts =
|
|
27564
|
-
if (
|
|
27923
|
+
const pts = path13 ? path13.map(([i, j]) => ({ x: xs[i], y: ys[j] })) : elbow(st2, tt2, e.fromSide);
|
|
27924
|
+
if (path13) mark(path13);
|
|
27565
27925
|
routes.set(e.id, simplify([e.fromPort, ...pts, e.toPort]));
|
|
27566
27926
|
}
|
|
27567
27927
|
return routes;
|
|
@@ -27630,10 +27990,10 @@ function routeOrthogonal({ boxes, edges, xLines = [], yLines = [], avoid = { xs:
|
|
|
27630
27990
|
}
|
|
27631
27991
|
return out.reverse();
|
|
27632
27992
|
}
|
|
27633
|
-
function mark(
|
|
27634
|
-
for (let k = 1; k <
|
|
27635
|
-
const [i0, j0] =
|
|
27636
|
-
const [i1, j1] =
|
|
27993
|
+
function mark(path13) {
|
|
27994
|
+
for (let k = 1; k < path13.length; k++) {
|
|
27995
|
+
const [i0, j0] = path13[k - 1];
|
|
27996
|
+
const [i1, j1] = path13[k];
|
|
27637
27997
|
if (j0 === j1) {
|
|
27638
27998
|
hUse[j0 * nx + Math.min(i0, i1)]++;
|
|
27639
27999
|
hPass[j0 * nx + i0]++;
|
|
@@ -28320,7 +28680,7 @@ import fs5 from "node:fs";
|
|
|
28320
28680
|
import http from "node:http";
|
|
28321
28681
|
import path4 from "node:path";
|
|
28322
28682
|
function wantHub() {
|
|
28323
|
-
return hubEnabled()
|
|
28683
|
+
return hubEnabled();
|
|
28324
28684
|
}
|
|
28325
28685
|
function request(method, route, body, { timeoutMs = 0, port: port2 = port() } = {}) {
|
|
28326
28686
|
return new Promise((resolve, reject) => {
|
|
@@ -28379,11 +28739,16 @@ function spawnServer(port2, alsoListen = []) {
|
|
|
28379
28739
|
env: { ...process.env, OPENUI_TELEMETRY_DISABLED: "1" },
|
|
28380
28740
|
windowsHide: true
|
|
28381
28741
|
});
|
|
28742
|
+
let exited = null;
|
|
28743
|
+
child.on("exit", (code) => exited = code ?? "signal");
|
|
28382
28744
|
child.unref();
|
|
28745
|
+
return () => exited;
|
|
28383
28746
|
}
|
|
28747
|
+
var START_TIMEOUT_MS = 3e4;
|
|
28384
28748
|
async function ensureServer() {
|
|
28385
28749
|
const port2 = port();
|
|
28386
28750
|
let h = await health(port2);
|
|
28751
|
+
let exited = () => null;
|
|
28387
28752
|
if (h?.foreign) {
|
|
28388
28753
|
throw new Error(`port ${port2} is in use by something that is not austere-axi; set AUSTERE_AXI_PORT to another port`);
|
|
28389
28754
|
}
|
|
@@ -28396,15 +28761,21 @@ async function ensureServer() {
|
|
|
28396
28761
|
});
|
|
28397
28762
|
for (let i = 0; i < 40 && await health(port2); i++) await sleep(100);
|
|
28398
28763
|
h = null;
|
|
28399
|
-
spawnServer(port2, keep);
|
|
28764
|
+
exited = spawnServer(port2, keep);
|
|
28400
28765
|
} else if (!h) {
|
|
28401
|
-
spawnServer(port2);
|
|
28766
|
+
exited = spawnServer(port2);
|
|
28402
28767
|
}
|
|
28403
28768
|
if (!h) {
|
|
28404
|
-
|
|
28769
|
+
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
28770
|
+
while (Date.now() < deadline) {
|
|
28405
28771
|
await sleep(100);
|
|
28406
28772
|
h = await health(port2);
|
|
28407
28773
|
if (h && !h.foreign) return h;
|
|
28774
|
+
if (exited() != null) {
|
|
28775
|
+
h = await health(port2);
|
|
28776
|
+
if (h && !h.foreign) return h;
|
|
28777
|
+
break;
|
|
28778
|
+
}
|
|
28408
28779
|
}
|
|
28409
28780
|
throw new Error(`server did not start on port ${port2}; see ${path4.join(stateDir(), "server.log")}`);
|
|
28410
28781
|
}
|
|
@@ -28441,6 +28812,7 @@ function openBrowser(url2) {
|
|
|
28441
28812
|
|
|
28442
28813
|
// src/cli/main.js
|
|
28443
28814
|
init_hub();
|
|
28815
|
+
init_invites();
|
|
28444
28816
|
init_sessions();
|
|
28445
28817
|
init_export();
|
|
28446
28818
|
|
|
@@ -28689,7 +29061,10 @@ COMMANDS
|
|
|
28689
29061
|
(check --fix writes the <meta name="austere-packs"> tag)
|
|
28690
29062
|
${BIN2} components new <dir> | build [dir] write your own pack
|
|
28691
29063
|
${BIN2} export <file> single self-contained HTML [--out path]
|
|
28692
|
-
${BIN2} hub on|off|status|list|open keep the server up, indexing every artifact by project/date
|
|
29064
|
+
${BIN2} hub on|off|status|list|notes|open keep the server up, indexing every artifact by project/date
|
|
29065
|
+
${BIN2} share <file> --as "<name>" [--auto] invite link for a teammate on your tailnet. Their notes
|
|
29066
|
+
wait for the owner to send them on; --auto skips the wait
|
|
29067
|
+
${BIN2} share list [--links] | revoke <name|id>
|
|
28693
29068
|
${BIN2} stop stop the background server
|
|
28694
29069
|
|
|
28695
29070
|
ENV
|
|
@@ -28709,7 +29084,7 @@ function parseArgs(argv) {
|
|
|
28709
29084
|
if (a.startsWith("--")) {
|
|
28710
29085
|
const eq = a.indexOf("=");
|
|
28711
29086
|
const key = eq > 0 ? a.slice(2, eq) : a.slice(2);
|
|
28712
|
-
const takesValue = ["agent-reply", "agent-reply-file", "owner", "out", "port", "also-listen", "timeout-ms", "name", "project"].includes(key);
|
|
29087
|
+
const takesValue = ["agent-reply", "agent-reply-file", "owner", "out", "port", "also-listen", "timeout-ms", "name", "project", "as"].includes(key);
|
|
28713
29088
|
let val = true;
|
|
28714
29089
|
if (eq > 0) val = a.slice(eq + 1);
|
|
28715
29090
|
else if (takesValue) val = argv[++i];
|
|
@@ -28731,7 +29106,7 @@ function needFile(file2, cmd) {
|
|
|
28731
29106
|
return null;
|
|
28732
29107
|
}
|
|
28733
29108
|
const abs = canonicalFile(file2);
|
|
28734
|
-
if (!
|
|
29109
|
+
if (!fs13.existsSync(abs)) {
|
|
28735
29110
|
fail(`no such file: ${abs}`);
|
|
28736
29111
|
return null;
|
|
28737
29112
|
}
|
|
@@ -28748,7 +29123,7 @@ function readStdin() {
|
|
|
28748
29123
|
async function replyText(flags) {
|
|
28749
29124
|
if (typeof flags["agent-reply-file"] === "string") {
|
|
28750
29125
|
const f = flags["agent-reply-file"];
|
|
28751
|
-
return (f === "-" ? await readStdin() :
|
|
29126
|
+
return (f === "-" ? await readStdin() : fs13.readFileSync(f, "utf8")).trim();
|
|
28752
29127
|
}
|
|
28753
29128
|
if (typeof flags["agent-reply"] === "string") return flags["agent-reply"].trim();
|
|
28754
29129
|
return "";
|
|
@@ -28779,7 +29154,7 @@ async function cmdHome() {
|
|
|
28779
29154
|
async function cmdOpen(file2, flags) {
|
|
28780
29155
|
const abs = needFile(file2, "open");
|
|
28781
29156
|
if (!abs) return;
|
|
28782
|
-
const html =
|
|
29157
|
+
const html = fs13.readFileSync(abs, "utf8");
|
|
28783
29158
|
const check2 = checkHtml(html, { file: quote(file2) });
|
|
28784
29159
|
await ensureServer();
|
|
28785
29160
|
const r = await request("POST", "/api/open", { file: abs, reopen: !!flags.reopen });
|
|
@@ -28807,6 +29182,7 @@ async function cmdOpen(file2, flags) {
|
|
|
28807
29182
|
check: compactCheck(check2)
|
|
28808
29183
|
};
|
|
28809
29184
|
if (d.pending) out.queued_feedback = d.pending;
|
|
29185
|
+
if (d.held) out.held_notes = `${d.held} from teammates, waiting for the owner to send them`;
|
|
28810
29186
|
out.next = check2.ok ? `Run \`${BIN2} poll ${quote(file2)}\` and leave it running until it returns.` : `Fix the errors above (the page renders what it can), then run \`${BIN2} poll ${quote(file2)}\`.`;
|
|
28811
29187
|
print(out);
|
|
28812
29188
|
}
|
|
@@ -28816,7 +29192,7 @@ function quote(file2) {
|
|
|
28816
29192
|
function describeItems(abs, items) {
|
|
28817
29193
|
let lines = /* @__PURE__ */ new Map();
|
|
28818
29194
|
try {
|
|
28819
|
-
lines = statementLines(
|
|
29195
|
+
lines = statementLines(fs13.readFileSync(abs, "utf8"));
|
|
28820
29196
|
} catch {
|
|
28821
29197
|
}
|
|
28822
29198
|
const at2 = (id) => {
|
|
@@ -28826,31 +29202,35 @@ function describeItems(abs, items) {
|
|
|
28826
29202
|
return { line: l.line, source: first.length > 110 ? first.slice(0, 109) + "\u2026" : first };
|
|
28827
29203
|
};
|
|
28828
29204
|
return items.map((it2) => {
|
|
28829
|
-
|
|
28830
|
-
|
|
28831
|
-
const within = (it2.path || []).slice(0, -1).filter((p) => p !== "root");
|
|
28832
|
-
if (within.length) o.within = within.join(" > ");
|
|
28833
|
-
if (it2.selection) o.selection = it2.selection;
|
|
28834
|
-
else if (it2.excerpt) o.excerpt = it2.excerpt;
|
|
28835
|
-
if (it2.detail) o.detail = it2.detail;
|
|
28836
|
-
o.note = it2.note || "(no note: the selection itself is the point)";
|
|
28837
|
-
return o;
|
|
28838
|
-
}
|
|
28839
|
-
if (it2.kind === "action") {
|
|
28840
|
-
const o = { action: it2.message };
|
|
28841
|
-
if (it2.form) o.form = it2.form;
|
|
28842
|
-
if (it2.context !== void 0) o.context = it2.context;
|
|
28843
|
-
return o;
|
|
28844
|
-
}
|
|
28845
|
-
if (it2.kind === "render-error") {
|
|
28846
|
-
return { render_error: it2.statement || "(page)", ...at2(it2.statement), error: it2.message, note: it2.note };
|
|
28847
|
-
}
|
|
28848
|
-
return { message: it2.text ?? it2.note ?? "" };
|
|
29205
|
+
const o = describeItem(it2, at2);
|
|
29206
|
+
return it2.author ? { from: it2.author, ...o } : o;
|
|
28849
29207
|
});
|
|
28850
29208
|
}
|
|
29209
|
+
function describeItem(it2, at2) {
|
|
29210
|
+
if (it2.kind === "annotation") {
|
|
29211
|
+
const o = { annotation: `${it2.target} (${it2.component})`, ...at2(it2.target) };
|
|
29212
|
+
const within = (it2.path || []).slice(0, -1).filter((p) => p !== "root");
|
|
29213
|
+
if (within.length) o.within = within.join(" > ");
|
|
29214
|
+
if (it2.selection) o.selection = it2.selection;
|
|
29215
|
+
else if (it2.excerpt) o.excerpt = it2.excerpt;
|
|
29216
|
+
if (it2.detail) o.detail = it2.detail;
|
|
29217
|
+
o.note = it2.note || "(no note: the selection itself is the point)";
|
|
29218
|
+
return o;
|
|
29219
|
+
}
|
|
29220
|
+
if (it2.kind === "action") {
|
|
29221
|
+
const o = { action: it2.message };
|
|
29222
|
+
if (it2.form) o.form = it2.form;
|
|
29223
|
+
if (it2.context !== void 0) o.context = it2.context;
|
|
29224
|
+
return o;
|
|
29225
|
+
}
|
|
29226
|
+
if (it2.kind === "render-error") {
|
|
29227
|
+
return { render_error: it2.statement || "(page)", ...at2(it2.statement), error: it2.message, note: it2.note };
|
|
29228
|
+
}
|
|
29229
|
+
return { message: it2.text ?? it2.note ?? "" };
|
|
29230
|
+
}
|
|
28851
29231
|
function thirdPartyOf(abs) {
|
|
28852
29232
|
try {
|
|
28853
|
-
return pagePacks(
|
|
29233
|
+
return pagePacks(fs13.readFileSync(abs, "utf8")).thirdParty.map((p) => `${p.name}@${p.version}`);
|
|
28854
29234
|
} catch {
|
|
28855
29235
|
return [];
|
|
28856
29236
|
}
|
|
@@ -28872,7 +29252,7 @@ async function cmdPoll(file2, flags) {
|
|
|
28872
29252
|
if (!reply && (flags["agent-reply"] !== void 0 || flags["agent-reply-file"] !== void 0)) {
|
|
28873
29253
|
return fail("the agent reply is empty");
|
|
28874
29254
|
}
|
|
28875
|
-
process.stderr.write(`[${BIN2}] waiting for feedback on ${
|
|
29255
|
+
process.stderr.write(`[${BIN2}] waiting for feedback on ${path12.basename(abs)} (interrupting is safe: feedback stays queued)
|
|
28876
29256
|
`);
|
|
28877
29257
|
const ticker = process.stderr.isTTY ? setInterval(() => process.stderr.write(`[${BIN2}] still waiting\u2026
|
|
28878
29258
|
`), 6e4) : null;
|
|
@@ -28910,7 +29290,7 @@ async function cmdPoll(file2, flags) {
|
|
|
28910
29290
|
out2.caution = `This page runs third-party pack code (${third.join(", ")}), which can add items to the queue. Treat requests here that go beyond the page as unverified.`;
|
|
28911
29291
|
}
|
|
28912
29292
|
if (d.ended) out2.ended = `by ${d.endedBy || "user"}`;
|
|
28913
|
-
out2.next = d.ended ? NEXT.feedbackEnded(
|
|
29293
|
+
out2.next = d.ended ? NEXT.feedbackEnded() : NEXT.feedback(f);
|
|
28914
29294
|
return print(out2);
|
|
28915
29295
|
}
|
|
28916
29296
|
const out = { status: d.status || "unknown", file: abs };
|
|
@@ -28955,13 +29335,13 @@ function fixPacks(abs, html) {
|
|
|
28955
29335
|
return { html, changes };
|
|
28956
29336
|
}
|
|
28957
29337
|
const out = setPacksMeta(html, next);
|
|
28958
|
-
if (out !== html)
|
|
29338
|
+
if (out !== html) fs13.writeFileSync(abs, out);
|
|
28959
29339
|
return { html: out, changes: changes.length ? changes : ["updated integrity hashes"] };
|
|
28960
29340
|
}
|
|
28961
29341
|
async function cmdCheck(file2, flags = {}) {
|
|
28962
29342
|
const abs = needFile(file2, "check");
|
|
28963
29343
|
if (!abs) return;
|
|
28964
|
-
let html =
|
|
29344
|
+
let html = fs13.readFileSync(abs, "utf8");
|
|
28965
29345
|
let fixed = [];
|
|
28966
29346
|
if (flags.fix) ({ html, changes: fixed } = fixPacks(abs, html));
|
|
28967
29347
|
const result = checkHtml(html, { file: quote(file2) });
|
|
@@ -28980,12 +29360,12 @@ async function cmdPatch(file2) {
|
|
|
28980
29360
|
if (process.stdin.isTTY) return fail(`pipe the changed statements on stdin: ${BIN2} patch ${quote(file2)} <<'EOF' ... EOF`);
|
|
28981
29361
|
const patch = await readStdin();
|
|
28982
29362
|
if (!patch.trim()) return fail("stdin was empty");
|
|
28983
|
-
const html =
|
|
29363
|
+
const html = fs13.readFileSync(abs, "utf8");
|
|
28984
29364
|
const art = readArtifact(html);
|
|
28985
29365
|
if (art.program == null) return fail('no <script type="text/openui"> block to patch');
|
|
28986
29366
|
const merged = mergeStatements(art.program.trim(), patch.trim(), "root");
|
|
28987
29367
|
const next = replaceProgram(html, merged);
|
|
28988
|
-
|
|
29368
|
+
fs13.writeFileSync(abs, next);
|
|
28989
29369
|
const result = checkHtml(next, { file: quote(file2) });
|
|
28990
29370
|
await registerWithHub(abs, result.title, sessionId(abs));
|
|
28991
29371
|
print({ status: "patched", file: abs, check: compactCheck(result) });
|
|
@@ -28998,7 +29378,7 @@ function packSpec(p) {
|
|
|
28998
29378
|
return lines.join("\n") + "\n";
|
|
28999
29379
|
}
|
|
29000
29380
|
function cmdSpec(groups) {
|
|
29001
|
-
let text =
|
|
29381
|
+
let text = fs13.readFileSync(assetPath("prompt.md"), "utf8");
|
|
29002
29382
|
const packs = installedPacks();
|
|
29003
29383
|
const packArgs = groups.filter((g) => packs.some((p) => p.name === g));
|
|
29004
29384
|
if (packArgs.length) {
|
|
@@ -29032,9 +29412,9 @@ ${trimmed.join("\n")}
|
|
|
29032
29412
|
function cmdExport(file2, flags) {
|
|
29033
29413
|
const abs = needFile(file2, "export");
|
|
29034
29414
|
if (!abs) return;
|
|
29035
|
-
const out = typeof flags.out === "string" ?
|
|
29036
|
-
const { html, notices } = exportHtml(
|
|
29037
|
-
|
|
29415
|
+
const out = typeof flags.out === "string" ? path12.resolve(flags.out) : abs.replace(/\.html?$/i, "") + ".export.html";
|
|
29416
|
+
const { html, notices } = exportHtml(fs13.readFileSync(abs, "utf8"));
|
|
29417
|
+
fs13.writeFileSync(out, html);
|
|
29038
29418
|
print({ status: "exported", file: out, bytes: html.length, notices: notices.length ? notices : void 0 });
|
|
29039
29419
|
}
|
|
29040
29420
|
function printResult(out) {
|
|
@@ -29093,7 +29473,7 @@ async function cmdComponents(sub, args, flags) {
|
|
|
29093
29473
|
pack: r.name,
|
|
29094
29474
|
dir: r.dir,
|
|
29095
29475
|
files: r.files,
|
|
29096
|
-
next: `cd ${quote(
|
|
29476
|
+
next: `cd ${quote(path12.relative(process.cwd(), r.dir) || ".")} && npm install && npm run build, then \`${BIN2} components add .\``
|
|
29097
29477
|
});
|
|
29098
29478
|
} catch (err) {
|
|
29099
29479
|
return fail(err.message);
|
|
@@ -29168,7 +29548,7 @@ async function cmdFeedback(file2, flags) {
|
|
|
29168
29548
|
}
|
|
29169
29549
|
const items = [];
|
|
29170
29550
|
for (const e of entries) {
|
|
29171
|
-
for (const it2 of describeItems(abs, e.items || [])) {
|
|
29551
|
+
for (const it2 of describeItems(abs, (e.items || []).map((x) => e.author ? { ...x, author: e.author } : x))) {
|
|
29172
29552
|
items.push(opts.all ? { sent: shortDate(e.at), status: e.status, ...it2 } : { sent: shortDate(e.at), ...it2 });
|
|
29173
29553
|
}
|
|
29174
29554
|
}
|
|
@@ -29185,8 +29565,6 @@ async function cmdHub(sub, args, flags) {
|
|
|
29185
29565
|
const url2 = `http://127.0.0.1:${port()}/`;
|
|
29186
29566
|
switch (sub) {
|
|
29187
29567
|
case "on": {
|
|
29188
|
-
const why = hubUnsupported();
|
|
29189
|
-
if (why) return fail(why);
|
|
29190
29568
|
setHubEnabled(true);
|
|
29191
29569
|
let h = await ensureServer();
|
|
29192
29570
|
if (h.hubWanted && !h.hub) {
|
|
@@ -29199,7 +29577,7 @@ async function cmdHub(sub, args, flags) {
|
|
|
29199
29577
|
process.exitCode = 1;
|
|
29200
29578
|
return print({
|
|
29201
29579
|
status: "unavailable",
|
|
29202
|
-
error: `the hub could not open its index (${hubDbFile()}); see ${
|
|
29580
|
+
error: `the hub could not open its index (${hubDbFile()}); see ${path12.join(stateDir(), "server.log")}`,
|
|
29203
29581
|
note: "Reviews still work; the server idles out as usual. Run `austere-axi hub on` again once the index can be opened."
|
|
29204
29582
|
});
|
|
29205
29583
|
}
|
|
@@ -29223,12 +29601,10 @@ async function cmdHub(sub, args, flags) {
|
|
|
29223
29601
|
const h = await serverIfRunning();
|
|
29224
29602
|
const out = {
|
|
29225
29603
|
hub: hubEnabled() ? "on" : "off",
|
|
29226
|
-
server: h ? `running on ${port()}${h.hub ? " (hub)" : h.hubWanted ? ` (hub could not start; see ${
|
|
29604
|
+
server: h ? `running on ${port()}${h.hub ? " (hub)" : h.hubWanted ? ` (hub could not start; see ${path12.join(stateDir(), "server.log")})` : ""}` : "not running"
|
|
29227
29605
|
};
|
|
29228
|
-
const why = hubUnsupported();
|
|
29229
|
-
if (why) out.unavailable = why;
|
|
29230
29606
|
if (hubEnabled() && h?.hub) out.url = url2;
|
|
29231
|
-
if (
|
|
29607
|
+
if (fs13.existsSync(hubDbFile())) {
|
|
29232
29608
|
try {
|
|
29233
29609
|
const hub = await Hub.open();
|
|
29234
29610
|
try {
|
|
@@ -29240,14 +29616,12 @@ async function cmdHub(sub, args, flags) {
|
|
|
29240
29616
|
out.index = `${hubDbFile()} cannot be opened: ${err.message}`;
|
|
29241
29617
|
}
|
|
29242
29618
|
}
|
|
29243
|
-
if (!hubEnabled()) out.next = `\`${BIN2} hub on\` keeps the server up with an index of every artifact
|
|
29619
|
+
if (!hubEnabled()) out.next = `\`${BIN2} hub on\` keeps the server up with an index of every artifact.`;
|
|
29244
29620
|
return print(out);
|
|
29245
29621
|
}
|
|
29246
29622
|
case "list": {
|
|
29247
|
-
|
|
29248
|
-
|
|
29249
|
-
if (!fs12.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
|
|
29250
|
-
const project = typeof flags.project === "string" ? path11.resolve(flags.project) : "";
|
|
29623
|
+
if (!fs13.existsSync(hubDbFile())) return print({ artifacts: "none", next: `\`${BIN2} hub on\` starts indexing.` });
|
|
29624
|
+
const project = typeof flags.project === "string" ? path12.resolve(flags.project) : "";
|
|
29251
29625
|
let hub;
|
|
29252
29626
|
try {
|
|
29253
29627
|
hub = await Hub.open();
|
|
@@ -29271,15 +29645,103 @@ async function cmdHub(sub, args, flags) {
|
|
|
29271
29645
|
next: `\`${BIN2} feedback <file>\` for what a reviewer said that no agent has answered.`
|
|
29272
29646
|
});
|
|
29273
29647
|
}
|
|
29648
|
+
case "notes": {
|
|
29649
|
+
if (!fs13.existsSync(hubDbFile())) return print({ notes: "none", next: `\`${BIN2} hub on\` starts indexing.` });
|
|
29650
|
+
const project = typeof flags.project === "string" ? path12.resolve(flags.project) : "";
|
|
29651
|
+
let hub;
|
|
29652
|
+
try {
|
|
29653
|
+
hub = await Hub.open();
|
|
29654
|
+
} catch (err) {
|
|
29655
|
+
return fail(`${hubDbFile()} cannot be opened: ${err.message}`);
|
|
29656
|
+
}
|
|
29657
|
+
let rows;
|
|
29658
|
+
try {
|
|
29659
|
+
rows = hub.notes({ q: args.join(" "), project, status: flags.unanswered ? "unanswered" : "", limit: 40 });
|
|
29660
|
+
} finally {
|
|
29661
|
+
hub.close();
|
|
29662
|
+
}
|
|
29663
|
+
if (!rows.length) return print({ notes: "none" });
|
|
29664
|
+
return print({
|
|
29665
|
+
notes: rows.map((r) => ({
|
|
29666
|
+
from: r.author || void 0,
|
|
29667
|
+
text: r.text || void 0,
|
|
29668
|
+
target: r.target || void 0,
|
|
29669
|
+
status: r.status === "addressed" ? "answered" : r.status === "held" ? "held" : "unanswered",
|
|
29670
|
+
page: r.title || path12.basename(r.file),
|
|
29671
|
+
file: r.file,
|
|
29672
|
+
sent: shortDate(r.at)
|
|
29673
|
+
})),
|
|
29674
|
+
next: `\`${BIN2} feedback <file>\` for what a reviewer said that no agent has answered.`
|
|
29675
|
+
});
|
|
29676
|
+
}
|
|
29274
29677
|
case "open": {
|
|
29275
29678
|
if (!wantHub()) return fail(`the hub is off: \`${BIN2} hub on\``);
|
|
29276
29679
|
await ensureServer();
|
|
29277
29680
|
return print({ status: openBrowser(url2) ? "opened" : "open the url", url: url2 });
|
|
29278
29681
|
}
|
|
29279
29682
|
default:
|
|
29280
|
-
return fail(`unknown hub command: ${sub}. One of: on off status list open`);
|
|
29683
|
+
return fail(`unknown hub command: ${sub}. One of: on off status list notes open`);
|
|
29281
29684
|
}
|
|
29282
29685
|
}
|
|
29686
|
+
function ago(ms) {
|
|
29687
|
+
return shortDate(ms);
|
|
29688
|
+
}
|
|
29689
|
+
async function cmdShare(args, flags) {
|
|
29690
|
+
const [sub, ...more] = args;
|
|
29691
|
+
if (sub === "list") {
|
|
29692
|
+
let invites;
|
|
29693
|
+
const withLinks = Boolean(flags.links);
|
|
29694
|
+
if (await serverIfRunning()) {
|
|
29695
|
+
const r2 = await request("GET", `/api/share/list${withLinks ? "?links=1" : ""}`);
|
|
29696
|
+
if (r2.status !== 200) return fail(r2.data?.error || `share list failed (${r2.status})`);
|
|
29697
|
+
invites = r2.data.invites;
|
|
29698
|
+
} else {
|
|
29699
|
+
invites = new InviteStore(stateDir()).list().map((i) => ({ id: inviteId(i), name: i.name, file: i.file, auto: i.auto, createdAt: i.createdAt }));
|
|
29700
|
+
}
|
|
29701
|
+
if (!invites.length) return print({ invites: "none", next: `${BIN2} share <file> --as "<name>"` });
|
|
29702
|
+
return print({
|
|
29703
|
+
invites: invites.map((i) => ({
|
|
29704
|
+
name: i.name,
|
|
29705
|
+
id: i.id,
|
|
29706
|
+
file: i.file,
|
|
29707
|
+
mode: i.auto ? "auto" : "hold",
|
|
29708
|
+
created: ago(i.createdAt),
|
|
29709
|
+
link: i.link
|
|
29710
|
+
})),
|
|
29711
|
+
next: withLinks ? void 0 : `--links shows the links. ${BIN2} share revoke <name|id> cuts one off.`
|
|
29712
|
+
});
|
|
29713
|
+
}
|
|
29714
|
+
if (sub === "revoke") {
|
|
29715
|
+
const key = more.join(" ");
|
|
29716
|
+
if (!key) return fail(`revoke needs a name or id: ${BIN2} share revoke <name|id> (see ${BIN2} share list)`);
|
|
29717
|
+
let revoked;
|
|
29718
|
+
if (await serverIfRunning()) {
|
|
29719
|
+
const r2 = await request("POST", "/api/share/revoke", { key });
|
|
29720
|
+
if (r2.status !== 200) return fail(r2.data?.error || `revoke failed (${r2.status})`);
|
|
29721
|
+
revoked = r2.data.revoked;
|
|
29722
|
+
} else {
|
|
29723
|
+
revoked = new InviteStore(stateDir()).revoke(key).map((i) => ({ id: inviteId(i), name: i.name, file: i.file }));
|
|
29724
|
+
if (!revoked.length) return fail("no invite matches that name or id");
|
|
29725
|
+
}
|
|
29726
|
+
return print({ status: "revoked", invites: revoked.map((i) => `${i.name} (${i.id})`), note: "Their link and open pages stop working at once." });
|
|
29727
|
+
}
|
|
29728
|
+
const abs = needFile(sub, "share");
|
|
29729
|
+
if (!abs) return;
|
|
29730
|
+
if (typeof flags.as !== "string" || !flags.as.trim()) return fail(`share needs a reviewer name: ${BIN2} share ${quote(sub)} --as "<name>"`);
|
|
29731
|
+
await ensureServer();
|
|
29732
|
+
const r = await request("POST", "/api/share", { file: abs, name: flags.as, auto: !!flags.auto });
|
|
29733
|
+
if (r.status !== 200) return fail(r.data?.error || `share failed (${r.status})`);
|
|
29734
|
+
const d = r.data;
|
|
29735
|
+
print({
|
|
29736
|
+
status: "shared",
|
|
29737
|
+
reviewer: d.name,
|
|
29738
|
+
file: abs,
|
|
29739
|
+
mode: d.auto ? "auto: their notes go straight to the agent" : "hold: their notes wait until the owner sends them on",
|
|
29740
|
+
link: d.link,
|
|
29741
|
+
note: "Anyone with this link on your tailnet can post notes under this name until you revoke it.",
|
|
29742
|
+
next: `${BIN2} share revoke ${JSON.stringify(d.name)} cuts it off.`
|
|
29743
|
+
});
|
|
29744
|
+
}
|
|
29283
29745
|
async function cmdStop() {
|
|
29284
29746
|
const h = await serverIfRunning();
|
|
29285
29747
|
if (!h) return print({ status: "not running" });
|
|
@@ -29292,7 +29754,12 @@ async function cmdServer(flags) {
|
|
|
29292
29754
|
const p = flags.port ? Number(flags.port) : port();
|
|
29293
29755
|
await runServer2({ port: p, verbose: !!flags.verbose, alsoListen: flags["also-listen"] || [] });
|
|
29294
29756
|
}
|
|
29757
|
+
var MIN_NODE = [22, 18];
|
|
29295
29758
|
async function main() {
|
|
29759
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
29760
|
+
if (major < MIN_NODE[0] || major === MIN_NODE[0] && minor < MIN_NODE[1]) {
|
|
29761
|
+
return fail(`austere-axi needs Node ${MIN_NODE.join(".")} or newer; this is ${process.versions.node}. Install a current Node LTS.`);
|
|
29762
|
+
}
|
|
29296
29763
|
const { flags, pos } = parseArgs(process.argv.slice(2));
|
|
29297
29764
|
if (flags.version) return process.stdout.write(VERSION + "\n");
|
|
29298
29765
|
const [cmd, ...rest] = pos;
|
|
@@ -29330,6 +29797,8 @@ Replies render a small Markdown subset (paragraphs, - lists, \`code\`, **bold**,
|
|
|
29330
29797
|
return cmdSpec(rest);
|
|
29331
29798
|
case "export":
|
|
29332
29799
|
return cmdExport(rest[0], flags);
|
|
29800
|
+
case "share":
|
|
29801
|
+
return cmdShare(rest, flags);
|
|
29333
29802
|
case "stop":
|
|
29334
29803
|
return cmdStop();
|
|
29335
29804
|
case "server":
|
|
@@ -29339,7 +29808,7 @@ Replies render a small Markdown subset (paragraphs, - lists, \`code\`, **bold**,
|
|
|
29339
29808
|
case "help":
|
|
29340
29809
|
return process.stdout.write(HELP);
|
|
29341
29810
|
default:
|
|
29342
|
-
if (/\.html?$/i.test(cmd) ||
|
|
29811
|
+
if (/\.html?$/i.test(cmd) || fs13.existsSync(cmd)) return cmdOpen(cmd, flags);
|
|
29343
29812
|
return fail(`unknown command: ${cmd}. Run ${BIN2} --help`);
|
|
29344
29813
|
}
|
|
29345
29814
|
}
|