@instafy/cli 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bind/src/check.mjs +38 -4
- package/dist/bind/src/starters/compare/app.js +13 -6
- package/dist/cli.js +56 -11
- package/package.json +1 -1
package/dist/bind/src/check.mjs
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
// every string in its data files (data.json, data/**/*.json, records included). Items come from --expect and from
|
|
5
5
|
// an "expect" array in data.json. Text that only says an item is unavailable ("Not found yet: Tokyo", a data file's
|
|
6
6
|
// "missing" list) does not count as showing it: builders are told to say on the page what they could not add, and
|
|
7
|
-
// that line must not pass the check. On a page of options it also checks the values
|
|
8
|
-
// exported for the tests.
|
|
7
|
+
// that line must not pass the check. On a page of options it also checks the values and where each row comes from
|
|
8
|
+
// (below). Pure helpers are exported for the tests.
|
|
9
9
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
10
10
|
import fs from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
@@ -162,7 +162,9 @@ export function checkSite(dir, expect = []) {
|
|
|
162
162
|
// Each value on such a page names the page it was read on and the day ("checked 27 Oct on maker.com"), and a value
|
|
163
163
|
// nobody found says so with why; a dash or "n/a" is not a value. A filter from a stated limit ("under €600") counts
|
|
164
164
|
// only values the page shows: the starters' site.byFilter lists a row whose value is unknown apart ("Price
|
|
165
|
-
// unknown"), never as within the limit.
|
|
165
|
+
// unknown"), never as within the limit. A row of a comparison is a candidate a page read names (a value's or a
|
|
166
|
+
// "sources" page), or only a search result ("found": its site, drawn "Not checked"). These rules read the data the
|
|
167
|
+
// same way site.js and the compare starter draw it.
|
|
166
168
|
|
|
167
169
|
/** A value that only says nothing is known: blank, dashes, "?", "n/a", "unknown", "TBD" (site.js: no value). */
|
|
168
170
|
export const NO_VALUE = /^(?:[\s\-\u2010-\u2015?.\u00b7\u2026/]*|n\s*\/\s*a|unknown|tb[ad])$/i;
|
|
@@ -269,7 +271,38 @@ function liveValue(records, key) {
|
|
|
269
271
|
return NO_VALUE.test(String(newest.value).trim()) ? null : numberIn(String(newest.value));
|
|
270
272
|
}
|
|
271
273
|
|
|
272
|
-
/**
|
|
274
|
+
/** A page a row cites: a value with its source and https page, or a "sources" entry with an https page. */
|
|
275
|
+
const citesPage = (fact) => !!(fact && typeof fact === "object" && !fact.est && fact.src && httpsUrl(fact.url));
|
|
276
|
+
|
|
277
|
+
/** A site as a search result names it ("reviews.example.net"): a host name, not a word or an address. */
|
|
278
|
+
const SITE_NAME = /^(?=.{1,253}$)(?:[\p{L}\p{N}](?:[\p{L}\p{N}-]*[\p{L}\p{N}])?\.)+\p{L}[\p{L}\p{N}-]*$/u;
|
|
279
|
+
|
|
280
|
+
/** The rows of a comparison (the compare starter's data: "columns" and "items") that say nowhere where they come from:
|
|
281
|
+
* no value or "sources" entry cites a page that was read, and no "found" names the site of the search result that
|
|
282
|
+
* alone names them. Such a row is a guess or a placeholder ("Model 1"): [{item, problem}]. Judged by the data's
|
|
283
|
+
* shape, never by the row's words. A list of names the same turn writes (`--expect`, data.json's "expect") only says
|
|
284
|
+
* what the page must show; it never vouches for a row. */
|
|
285
|
+
export function uncitedRows(data) {
|
|
286
|
+
if (!isOptionsData(data) || !Array.isArray(data.columns)) return [];
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const item of data.items) {
|
|
289
|
+
if (!item || typeof item !== "object") continue;
|
|
290
|
+
const name = String(item.name ?? item.ref ?? "");
|
|
291
|
+
const facts = [...Object.values(item.facts && typeof item.facts === "object" ? item.facts : {}), ...(Array.isArray(item.more) ? item.more.map((m) => m?.fact) : [])];
|
|
292
|
+
const sources = Array.isArray(item.sources) ? item.sources : [];
|
|
293
|
+
if (facts.some(citesPage) || sources.some((s) => s && typeof s === "object" && s.name && httpsUrl(s.url))) continue;
|
|
294
|
+
if (typeof item.found === "string" && SITE_NAME.test(item.found.trim())) continue;
|
|
295
|
+
const label = name || "a row";
|
|
296
|
+
out.push({
|
|
297
|
+
item: label,
|
|
298
|
+
problem: `${label}: nothing says where this row comes from. Cite the page you read that names it (a value's "src" and "url", or "sources": [{"name", "url"}]). When only a search result names it, put that result's site in "found" ("found": "example.com"): the page then says it is not checked. Naming it in --expect or "expect" does not count. A row nothing named (a guess, "Model 1") does not belong on the page.`,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The values of a site's page of options, and where its rows come from: problems to fix before the card, and notes
|
|
305
|
+
* for the reply. */
|
|
273
306
|
export function checkValues(dir) {
|
|
274
307
|
const root = path.resolve(dir);
|
|
275
308
|
const problems = [];
|
|
@@ -294,6 +327,7 @@ export function checkValues(dir) {
|
|
|
294
327
|
const list = lacks.length > 1 ? `${lacks.slice(0, -1).join(", ")} or ${lacks[lacks.length - 1]}` : lacks[0];
|
|
295
328
|
if (lacks.length) problems.push(`${where}: "${valueOf(fact)}" has no ${list}: add where you read it and when ("checked 27 Oct on maker.com"), or leave the value out.${fact.src ? "" : " Without a source the page shows it as not checked."}`);
|
|
296
329
|
}
|
|
330
|
+
for (const row of uncitedRows(data)) problems.push(row.problem);
|
|
297
331
|
|
|
298
332
|
const filter = data.filter;
|
|
299
333
|
if (filter && typeof filter === "object") {
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
* unknown?: "Price unknown"}. Only a value the page shows counts: a row whose value is
|
|
15
15
|
* unknown is never listed as under $160; it is listed apart, under "Price unknown".
|
|
16
16
|
* items: [{ref: "option:a", name, why (one short line), badge?: "Previous model", note?, facts: {key: fact},
|
|
17
|
-
* store: {name, url, readable}, more?: [{label, fact}], sources?: [{name, url}]}]
|
|
17
|
+
* store: {name, url, readable}, more?: [{label, fact}], sources?: [{name, url}], found?: "example.com"}]
|
|
18
18
|
* every candidate a page you read named, also one nothing could be read for: its cells say
|
|
19
19
|
* "Unknown" (a table of unknowns). Empty only when no page named any: it reads "No options yet".
|
|
20
|
-
*
|
|
20
|
+
* A row cites a page you read that names it (a value's or a "sources" page). When only a
|
|
21
|
+
* search result names it, "found" is that result's site instead, and the row says "Not
|
|
22
|
+
* checked". `instafy bind check` fails a row with neither: a guess or a placeholder.
|
|
23
|
+
* unchecked: ["what was not checked", …] listed once in the footer, on an empty page too
|
|
21
24
|
* fact: {"v": "$145", "src": "Example Store", "url": "https://…", "date": "2026-10-17", "asof"?: "2026-08-05", "est": false}
|
|
22
25
|
* Each value cites its page and day ("checked 17 Oct on maker.com ↗"), and no cell is blank: a source you read that
|
|
23
26
|
* does not give the value is the fact without "v" ({"src", "url", "date"}): "Not listed · checked 17 Oct on
|
|
@@ -126,7 +129,9 @@
|
|
|
126
129
|
}
|
|
127
130
|
return site.trust(valueOf(item, col), { shared: isShared(src.cols, item, col) || ownRow(src.rows, item, col), label: label, unknown: "Unknown" });
|
|
128
131
|
}
|
|
129
|
-
function hasMore(item) { return !!(item.note || (item.more && item.more.length) || (item.sources && item.sources.length)); }
|
|
132
|
+
function hasMore(item) { return !!(item.note || found(item) || (item.more && item.more.length) || (item.sources && item.sources.length)); }
|
|
133
|
+
/** The site of the search result that alone names this row ("found"), or "". */
|
|
134
|
+
function found(item) { return typeof item.found === "string" ? item.found.trim().slice(0, 100) : ""; }
|
|
130
135
|
|
|
131
136
|
/** The "Sort by" select shows (and rows are sorted) only with 5 or more options and more than one way to sort. */
|
|
132
137
|
function sortShown(data) { return (data.sort || []).length > 1 && (data.items || []).length >= 5; }
|
|
@@ -235,8 +240,8 @@
|
|
|
235
240
|
sources = sources.filter(function (s) {
|
|
236
241
|
return !named.some(function (f) { return f.src === s.name || (site.host(f.url) && site.host(f.url) === site.host(s.url)); });
|
|
237
242
|
});
|
|
238
|
-
//
|
|
239
|
-
site.notes(
|
|
243
|
+
// what was not checked is said on an empty page too: it is why the page is empty
|
|
244
|
+
site.notes(data.unchecked, sources);
|
|
240
245
|
}
|
|
241
246
|
|
|
242
247
|
/** "2 without a price are listed last." when the sort key is missing for some rows. */
|
|
@@ -275,7 +280,8 @@
|
|
|
275
280
|
|
|
276
281
|
function nameBlock(item) {
|
|
277
282
|
return [
|
|
278
|
-
h("div", { "class": "name-line" }, h("span", { "class": "name", text: item.name }), item.badge ? h("span", { "class": "badge", text: item.badge }) : null
|
|
283
|
+
h("div", { "class": "name-line" }, h("span", { "class": "name", text: item.name }), item.badge ? h("span", { "class": "badge", text: item.badge }) : null,
|
|
284
|
+
found(item) ? h("span", { "class": "badge", text: "Not checked" }) : null),
|
|
279
285
|
item.why ? h("p", { "class": "cap", text: item.why }) : null
|
|
280
286
|
];
|
|
281
287
|
}
|
|
@@ -296,6 +302,7 @@
|
|
|
296
302
|
var sources = (item.sources || []).filter(function (s) { return s && !linked[s.url]; });
|
|
297
303
|
return h("div", { "class": "more" },
|
|
298
304
|
item.note ? h("p", { text: item.note }) : null,
|
|
305
|
+
found(item) ? h("p", { "class": "cap", text: "Named in a search result on " + found(item) + "." }) : null,
|
|
299
306
|
facts.length ? h("dl", { "class": "facts" }, facts.map(function (f) {
|
|
300
307
|
return h("div", null, h("dt", { text: f.label }), h("dd", null, site.trust(f.fact)));
|
|
301
308
|
})) : null,
|
package/dist/cli.js
CHANGED
|
@@ -1330,6 +1330,8 @@ var require_redaction = __commonJS({
|
|
|
1330
1330
|
const opaqueCandidatePattern = /[A-Za-z0-9][A-Za-z0-9_\-+/=~]{18,}[A-Za-z0-9=]/g;
|
|
1331
1331
|
const digitCodePattern = /(?<![\d.,][ \u00a0]?)(?:\d{6,8}|\d{3}[ \u00a0]\d{3}|\d{4}[ \u00a0]\d{4})(?![\d]|[.,]\d)/g;
|
|
1332
1332
|
const codeWordPattern = /(?<![\p{L}])(?:codes?|kode|otp|passcode|pin|tan|2fa|mfa|(?:best(?:ä|ae)tigungs|sicherheits|verifizierungs|verifikations|zugangs|anmelde|freigabe|authentifizierungs|wiederherstellungs|einmal)code)(?![\p{L}])/iu;
|
|
1333
|
+
const DIGIT_CODE_SOURCE = digitCodePattern.source;
|
|
1334
|
+
const CODE_WORD_SOURCE = codeWordPattern.source;
|
|
1333
1335
|
const NEARBY_CHARACTERS = 120;
|
|
1334
1336
|
const CODE_NEARBY_CHARACTERS = 32;
|
|
1335
1337
|
const HEX_NEARBY_CHARACTERS = 32;
|
|
@@ -1556,6 +1558,9 @@ var require_redaction = __commonJS({
|
|
|
1556
1558
|
CREDENTIAL_CONTEXT_SOURCE,
|
|
1557
1559
|
PLACEHOLDER_SOURCE,
|
|
1558
1560
|
MASKED_TEXT_SOURCE,
|
|
1561
|
+
DIGIT_CODE_SOURCE,
|
|
1562
|
+
CODE_WORD_SOURCE,
|
|
1563
|
+
CODE_NEARBY_CHARACTERS,
|
|
1559
1564
|
SECRET_SCREEN_MESSAGE,
|
|
1560
1565
|
hasCredentialWord: hasCredentialWord2,
|
|
1561
1566
|
looksRandom: looksRandom3,
|
|
@@ -9811,7 +9816,9 @@ async function runSiteCheck(opts) {
|
|
|
9811
9816
|
const port = parsePreviewPort(opts.port, env);
|
|
9812
9817
|
const sitePath = parseCheckPath(opts.path);
|
|
9813
9818
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
9814
|
-
const viewports = VIEWPORTS.filter(
|
|
9819
|
+
const viewports = VIEWPORTS.filter(
|
|
9820
|
+
(v) => !opts.phone && !opts.desktop ? true : opts.phone && v.name === "phone" || opts.desktop && v.name === "desktop"
|
|
9821
|
+
);
|
|
9815
9822
|
let outDir = null;
|
|
9816
9823
|
if (opts.outDir !== void 0) {
|
|
9817
9824
|
outDir = path12.resolve(opts.outDir);
|
|
@@ -9925,12 +9932,14 @@ async function runPreviewCheckCli(opts, io = { out: console.log, err: console.er
|
|
|
9925
9932
|
const port = parsePreviewPort(opts.port, env);
|
|
9926
9933
|
const sitePath = parseCheckPath(opts.path);
|
|
9927
9934
|
const wantsThumbnail = Boolean(io.uploadThumbnail && io.hasOpenPreview?.(port, sitePath));
|
|
9935
|
+
const takenAt = Date.now();
|
|
9928
9936
|
const result = await runSiteCheck({
|
|
9929
9937
|
port,
|
|
9930
9938
|
path: opts.path,
|
|
9931
9939
|
thumbnail: wantsThumbnail,
|
|
9932
9940
|
proxyServer: io.proxyServer,
|
|
9933
9941
|
phone: opts.phone,
|
|
9942
|
+
desktop: opts.desktop,
|
|
9934
9943
|
framed: opts.framed,
|
|
9935
9944
|
workspacePlaywright: opts.workspacePlaywright,
|
|
9936
9945
|
outDir: opts.out,
|
|
@@ -9945,7 +9954,9 @@ async function runPreviewCheckCli(opts, io = { out: console.log, err: console.er
|
|
|
9945
9954
|
}
|
|
9946
9955
|
if (wantsThumbnail && result.thumbnail && io.uploadThumbnail) {
|
|
9947
9956
|
const shot = result.thumbnail;
|
|
9948
|
-
|
|
9957
|
+
if ("skipped" in shot) await io.pictureTried?.({ port, path: sitePath, takenAt }).catch(() => {
|
|
9958
|
+
});
|
|
9959
|
+
const outcome = "skipped" in shot ? { status: "skipped", reason: shot.skipped } : await io.uploadThumbnail({ port, path: sitePath, image: shot.image, description: shot.description, takenAt }).catch((error) => ({ status: "failed", reason: firstLine(error) }));
|
|
9949
9960
|
if (outcome) {
|
|
9950
9961
|
result.report.thumbnail = outcome.status;
|
|
9951
9962
|
if (outcome.reason) result.report.thumbnailReason = outcome.reason;
|
|
@@ -9965,7 +9976,7 @@ async function runPreviewCheckCli(opts, io = { out: console.log, err: console.er
|
|
|
9965
9976
|
function registerPreviewCheckCommand(preview, card = {}) {
|
|
9966
9977
|
return preview.command("check").description(
|
|
9967
9978
|
"Load a local site in a headless browser and report its title, errors, failed requests and a blank page; writes desktop and phone screenshots, and refreshes the picture on the card of a preview open on that port (exit 1: problems, 2: nothing answering, 3: port not allowed, 4: no browser, 64: bad options, 70: the check itself failed)"
|
|
9968
|
-
).option("--port <port>", "Local port the site listens on (1024-65535)").option("--path <path>", "Path to load (default /); a ?query and #hash are fine").option("--phone", "Only the phone size (390\xD7844); default: desktop 1280\xD7800 and phone").option("--framed", "Load the site inside a frame on another origin, and report a site that refuses to be framed").option("--workspace-playwright", "Also look for Playwright in this project's node_modules (runs the project's own copy)").option("--out <dir>", "Folder for desktop.png and phone.png (default: a new private folder under the temp folder each run; the report names it)").option("--timeout <ms>", "Page load timeout in ms (default 30000)").option("--wait <ms>", "Extra settle time after load in ms (default 600)").option("--json", "Output JSON").action(async (opts) => {
|
|
9979
|
+
).option("--port <port>", "Local port the site listens on (1024-65535)").option("--path <path>", "Path to load (default /); a ?query and #hash are fine").option("--phone", "Only the phone size (390\xD7844); default: desktop 1280\xD7800 and phone").option("--desktop", "Only the desktop size (1280\xD7800), the one the card's picture is taken from").option("--framed", "Load the site inside a frame on another origin, and report a site that refuses to be framed").option("--workspace-playwright", "Also look for Playwright in this project's node_modules (runs the project's own copy)").option("--out <dir>", "Folder for desktop.png and phone.png (default: a new private folder under the temp folder each run; the report names it)").option("--timeout <ms>", "Page load timeout in ms (default 30000)").option("--wait <ms>", "Extra settle time after load in ms (default 600)").option("--json", "Output JSON").action(async (opts) => {
|
|
9969
9980
|
process.exitCode = await runPreviewCheckCli(opts, { out: console.log, err: console.error, ...card });
|
|
9970
9981
|
});
|
|
9971
9982
|
}
|
|
@@ -10395,10 +10406,10 @@ function lockHolderIsGone(lockFile) {
|
|
|
10395
10406
|
return false;
|
|
10396
10407
|
}
|
|
10397
10408
|
}
|
|
10398
|
-
async function withStateLock(fn) {
|
|
10409
|
+
async function withStateLock(fn, waitMs = STATE_LOCK_WAIT_MS) {
|
|
10399
10410
|
const lockFile = previewStateLockFile();
|
|
10400
10411
|
fs12.mkdirSync(instafyDir(), { recursive: true });
|
|
10401
|
-
const deadline = Date.now() +
|
|
10412
|
+
const deadline = Date.now() + waitMs;
|
|
10402
10413
|
for (; ; ) {
|
|
10403
10414
|
try {
|
|
10404
10415
|
const fd = fs12.openSync(lockFile, "wx", 384);
|
|
@@ -11135,7 +11146,11 @@ ${started.logTail}` : "";
|
|
|
11135
11146
|
...binary ? { binary } : {},
|
|
11136
11147
|
...hostSuffix ? { hostSuffix } : {},
|
|
11137
11148
|
...studioOrigin ? { studioOrigin } : {},
|
|
11138
|
-
...siteRecord
|
|
11149
|
+
...siteRecord,
|
|
11150
|
+
...serve ? { siteDir: serve.cwd } : {},
|
|
11151
|
+
// The same preview opened again keeps its picture until a new one is uploaded.
|
|
11152
|
+
...current?.pictureAt ? { pictureAt: current.pictureAt } : {},
|
|
11153
|
+
...opts.thumbnail === false ? { noThumbnail: true } : {}
|
|
11139
11154
|
};
|
|
11140
11155
|
upsertPreviewState(entry);
|
|
11141
11156
|
return { grant, previewId, entry, current };
|
|
@@ -11241,8 +11256,10 @@ async function previewOpen(opts) {
|
|
|
11241
11256
|
thumbnail = { status: "skipped", reason: "the site is not live through the preview yet" };
|
|
11242
11257
|
} else {
|
|
11243
11258
|
if (!opts.thumbnailFile) log("Taking a picture of the site for its card\u2026");
|
|
11259
|
+
const takenAt = Date.now();
|
|
11244
11260
|
const attempt2 = opts.thumbnailFile ? readThumbnailFile(opts.thumbnailFile) : await captureSiteThumbnail({ port, path: sitePath, host: "127.0.0.1" });
|
|
11245
11261
|
thumbnail = "thumbnail" in attempt2 ? await uploadPreviewThumbnail({ controllerUrl, auth, projectId: projectId2, previewId, thumbnail: attempt2.thumbnail, retryCommand }) : { status: attempt2.status, reason: attempt2.reason };
|
|
11262
|
+
if (thumbnail.status === "uploaded" || !opts.thumbnailFile) await recordPictured(previewId, takenAt);
|
|
11246
11263
|
}
|
|
11247
11264
|
return {
|
|
11248
11265
|
previewId,
|
|
@@ -11286,12 +11303,30 @@ async function uploadPreviewThumbnail(params) {
|
|
|
11286
11303
|
return { status: "failed", reason: message.slice(0, 300) };
|
|
11287
11304
|
}
|
|
11288
11305
|
}
|
|
11306
|
+
var PICTURE_RECORD_LOCK_WAIT_MS = 5e3;
|
|
11307
|
+
async function recordPictured(previewId, takenAt) {
|
|
11308
|
+
try {
|
|
11309
|
+
await withStateLock(async () => {
|
|
11310
|
+
const state = readStateFile2();
|
|
11311
|
+
const entry = state.previews.find((preview) => preview.previewId === previewId);
|
|
11312
|
+
if (!entry || (entry.pictureAt ?? 0) >= takenAt) return;
|
|
11313
|
+
entry.pictureAt = takenAt;
|
|
11314
|
+
writeStateFile2(state);
|
|
11315
|
+
}, PICTURE_RECORD_LOCK_WAIT_MS);
|
|
11316
|
+
} catch {
|
|
11317
|
+
}
|
|
11318
|
+
}
|
|
11289
11319
|
function openPreviewFor(port, sitePath) {
|
|
11290
|
-
|
|
11320
|
+
const entry = readStateFile2().previews.find((preview) => preview.localPort === port && preview.path === sitePath && preview.pid > 0);
|
|
11321
|
+
return entry && !entry.noThumbnail ? entry : null;
|
|
11291
11322
|
}
|
|
11292
11323
|
function hasOpenPreviewFor(port, sitePath) {
|
|
11293
11324
|
return openPreviewFor(port, sitePath) !== null;
|
|
11294
11325
|
}
|
|
11326
|
+
async function recordCheckPictureTried(input3) {
|
|
11327
|
+
const entry = openPreviewFor(input3.port, input3.path);
|
|
11328
|
+
if (entry) await recordPictured(entry.previewId, input3.takenAt);
|
|
11329
|
+
}
|
|
11295
11330
|
async function uploadCheckThumbnail(input3) {
|
|
11296
11331
|
const entry = openPreviewFor(input3.port, input3.path);
|
|
11297
11332
|
if (!entry) return null;
|
|
@@ -11306,9 +11341,13 @@ async function uploadCheckThumbnail(input3) {
|
|
|
11306
11341
|
if (cleanUrl(controllerUrl) !== cleanUrl(entry.controllerUrl)) {
|
|
11307
11342
|
return { status: "skipped", reason: "the preview was opened on another server" };
|
|
11308
11343
|
}
|
|
11344
|
+
const takenAt = input3.takenAt ?? Date.now();
|
|
11309
11345
|
const facts = sniffImage(input3.image);
|
|
11310
|
-
if (!facts)
|
|
11311
|
-
|
|
11346
|
+
if (!facts) {
|
|
11347
|
+
await recordPictured(entry.previewId, takenAt);
|
|
11348
|
+
return { status: "skipped", reason: "the picture is not a JPEG" };
|
|
11349
|
+
}
|
|
11350
|
+
const outcome = await uploadPreviewThumbnail({
|
|
11312
11351
|
controllerUrl,
|
|
11313
11352
|
auth,
|
|
11314
11353
|
projectId: entry.projectId,
|
|
@@ -11316,6 +11355,8 @@ async function uploadCheckThumbnail(input3) {
|
|
|
11316
11355
|
thumbnail: { image: input3.image, ...facts, source: "capture", description: input3.description },
|
|
11317
11356
|
retryCommand
|
|
11318
11357
|
});
|
|
11358
|
+
await recordPictured(entry.previewId, takenAt);
|
|
11359
|
+
return outcome;
|
|
11319
11360
|
}
|
|
11320
11361
|
function toPreviewSummary(raw) {
|
|
11321
11362
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -13458,7 +13499,7 @@ function liveTurn(env, now) {
|
|
|
13458
13499
|
}
|
|
13459
13500
|
function keyless(chain) {
|
|
13460
13501
|
const find = chain === DEFAULT_CHAIN ? 'find official sites with `instafy web search "<name>" --chain reference-search` (no key) and read them' : "read the official sites you know of";
|
|
13461
|
-
return `Go on without it: ${find}, and
|
|
13502
|
+
return `Go on without it: ${find}, and for reviews a reviewer's home page and the pages it links, with \`instafy web read\`. Then answer or build the page with what you read, each missing fact marked unknown with why. The person may send more pages with Send page to Octo.`;
|
|
13462
13503
|
}
|
|
13463
13504
|
function tellFor(status, chain) {
|
|
13464
13505
|
switch (status) {
|
|
@@ -18916,7 +18957,11 @@ function exitWithPreviewError(error) {
|
|
|
18916
18957
|
}
|
|
18917
18958
|
var previewCommand = program.command("preview").description("Look at sites running in this space, and show them in Studio as a private preview card");
|
|
18918
18959
|
failWithGroupHelp(previewCommand);
|
|
18919
|
-
registerPreviewCheckCommand(previewCommand, {
|
|
18960
|
+
registerPreviewCheckCommand(previewCommand, {
|
|
18961
|
+
hasOpenPreview: hasOpenPreviewFor,
|
|
18962
|
+
uploadThumbnail: uploadCheckThumbnail,
|
|
18963
|
+
pictureTried: recordCheckPictureTried
|
|
18964
|
+
});
|
|
18920
18965
|
var webCommand = registerWebSearchCommand(registerWebLinkCommand(registerWebCommand(program)));
|
|
18921
18966
|
failWithGroupHelp(webCommand);
|
|
18922
18967
|
var webSettingsCommand = webCommand.command("settings").description("Inspect or change the space's shared research budget for new tasks");
|