dazzer-connect 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 +20 -6
- package/dist/index.js +271 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,12 +69,16 @@ Merge either one with any `mcpServers` entries you already have, save, and resta
|
|
|
69
69
|
|
|
70
70
|
## Put files into Dazzer
|
|
71
71
|
|
|
72
|
+
Dazzer takes a new file only with a description of it. A single new file needs an entry under its own name in a descriptions file, passed with `--described-in`, just as a folder does. The descriptions file is shown below.
|
|
73
|
+
|
|
72
74
|
```bash
|
|
73
75
|
dazzer add report.pdf
|
|
74
76
|
dazzer add ./backlog
|
|
75
77
|
```
|
|
76
78
|
|
|
77
|
-
A folder is walked with its subfolders. Hidden files are left alone, and nothing outside the folder is ever sent. The first time, you are asked which space things go in: run the same command again with `--space "<name>"`, or with `--keep-to-myself`. A folder run ends with a count of what went in and what was skipped. Add `--why` to see each file that was skipped, and why.
|
|
79
|
+
A folder is walked with its subfolders. Hidden files are left alone, and nothing outside the folder is ever sent. The first time, you are asked which space things go in: run the same command again with `--space "<name>"`, or with `--keep-to-myself`. A folder run ends with a count of what went in and what was skipped. Add `--why` to see each file that was skipped, and why. A run that left a file out exits with a failure code. A file already in Dazzer was not left out. In a folder run, neither was a file held while you are asked whether it is a newer version of one already there.
|
|
80
|
+
|
|
81
|
+
A folder run without a descriptions file sends nothing and says so.
|
|
78
82
|
|
|
79
83
|
### With what your assistant wrote about each file
|
|
80
84
|
|
|
@@ -104,7 +108,7 @@ Your own AI assistant can read the files and say what each one is. It writes tha
|
|
|
104
108
|
}
|
|
105
109
|
```
|
|
106
110
|
|
|
107
|
-
Save it beside the folder, never inside it — for example as `backlog-descriptions.json` next to `backlog`. Inside the folder,
|
|
111
|
+
Save it beside the folder, never inside it — for example as `backlog-descriptions.json` next to `backlog`. Inside the folder, it is one more file a run has to tell apart from the documents. Then:
|
|
108
112
|
|
|
109
113
|
```bash
|
|
110
114
|
dazzer add ./backlog --described-in ./backlog-descriptions.json
|
|
@@ -113,14 +117,24 @@ dazzer add ./backlog --described-in ./backlog-descriptions.json
|
|
|
113
117
|
Each file goes with its own entry. The descriptions file named by `--described-in` is not sent with them, even when it sits inside the folder, and the run says to move it.
|
|
114
118
|
|
|
115
119
|
- Each entry's form is checked before its file is sent. An entry in a form Dazzer refuses, such as a date written as "March 2026", holds its file back, and a line names the field and the form it takes. What the entry says is judged by Dazzer once the file has arrived.
|
|
116
|
-
- A file with no entry
|
|
117
|
-
- An entry that names no file being added is reported, and nothing is sent for it. If no entry names a file being added, nothing is sent at all.
|
|
118
|
-
- A file Dazzer refuses
|
|
119
|
-
- Whatever Dazzer did not keep of an entry is printed file by file,
|
|
120
|
+
- A file with no entry, or whose entry has no description, is not sent. The count says how many, and names them while there are few.
|
|
121
|
+
- An entry that names no file being added is reported, and nothing is sent for it. If no entry names a file being added, or none has a description, nothing is sent at all.
|
|
122
|
+
- A file Dazzer refuses is named, with why it was not added. A file refused for its kind or its size is only counted, and `--why` names it.
|
|
123
|
+
- Whatever Dazzer did not keep of an entry is printed file by file, in plain words.
|
|
120
124
|
- Sent again, a folder already in Dazzer says how many files took the description sent now, how many already had one, and how many did not take the one sent.
|
|
121
125
|
|
|
122
126
|
An assistant that can run commands runs this one itself. One that cannot gives you the descriptions file to save beside the folder, and the line to run.
|
|
123
127
|
|
|
128
|
+
## Keep it up to date
|
|
129
|
+
|
|
130
|
+
Dazzer takes files only from a recent enough copy of this tool. An older copy is told so on its first call, before it sends anything, with the one command that updates it:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npm install -g dazzer-connect@latest
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Once a day, the tool also tells you when a newer version is out. It looks at most once a day, never makes a command wait for the answer, and stays quiet if it cannot find out. What it remembers sits in the same settings folder as the rest of its state, below.
|
|
137
|
+
|
|
124
138
|
## Other commands
|
|
125
139
|
|
|
126
140
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,10 @@ import { Command } from "commander";
|
|
|
8
8
|
import { open, stat as statPath } from "node:fs/promises";
|
|
9
9
|
import { basename, extname } from "node:path";
|
|
10
10
|
|
|
11
|
+
// src/lib/api-client.ts
|
|
12
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
11
15
|
// src/lib/output.ts
|
|
12
16
|
function writeOut(message) {
|
|
13
17
|
process.stdout.write(`${message}
|
|
@@ -19,6 +23,8 @@ function writeErr(message) {
|
|
|
19
23
|
}
|
|
20
24
|
var CANNOT_REACH_DAZZER = "I can't reach Dazzer right now \u2014 nothing was sent. Try again shortly.";
|
|
21
25
|
var SIGN_IN_NO_LONGER_VALID = "Your Dazzer sign-in is no longer valid. Reconnect your client (or run `dazzer auth login`).";
|
|
26
|
+
var UPDATE_THE_TOOL = "npm install -g dazzer-connect@latest";
|
|
27
|
+
var newerToolIsOut = (version2) => `Version ${version2} of dazzer is out. Update it with: ${UPDATE_THE_TOOL}`;
|
|
22
28
|
function printDeviceInstructions(deviceAuth) {
|
|
23
29
|
writeOut("");
|
|
24
30
|
if (deviceAuth.verification_uri_complete) {
|
|
@@ -315,6 +321,31 @@ function refusalPlaces(body) {
|
|
|
315
321
|
...typeof keep === "boolean" ? { keepToYourself: keep } : {}
|
|
316
322
|
};
|
|
317
323
|
}
|
|
324
|
+
var wordsIn = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
325
|
+
function refusalAbout(body) {
|
|
326
|
+
if (typeof body !== "object" || body === null) return {};
|
|
327
|
+
const flat = body;
|
|
328
|
+
const enveloped = typeof flat.error === "object" && flat.error !== null ? flat.error.details : void 0;
|
|
329
|
+
const fields = [
|
|
330
|
+
...wordsIn(flat.field) === void 0 ? [] : [flat.field],
|
|
331
|
+
...Array.isArray(enveloped) ? enveloped.flatMap((detail) => {
|
|
332
|
+
const named2 = typeof detail === "object" && detail !== null ? wordsIn(detail.field) : void 0;
|
|
333
|
+
return named2 === void 0 ? [] : [named2];
|
|
334
|
+
}) : []
|
|
335
|
+
];
|
|
336
|
+
const dropped = Array.isArray(flat.dropped) ? flat.dropped.flatMap((entry) => {
|
|
337
|
+
if (typeof entry !== "object" || entry === null) return [];
|
|
338
|
+
const line = wordsIn(entry.line);
|
|
339
|
+
const reason = wordsIn(entry.reason);
|
|
340
|
+
return line === void 0 || reason === void 0 ? [] : [{ line, reason }];
|
|
341
|
+
}) : [];
|
|
342
|
+
const howMuch = typeof flat.how_much_was_read === "object" && flat.how_much_was_read !== null ? wordsIn(flat.how_much_was_read.reason) : void 0;
|
|
343
|
+
return {
|
|
344
|
+
...fields.length > 0 ? { fields } : {},
|
|
345
|
+
...dropped.length > 0 ? { dropped } : {},
|
|
346
|
+
...howMuch === void 0 ? {} : { howMuchWasRead: howMuch }
|
|
347
|
+
};
|
|
348
|
+
}
|
|
318
349
|
async function resolveCredential(auth2) {
|
|
319
350
|
let held;
|
|
320
351
|
try {
|
|
@@ -384,15 +415,99 @@ async function callDazzer(request) {
|
|
|
384
415
|
httpStatus: res.status,
|
|
385
416
|
...detail === void 0 ? {} : { detail },
|
|
386
417
|
...code === void 0 ? {} : { code },
|
|
387
|
-
...refusalPlaces(body)
|
|
418
|
+
...refusalPlaces(body),
|
|
419
|
+
...refusalAbout(body)
|
|
388
420
|
};
|
|
389
421
|
}
|
|
390
422
|
return { status: "ok", httpStatus: res.status, body };
|
|
391
423
|
}
|
|
424
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
425
|
+
var LOOK_AT_MOST_MS = 3e3;
|
|
426
|
+
var A_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
427
|
+
var NEWER_TOOL_FILE = "newer-version.json";
|
|
428
|
+
function releaseOf(version2) {
|
|
429
|
+
if (typeof version2 !== "string") return null;
|
|
430
|
+
const said = /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/.exec(version2);
|
|
431
|
+
return said === null ? null : [Number(said[1]), Number(said[2]), Number(said[3])];
|
|
432
|
+
}
|
|
433
|
+
function isLater(published, mine) {
|
|
434
|
+
const theirs = releaseOf(published);
|
|
435
|
+
const own = /^(\d{1,6}\.\d{1,6}\.\d{1,6})(-[^+]+)?(\+.+)?$/.exec(mine);
|
|
436
|
+
const ours = own === null ? null : releaseOf(own[1]);
|
|
437
|
+
if (theirs === null || own === null || ours === null) return false;
|
|
438
|
+
for (let at = 0; at < 3; at += 1) {
|
|
439
|
+
if (theirs[at] !== ours[at]) return (theirs[at] ?? 0) > (ours[at] ?? 0);
|
|
440
|
+
}
|
|
441
|
+
return own[2] !== void 0;
|
|
442
|
+
}
|
|
443
|
+
async function recall(file) {
|
|
444
|
+
try {
|
|
445
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
446
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
447
|
+
} catch {
|
|
448
|
+
return {};
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function remember(folder, file, what) {
|
|
452
|
+
try {
|
|
453
|
+
await mkdir(folder, { recursive: true });
|
|
454
|
+
await writeFile(file, `${JSON.stringify(what)}
|
|
455
|
+
`, "utf8");
|
|
456
|
+
return true;
|
|
457
|
+
} catch {
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
var withinADay = (then, now) => typeof then === "number" && Number.isFinite(then) && then <= now && now - then < A_DAY_MS;
|
|
462
|
+
function lookForNewerTool(check) {
|
|
463
|
+
const now = check.now ?? Date.now;
|
|
464
|
+
const file = join(check.folder, NEWER_TOOL_FILE);
|
|
465
|
+
const stop = new AbortController();
|
|
466
|
+
const looked = (async () => {
|
|
467
|
+
try {
|
|
468
|
+
const before = await recall(file);
|
|
469
|
+
if (withinADay(before.looked_at, now()) || stop.signal.aborted) return;
|
|
470
|
+
const signal = AbortSignal.any([
|
|
471
|
+
stop.signal,
|
|
472
|
+
AbortSignal.timeout(check.lookAtMostMs ?? LOOK_AT_MOST_MS)
|
|
473
|
+
]);
|
|
474
|
+
const res = await (check.fetchFn ?? fetch)(
|
|
475
|
+
`${REGISTRY}/${encodeURIComponent(check.name)}/latest`,
|
|
476
|
+
{ headers: { accept: "application/json" }, signal }
|
|
477
|
+
);
|
|
478
|
+
const published = res.ok ? (await res.json()).version : void 0;
|
|
479
|
+
if (stop.signal.aborted) return;
|
|
480
|
+
const latest = releaseOf(published) === null ? void 0 : published;
|
|
481
|
+
await remember(check.folder, file, {
|
|
482
|
+
...await recall(file),
|
|
483
|
+
looked_at: now(),
|
|
484
|
+
...latest === void 0 ? {} : { latest }
|
|
485
|
+
});
|
|
486
|
+
} catch {
|
|
487
|
+
}
|
|
488
|
+
})();
|
|
489
|
+
return {
|
|
490
|
+
looked,
|
|
491
|
+
finish: async () => {
|
|
492
|
+
stop.abort();
|
|
493
|
+
try {
|
|
494
|
+
await looked;
|
|
495
|
+
const known = await recall(file);
|
|
496
|
+
const latest = known.latest;
|
|
497
|
+
if (typeof latest !== "string" || !isLater(latest, check.version)) return null;
|
|
498
|
+
if (withinADay(known.told_at, now())) return null;
|
|
499
|
+
const kept = await remember(check.folder, file, { ...known, told_at: now() });
|
|
500
|
+
return kept ? newerToolIsOut(latest) : null;
|
|
501
|
+
} catch {
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
392
507
|
|
|
393
508
|
// src/lib/config-store.ts
|
|
394
|
-
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
395
|
-
import { join } from "node:path";
|
|
509
|
+
import { mkdir as mkdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
510
|
+
import { join as join2 } from "node:path";
|
|
396
511
|
import envPaths from "env-paths";
|
|
397
512
|
|
|
398
513
|
// src/lib/discovery.ts
|
|
@@ -456,9 +571,9 @@ async function discoverEndpoints(issuer, fetchFn = fetch) {
|
|
|
456
571
|
|
|
457
572
|
// src/lib/config-store.ts
|
|
458
573
|
var paths = envPaths("dazzer", { suffix: "" });
|
|
459
|
-
var CONFIG_FILE =
|
|
574
|
+
var CONFIG_FILE = join2(paths.config, "auth.json");
|
|
460
575
|
async function ensureConfigDir() {
|
|
461
|
-
await
|
|
576
|
+
await mkdir2(paths.config, { recursive: true });
|
|
462
577
|
}
|
|
463
578
|
var MAX_CLIENT_ID_LENGTH = 512;
|
|
464
579
|
async function saveAuthMetadata(meta) {
|
|
@@ -471,12 +586,12 @@ async function saveAuthMetadata(meta) {
|
|
|
471
586
|
const savedToStore = typeof meta.savedToStore === "boolean" ? meta.savedToStore : void 0;
|
|
472
587
|
await ensureConfigDir();
|
|
473
588
|
const checked = { issuer, clientId, expiresAt, savedToStore };
|
|
474
|
-
await
|
|
589
|
+
await writeFile2(CONFIG_FILE, `${JSON.stringify(checked, null, 2)}
|
|
475
590
|
`, "utf8");
|
|
476
591
|
}
|
|
477
592
|
async function loadAuthMetadata() {
|
|
478
593
|
try {
|
|
479
|
-
const raw = await
|
|
594
|
+
const raw = await readFile2(CONFIG_FILE, "utf8");
|
|
480
595
|
const parsed = JSON.parse(raw);
|
|
481
596
|
if (typeof parsed.issuer !== "string" || typeof parsed.clientId !== "string") {
|
|
482
597
|
return null;
|
|
@@ -642,7 +757,7 @@ function createTokenStore() {
|
|
|
642
757
|
|
|
643
758
|
// src/lib/walk.ts
|
|
644
759
|
import { readdir, realpath, stat } from "node:fs/promises";
|
|
645
|
-
import { join as
|
|
760
|
+
import { join as join3, sep } from "node:path";
|
|
646
761
|
function inside(base, candidate) {
|
|
647
762
|
return candidate === base || candidate.startsWith(base.endsWith(sep) ? base : `${base}${sep}`);
|
|
648
763
|
}
|
|
@@ -667,7 +782,7 @@ async function walkFolder(root) {
|
|
|
667
782
|
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
668
783
|
for (const entry of entries) {
|
|
669
784
|
if (entry.name.startsWith(".")) continue;
|
|
670
|
-
const full =
|
|
785
|
+
const full = join3(dir, entry.name);
|
|
671
786
|
const relative = `${prefix}${entry.name}`;
|
|
672
787
|
if (entry.isSymbolicLink()) {
|
|
673
788
|
await followShortcut(full, relative);
|
|
@@ -774,6 +889,10 @@ function namingNoFile(keys) {
|
|
|
774
889
|
var noEntryMatches = (name2, example) => `No entry in ${name2} names a file being added \u2014 nothing was sent.` + (example === void 0 ? "" : ` A key looks like ${JSON.stringify(asPrinted(example))}.`);
|
|
775
890
|
var keepItBeside = "The descriptions file is inside the folder. Keep it beside the folder instead, so no later run sends it as a document.";
|
|
776
891
|
var itHadNoDescription = "It had no description.";
|
|
892
|
+
var NOTHING_DESCRIBED = "Nothing was sent. No file here has a description.";
|
|
893
|
+
var DESCRIBE_THEM = "Write what each file is in a descriptions file. Then run this again with --described-in.";
|
|
894
|
+
var notSentNoDescription = (name2) => `${name2} was not sent. It has no description.`;
|
|
895
|
+
var notAdded = (name2, because2) => [`${name2} was not added.`, ...because2].join(" ");
|
|
777
896
|
function alreadyThereDescriptions(counted) {
|
|
778
897
|
const parts = [
|
|
779
898
|
...counted.filed > 0 ? [`${counted.filed} took the description sent now`] : [],
|
|
@@ -933,6 +1052,7 @@ async function settleSpace(options, auth2, deps, voice, name2) {
|
|
|
933
1052
|
var HELD_FOR_AN_ANSWER = "same_name_different_contents";
|
|
934
1053
|
function fromAnswer(answer) {
|
|
935
1054
|
if (answer.status === "refused") {
|
|
1055
|
+
if (aboutThisTool(answer)) return { kind: "stop", answer };
|
|
936
1056
|
if (answer.code === HELD_FOR_AN_ANSWER && answer.detail !== void 0) {
|
|
937
1057
|
return { kind: "held", question: answer.detail };
|
|
938
1058
|
}
|
|
@@ -943,11 +1063,17 @@ function fromAnswer(answer) {
|
|
|
943
1063
|
// Carried, not filled in: absent stays absent, so nothing downstream can print an offer the
|
|
944
1064
|
// service did not make.
|
|
945
1065
|
...answer.choices === void 0 ? {} : { choices: answer.choices },
|
|
946
|
-
...answer.keepToYourself === void 0 ? {} : { keepToYourself: answer.keepToYourself }
|
|
1066
|
+
...answer.keepToYourself === void 0 ? {} : { keepToYourself: answer.keepToYourself },
|
|
1067
|
+
...answer.fields === void 0 ? {} : { fields: answer.fields },
|
|
1068
|
+
...answer.dropped === void 0 ? {} : { dropped: answer.dropped },
|
|
1069
|
+
...answer.howMuchWasRead === void 0 ? {} : { howMuchWasRead: answer.howMuchWasRead }
|
|
947
1070
|
};
|
|
948
1071
|
}
|
|
949
1072
|
return { kind: "stop", answer };
|
|
950
1073
|
}
|
|
1074
|
+
function aboutThisTool(answer) {
|
|
1075
|
+
return answer.status === "refused" && answer.fields?.includes(CLIENT_HEADER) === true;
|
|
1076
|
+
}
|
|
951
1077
|
async function sendOne(input) {
|
|
952
1078
|
const { deps, handle, options, voice } = input;
|
|
953
1079
|
const described = await callPaced(
|
|
@@ -1208,8 +1334,22 @@ var LINE_DROPPED = {
|
|
|
1208
1334
|
"line-verbatim-from-document": "its words are copied out of the document",
|
|
1209
1335
|
"quote-empty": "it quotes nothing",
|
|
1210
1336
|
"quote-too-long": "its quote is too long",
|
|
1337
|
+
"unstorable-characters": "it holds a character that cannot be stored",
|
|
1211
1338
|
"quote-not-in-document": "the words it quotes are not in the document"
|
|
1212
1339
|
};
|
|
1340
|
+
var A_LINE = {
|
|
1341
|
+
what_it_is: "The line saying what it is",
|
|
1342
|
+
what_it_covers: "The line saying what it covers",
|
|
1343
|
+
what_it_decides: "The line saying what it decides"
|
|
1344
|
+
};
|
|
1345
|
+
var HOW_MUCH_NOT_KEPT = {
|
|
1346
|
+
"not-said": "Its description does not say how much of the file was read.",
|
|
1347
|
+
"which-part-empty": "Its description says only part of the file was read, and not which part.",
|
|
1348
|
+
"which-part-unstorable-characters": "The part it says was read holds a character that cannot be stored.",
|
|
1349
|
+
"which-part-too-long": "The part it says was read is too long. Say it in one short sentence.",
|
|
1350
|
+
"which-part-line-break-or-marker": "The part it says was read is not one plain line."
|
|
1351
|
+
};
|
|
1352
|
+
var HOW_MUCH_NOT_KEPT_OTHERWISE = "Its description does not say how much of the file was read in a form Dazzer keeps.";
|
|
1213
1353
|
var FACT_DROPPED = {
|
|
1214
1354
|
"fact-empty": "it has no words",
|
|
1215
1355
|
"fact-too-long": "it is too long",
|
|
@@ -1231,8 +1371,44 @@ var TYPING_NOT_USED = {
|
|
|
1231
1371
|
not_used_file_has_its_own_text: "the file has words of its own",
|
|
1232
1372
|
not_used_document_already_held: "the document was already here"
|
|
1233
1373
|
};
|
|
1234
|
-
var
|
|
1235
|
-
|
|
1374
|
+
var because = (words, name2) => {
|
|
1375
|
+
const said = words[name2];
|
|
1376
|
+
return said === void 0 ? ` (${name2}).` : `, because ${said}.`;
|
|
1377
|
+
};
|
|
1378
|
+
var lineNotKept = (dropped) => `${A_LINE[dropped.line] ?? "A line of its description"} was not kept${because(LINE_DROPPED, dropped.reason)}`;
|
|
1379
|
+
var howMuchNotKept = (reason) => HOW_MUCH_NOT_KEPT[reason] ?? HOW_MUCH_NOT_KEPT_OTHERWISE;
|
|
1380
|
+
function whatToDoAbout(refused) {
|
|
1381
|
+
return refused.code === "description_missing" ? DESCRIBE_THEM : void 0;
|
|
1382
|
+
}
|
|
1383
|
+
var ADD_THE_TEXT = "Add the text you read in it to its entry.";
|
|
1384
|
+
function notAddedBecause(refused) {
|
|
1385
|
+
const dropped = (refused.dropped ?? []).map(lineNotKept);
|
|
1386
|
+
const howMuch = refused.howMuchWasRead === void 0 ? [] : [howMuchNotKept(refused.howMuchWasRead)];
|
|
1387
|
+
switch (refused.code) {
|
|
1388
|
+
case "description_missing":
|
|
1389
|
+
return ["It has no description."];
|
|
1390
|
+
case "description_not_kept": {
|
|
1391
|
+
const noLineKept = refused.fields?.includes("description") === true;
|
|
1392
|
+
const said = [...noLineKept ? ["No line of its description was kept."] : [], ...dropped];
|
|
1393
|
+
const all = [...said, ...howMuch];
|
|
1394
|
+
return all.length > 0 ? all : ["Its description was not kept."];
|
|
1395
|
+
}
|
|
1396
|
+
case "nothing_to_check_it_against":
|
|
1397
|
+
return [
|
|
1398
|
+
"Dazzer holds no words of it to check its description against.",
|
|
1399
|
+
ADD_THE_TEXT,
|
|
1400
|
+
...howMuch
|
|
1401
|
+
];
|
|
1402
|
+
case "transcription_too_long":
|
|
1403
|
+
return ["The text typed out of it is longer than Dazzer keeps for one file."];
|
|
1404
|
+
case "try_again_shortly":
|
|
1405
|
+
return ["It could not be read just now. Try again shortly."];
|
|
1406
|
+
case "could_not_read_it":
|
|
1407
|
+
return ["Dazzer could not read its words.", ADD_THE_TEXT];
|
|
1408
|
+
default:
|
|
1409
|
+
return void 0;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1236
1412
|
function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
1237
1413
|
const lines = [];
|
|
1238
1414
|
let filing;
|
|
@@ -1240,34 +1416,25 @@ function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
|
1240
1416
|
if (description !== null) {
|
|
1241
1417
|
const state = wordsOf(description.state);
|
|
1242
1418
|
const what = wordsOf(description.what);
|
|
1243
|
-
const how = wordsOf(description.how);
|
|
1244
1419
|
if (descriptionSent && state !== void 0) {
|
|
1245
1420
|
filing = state === "filed" ? "filed" : alreadyHere && state === "not_filed" && what === void 0 ? "already-described" : "not-kept";
|
|
1246
1421
|
}
|
|
1247
1422
|
if (state === "not_filed" && what !== void 0) {
|
|
1248
|
-
lines.push(
|
|
1423
|
+
lines.push("Its description was not kept.");
|
|
1249
1424
|
} else if (state !== void 0 && state !== "filed" && state !== "none_supplied" && !(alreadyHere && state === "not_filed")) {
|
|
1250
1425
|
lines.push(
|
|
1251
|
-
state === "not_filed" ? "description not filed." : `description not filed
|
|
1426
|
+
state === "not_filed" ? "Its description was not filed." : `Its description was not filed${because(DESCRIPTION_NOT_FILED, state)}`
|
|
1252
1427
|
);
|
|
1253
1428
|
}
|
|
1254
1429
|
for (const dropped of listOf(description.dropped)) {
|
|
1255
1430
|
if (!isPlainObject(dropped)) continue;
|
|
1256
1431
|
const line = wordsOf(dropped.line);
|
|
1257
1432
|
const reason = wordsOf(dropped.reason);
|
|
1258
|
-
|
|
1259
|
-
const how2 = wordsOf(dropped.how);
|
|
1260
|
-
const why = what2 === void 0 ? reason === void 0 ? void 0 : inPlainWords(LINE_DROPPED, reason) : how2 === void 0 ? what2 : `${what2} ${how2}`;
|
|
1261
|
-
if (line !== void 0 && why !== void 0) lines.push(`${line} dropped: ${why}`);
|
|
1433
|
+
if (line !== void 0 && reason !== void 0) lines.push(lineNotKept({ line, reason }));
|
|
1262
1434
|
}
|
|
1263
1435
|
const howMuch = isPlainObject(description.how_much_was_read) ? description.how_much_was_read : null;
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
const howMuchHow = wordsOf(howMuch.how);
|
|
1267
|
-
const example = howMuch.example === void 0 ? "" : JSON.stringify(howMuch.example);
|
|
1268
|
-
lines.push(
|
|
1269
|
-
`how_much_was_read not kept: ${howMuchWhat}` + (howMuchHow === void 0 ? "" : ` ${howMuchHow}`) + (example !== "" && example.length <= SHORT_EXAMPLE ? ` Example: ${example}` : "")
|
|
1270
|
-
);
|
|
1436
|
+
if (howMuch !== null && wordsOf(howMuch.what) !== void 0) {
|
|
1437
|
+
lines.push(howMuchNotKept(wordsOf(howMuch.reason) ?? ""));
|
|
1271
1438
|
}
|
|
1272
1439
|
}
|
|
1273
1440
|
const facts = isPlainObject(stored.facts) ? stored.facts : null;
|
|
@@ -1275,16 +1442,16 @@ function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
|
1275
1442
|
if (!isPlainObject(dropped)) continue;
|
|
1276
1443
|
const reason = wordsOf(dropped.reason);
|
|
1277
1444
|
if (typeof dropped.fact === "number" && reason !== void 0) {
|
|
1278
|
-
lines.push(`
|
|
1445
|
+
lines.push(`Fact ${dropped.fact} was not kept${because(FACT_DROPPED, reason)}`);
|
|
1279
1446
|
}
|
|
1280
1447
|
}
|
|
1281
1448
|
const date = isPlainObject(stored.date_not_kept) ? wordsOf(stored.date_not_kept.reason) : void 0;
|
|
1282
1449
|
if (date !== void 0 && !(alreadyHere && date === "document-already-held")) {
|
|
1283
|
-
lines.push(`
|
|
1450
|
+
lines.push(`Its date was not kept${because(DATE_NOT_KEPT, date)}`);
|
|
1284
1451
|
}
|
|
1285
1452
|
const typed = wordsOf(stored.transcription);
|
|
1286
1453
|
if (typed !== void 0 && typed !== "used" && !(alreadyHere && typed === "not_used_document_already_held")) {
|
|
1287
|
-
lines.push(`
|
|
1454
|
+
lines.push(`The text typed out of it was not used${because(TYPING_NOT_USED, typed)}`);
|
|
1288
1455
|
}
|
|
1289
1456
|
return {
|
|
1290
1457
|
notKept: lines.map(asPrinted),
|
|
@@ -1305,6 +1472,7 @@ async function openOne(path) {
|
|
|
1305
1472
|
return err?.code === "ENOENT" ? "missing" : "cannot-open";
|
|
1306
1473
|
}
|
|
1307
1474
|
}
|
|
1475
|
+
var LEAVES_A_FILE_OUT = (skip) => skip.reason !== "already-here" && skip.reason !== "the-descriptions-file";
|
|
1308
1476
|
var REASON_ORDER = [
|
|
1309
1477
|
"type-not-readable",
|
|
1310
1478
|
"too-large",
|
|
@@ -1314,6 +1482,7 @@ var REASON_ORDER = [
|
|
|
1314
1482
|
"did-not-finish",
|
|
1315
1483
|
"leads-out",
|
|
1316
1484
|
"entry-wrong-shape",
|
|
1485
|
+
"no-description",
|
|
1317
1486
|
"the-descriptions-file"
|
|
1318
1487
|
];
|
|
1319
1488
|
var NAMED_ALWAYS = /* @__PURE__ */ new Set(["entry-wrong-shape"]);
|
|
@@ -1331,6 +1500,7 @@ var COUNTED = {
|
|
|
1331
1500
|
"did-not-finish": (many) => `${many} that didn't finish`,
|
|
1332
1501
|
"leads-out": (many) => `${many} shortcut${many === 1 ? "" : "s"} out of the folder`,
|
|
1333
1502
|
"entry-wrong-shape": (many) => `${many} with an entry in the wrong shape`,
|
|
1503
|
+
"no-description": (many) => `${many} with no description`,
|
|
1334
1504
|
"the-descriptions-file": (many) => `${many} that ${many === 1 ? "is" : "are"} the descriptions file`
|
|
1335
1505
|
};
|
|
1336
1506
|
var IN_DETAIL = {
|
|
@@ -1342,10 +1512,11 @@ var IN_DETAIL = {
|
|
|
1342
1512
|
"did-not-finish": "the send didn't finish",
|
|
1343
1513
|
"leads-out": "a shortcut out of the folder",
|
|
1344
1514
|
"entry-wrong-shape": "not sent: its entry is in the wrong shape.",
|
|
1515
|
+
"no-description": "not sent: it has no description.",
|
|
1345
1516
|
"the-descriptions-file": "the descriptions file itself, never sent"
|
|
1346
1517
|
};
|
|
1347
|
-
function countLine(added, space, skipped, why
|
|
1348
|
-
const documents = `${added} document${added === 1 ? "" : "s"} in ${named(space)}
|
|
1518
|
+
function countLine(added, space, skipped, why) {
|
|
1519
|
+
const documents = `${added} document${added === 1 ? "" : "s"} in ${named(space)}.`;
|
|
1349
1520
|
if (skipped.length === 0) return documents;
|
|
1350
1521
|
const reasons = REASON_ORDER.flatMap((reason) => {
|
|
1351
1522
|
const many = skipped.filter((skip) => skip.reason === reason).length;
|
|
@@ -1355,8 +1526,9 @@ function countLine(added, space, skipped, why, undescribed = []) {
|
|
|
1355
1526
|
return `${documents} ${skipped.length} skipped \u2014 ${reasons}.${pointAtWhy ? " Run with --why to see each." : ""}`;
|
|
1356
1527
|
}
|
|
1357
1528
|
function whyLine(skip) {
|
|
1358
|
-
|
|
1359
|
-
|
|
1529
|
+
if (skip.line !== void 0) return skip.line;
|
|
1530
|
+
const because2 = skip.said === void 0 ? IN_DETAIL[skip.reason] : skip.reason === "refused" ? `refused: ${skip.said}` : skip.said;
|
|
1531
|
+
return `${asPrinted(skip.relative)} \u2014 ${because2}`;
|
|
1360
1532
|
}
|
|
1361
1533
|
var FROM_THE_WALK = {
|
|
1362
1534
|
"leads-out": "leads-out",
|
|
@@ -1447,7 +1619,7 @@ async function addOneFile(options, deps, voice, descriptions) {
|
|
|
1447
1619
|
);
|
|
1448
1620
|
}
|
|
1449
1621
|
if (outcome.kind === "added" || outcome.kind === "already-here") {
|
|
1450
|
-
for (const line of outcome.notKept) voice.write(`${name2}
|
|
1622
|
+
for (const line of outcome.notKept) voice.write(`${name2}: ${line}`);
|
|
1451
1623
|
}
|
|
1452
1624
|
return result;
|
|
1453
1625
|
} finally {
|
|
@@ -1472,11 +1644,18 @@ function answerForOneFile(outcome, name2, voice) {
|
|
|
1472
1644
|
return { ok: false };
|
|
1473
1645
|
// ONE FILE, ONE REFUSAL, AND THE PLACES IT OFFERED. The sentence is the service's and is
|
|
1474
1646
|
// printed as sent; the places under it are the service's too, and without them a refusal
|
|
1475
|
-
// that says to name one of them is a dead end for the person holding this one file.
|
|
1476
|
-
|
|
1477
|
-
|
|
1647
|
+
// that says to name one of them is a dead end for the person holding this one file. A refusal
|
|
1648
|
+
// whose sentence is written for whoever makes the call is said in this command's own words.
|
|
1649
|
+
case "refused": {
|
|
1650
|
+
const because2 = notAddedBecause(outcome);
|
|
1651
|
+
voice.write(
|
|
1652
|
+
because2 === void 0 ? outcome.said ?? wentWrong(name2) : notAdded(name2, because2)
|
|
1653
|
+
);
|
|
1654
|
+
const next = whatToDoAbout(outcome);
|
|
1655
|
+
if (next !== void 0) voice.write(next);
|
|
1478
1656
|
offerThePlaces(voice.write, outcome);
|
|
1479
1657
|
return { ok: false };
|
|
1658
|
+
}
|
|
1480
1659
|
case "did-not-finish":
|
|
1481
1660
|
voice.write(didNotFinish(name2));
|
|
1482
1661
|
return { ok: false };
|
|
@@ -1521,6 +1700,17 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1521
1700
|
);
|
|
1522
1701
|
return { ok: false };
|
|
1523
1702
|
}
|
|
1703
|
+
const worthAsking = walked.files.some((file) => {
|
|
1704
|
+
const entry = descriptions?.entries.get(asKey(file.relative));
|
|
1705
|
+
if (entry === void 0) return false;
|
|
1706
|
+
const checked = checkEntry(entry);
|
|
1707
|
+
return "wrong" in checked || "description" in checked.described;
|
|
1708
|
+
});
|
|
1709
|
+
if (walked.files.length > 0 && !worthAsking) {
|
|
1710
|
+
voice.write(NOTHING_DESCRIBED);
|
|
1711
|
+
voice.write(DESCRIBE_THEM);
|
|
1712
|
+
return { ok: false };
|
|
1713
|
+
}
|
|
1524
1714
|
const auth2 = deps.auth ?? await resolveAuth(options);
|
|
1525
1715
|
if (auth2 === null) {
|
|
1526
1716
|
voice.write(CANNOT_REACH_DAZZER);
|
|
@@ -1535,7 +1725,6 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1535
1725
|
const held = [];
|
|
1536
1726
|
let added = 0;
|
|
1537
1727
|
let ran = true;
|
|
1538
|
-
const undescribed = [];
|
|
1539
1728
|
const notKept = [];
|
|
1540
1729
|
const alreadyThere = { filed: 0, alreadyDescribed: 0, notKept: 0 };
|
|
1541
1730
|
for (const file of walked.files) {
|
|
@@ -1553,6 +1742,16 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1553
1742
|
}
|
|
1554
1743
|
described = checked.described;
|
|
1555
1744
|
}
|
|
1745
|
+
if (described === void 0 || !("description" in described)) {
|
|
1746
|
+
skipped.push(
|
|
1747
|
+
descriptions !== void 0 && await isFile(file.path, descriptions.identity) ? { relative: file.relative, reason: "the-descriptions-file" } : {
|
|
1748
|
+
relative: file.relative,
|
|
1749
|
+
reason: "no-description",
|
|
1750
|
+
line: notSentNoDescription(asPrinted(file.relative))
|
|
1751
|
+
}
|
|
1752
|
+
);
|
|
1753
|
+
continue;
|
|
1754
|
+
}
|
|
1556
1755
|
const handle = await openOne(file.path);
|
|
1557
1756
|
if (typeof handle === "string") {
|
|
1558
1757
|
skipped.push({ relative: file.relative, reason: "cannot-open" });
|
|
@@ -1589,9 +1788,6 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1589
1788
|
}
|
|
1590
1789
|
if (outcome.kind === "added") {
|
|
1591
1790
|
added += 1;
|
|
1592
|
-
if (descriptions !== void 0 && !("description" in (described ?? {}))) {
|
|
1593
|
-
undescribed.push(file.relative);
|
|
1594
|
-
}
|
|
1595
1791
|
continue;
|
|
1596
1792
|
}
|
|
1597
1793
|
if (outcome.kind === "already-here") {
|
|
@@ -1607,16 +1803,19 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1607
1803
|
continue;
|
|
1608
1804
|
}
|
|
1609
1805
|
if (outcome.kind === "refused") {
|
|
1806
|
+
const because2 = notAddedBecause(outcome);
|
|
1807
|
+
const reason = (outcome.code === void 0 ? void 0 : FROM_THE_SERVICE[outcome.code]) ?? "refused";
|
|
1610
1808
|
skipped.push({
|
|
1611
1809
|
relative: file.relative,
|
|
1612
|
-
|
|
1613
|
-
// may be reworded, the code is the label the service publishes.
|
|
1614
|
-
reason: (outcome.code === void 0 ? void 0 : FROM_THE_SERVICE[outcome.code]) ?? "refused",
|
|
1810
|
+
reason,
|
|
1615
1811
|
...outcome.said === void 0 ? {} : { said: outcome.said },
|
|
1616
|
-
|
|
1617
|
-
//
|
|
1618
|
-
//
|
|
1619
|
-
|
|
1812
|
+
...because2 === void 0 ? {} : { line: notAdded(asPrinted(file.relative), because2) },
|
|
1813
|
+
// REFUSED ALTHOUGH IT CARRIED AN ENTRY, which every file sent now does: most likely about
|
|
1814
|
+
// what the entry says, which only the person's assistant can put right, so it is named
|
|
1815
|
+
// without anybody having to run the whole folder again with `--why`. A refusal about the
|
|
1816
|
+
// file's kind or size is about the file, not its entry, so it stays a count that `--why`
|
|
1817
|
+
// names: a folder with thirty spreadsheets in it is one line, not thirty.
|
|
1818
|
+
...reason === "refused" ? { named: true } : {}
|
|
1620
1819
|
});
|
|
1621
1820
|
continue;
|
|
1622
1821
|
}
|
|
@@ -1636,7 +1835,11 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1636
1835
|
skipped.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1637
1836
|
held.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1638
1837
|
notKept.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1639
|
-
|
|
1838
|
+
const undescribed = skipped.filter((skip) => skip.reason === "no-description");
|
|
1839
|
+
if (undescribed.length <= NAMED_WHEN_AT_MOST) {
|
|
1840
|
+
for (const skip of undescribed) skip.named = true;
|
|
1841
|
+
}
|
|
1842
|
+
voice.write(countLine(added, settled.space, skipped, options.why === true));
|
|
1640
1843
|
if (skipped.some((skip) => skip.reason === "the-descriptions-file")) voice.write(keepItBeside);
|
|
1641
1844
|
if (alreadyThere.filed + alreadyThere.alreadyDescribed + alreadyThere.notKept > 0) {
|
|
1642
1845
|
voice.write(alreadyThereDescriptions(alreadyThere));
|
|
@@ -1647,7 +1850,7 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1647
1850
|
if (options.why === true || namedWithoutWhy(skip)) voice.write(whyLine(skip));
|
|
1648
1851
|
}
|
|
1649
1852
|
for (const one of notKept) {
|
|
1650
|
-
for (const line of one.lines) voice.write(`${asPrinted(one.relative)}
|
|
1853
|
+
for (const line of one.lines) voice.write(`${asPrinted(one.relative)}: ${line}`);
|
|
1651
1854
|
}
|
|
1652
1855
|
const onlyOne = held.length === 1 ? held[0] : void 0;
|
|
1653
1856
|
if (onlyOne !== void 0) {
|
|
@@ -1660,7 +1863,15 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1660
1863
|
)
|
|
1661
1864
|
);
|
|
1662
1865
|
}
|
|
1663
|
-
return { ok: ran };
|
|
1866
|
+
return { ok: ran && !skipped.some(LEAVES_A_FILE_OUT) };
|
|
1867
|
+
}
|
|
1868
|
+
async function isFile(path, identity) {
|
|
1869
|
+
try {
|
|
1870
|
+
const info = await statPath(path, { bigint: true });
|
|
1871
|
+
return sameFile({ dev: info.dev, ino: info.ino }, identity);
|
|
1872
|
+
} catch {
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1664
1875
|
}
|
|
1665
1876
|
async function aKeyItWouldMatch(files, descriptionsFile) {
|
|
1666
1877
|
for (const file of files) {
|
|
@@ -2399,6 +2610,15 @@ var { name, version } = require2("../package.json");
|
|
|
2399
2610
|
var client = `${name}/${version}`;
|
|
2400
2611
|
var program = new Command();
|
|
2401
2612
|
program.name("dazzer").description("Sign in to Dazzer from your terminal").version(version);
|
|
2613
|
+
var newerTool;
|
|
2614
|
+
program.hook("preAction", () => {
|
|
2615
|
+
newerTool = lookForNewerTool({ name, version, folder: getConfigDir() });
|
|
2616
|
+
});
|
|
2617
|
+
program.hook("postAction", async () => {
|
|
2618
|
+
const said = await newerTool?.finish();
|
|
2619
|
+
if (said) process.stderr.write(`${said}
|
|
2620
|
+
`);
|
|
2621
|
+
});
|
|
2402
2622
|
program.command("add").description("Put a file, or a whole folder, into Dazzer").argument("<path>", "The file or folder to add").option("--space <name>", "The space to put it in \u2014 the name you were shown, or its number").option("--keep-to-myself", "Keep it to yourself, rather than putting it in a shared space").option("--why", "Show each document that was skipped, and why").option(
|
|
2403
2623
|
"--described-in <file>",
|
|
2404
2624
|
"A JSON file saying what each file is, keyed by its path inside the folder"
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dazzer-connect",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"description": "Sign in to Dazzer \u2014 the memory your AI tools share \u2014 from a terminal, for clients that cannot sign in themselves",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|