dazzer-connect 0.3.0 → 0.4.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/README.md +20 -6
- package/dist/index.js +306 -53
- package/package.json +2 -2
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,9 @@ 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 RENEWED_SIGN_IN_NOT_SAVED = "Your sign-in was renewed, but this machine's credential store would not save it. Run: dazzer auth login";
|
|
27
|
+
var UPDATE_THE_TOOL = "npm install -g dazzer-connect@latest";
|
|
28
|
+
var newerToolIsOut = (version2) => `Version ${version2} of dazzer is out. Update it with: ${UPDATE_THE_TOOL}`;
|
|
22
29
|
function printDeviceInstructions(deviceAuth) {
|
|
23
30
|
writeOut("");
|
|
24
31
|
if (deviceAuth.verification_uri_complete) {
|
|
@@ -89,6 +96,9 @@ function printAuthStatus(params) {
|
|
|
89
96
|
writeOut(" Common on servers and containers, which often have none.");
|
|
90
97
|
writeOut(" Run: dazzer auth login \u2014 it prints a pass you can paste in.");
|
|
91
98
|
return;
|
|
99
|
+
case "save-refused":
|
|
100
|
+
writeOut(RENEWED_SIGN_IN_NOT_SAVED);
|
|
101
|
+
return;
|
|
92
102
|
case "expired":
|
|
93
103
|
writeOut("Your sign-in has expired or been revoked. Run: dazzer auth login");
|
|
94
104
|
return;
|
|
@@ -193,6 +203,12 @@ var RefreshRejectedError = class extends Error {
|
|
|
193
203
|
}
|
|
194
204
|
oauthError;
|
|
195
205
|
};
|
|
206
|
+
var RenewedSignInNotSavedError = class extends Error {
|
|
207
|
+
constructor() {
|
|
208
|
+
super("The credential store would not save the renewed sign-in.");
|
|
209
|
+
this.name = "RenewedSignInNotSavedError";
|
|
210
|
+
}
|
|
211
|
+
};
|
|
196
212
|
async function refreshAccessToken(options) {
|
|
197
213
|
const fetchFn = options.fetchFn ?? fetch;
|
|
198
214
|
const params = new URLSearchParams({
|
|
@@ -220,7 +236,11 @@ async function refreshAccessToken(options) {
|
|
|
220
236
|
}
|
|
221
237
|
const newRefresh = typeof refreshToken === "string" ? refreshToken : options.refreshToken;
|
|
222
238
|
const expiresIn = typeof body.expires_in === "number" ? body.expires_in : null;
|
|
223
|
-
|
|
239
|
+
try {
|
|
240
|
+
await options.tokenStore.save({ accessToken, refreshToken: newRefresh });
|
|
241
|
+
} catch {
|
|
242
|
+
throw new RenewedSignInNotSavedError();
|
|
243
|
+
}
|
|
224
244
|
return { accessToken, refreshToken: newRefresh, expiresIn };
|
|
225
245
|
}
|
|
226
246
|
async function getValidAccessToken(options) {
|
|
@@ -235,6 +255,9 @@ async function getValidAccessToken(options) {
|
|
|
235
255
|
if (err instanceof RefreshRejectedError) {
|
|
236
256
|
return { status: "rejected" };
|
|
237
257
|
}
|
|
258
|
+
if (err instanceof RenewedSignInNotSavedError) {
|
|
259
|
+
return { status: "save-refused" };
|
|
260
|
+
}
|
|
238
261
|
return { status: "unreachable" };
|
|
239
262
|
}
|
|
240
263
|
}
|
|
@@ -315,6 +338,31 @@ function refusalPlaces(body) {
|
|
|
315
338
|
...typeof keep === "boolean" ? { keepToYourself: keep } : {}
|
|
316
339
|
};
|
|
317
340
|
}
|
|
341
|
+
var wordsIn = (value) => typeof value === "string" && value.trim() !== "" ? value : void 0;
|
|
342
|
+
function refusalAbout(body) {
|
|
343
|
+
if (typeof body !== "object" || body === null) return {};
|
|
344
|
+
const flat = body;
|
|
345
|
+
const enveloped = typeof flat.error === "object" && flat.error !== null ? flat.error.details : void 0;
|
|
346
|
+
const fields = [
|
|
347
|
+
...wordsIn(flat.field) === void 0 ? [] : [flat.field],
|
|
348
|
+
...Array.isArray(enveloped) ? enveloped.flatMap((detail) => {
|
|
349
|
+
const named2 = typeof detail === "object" && detail !== null ? wordsIn(detail.field) : void 0;
|
|
350
|
+
return named2 === void 0 ? [] : [named2];
|
|
351
|
+
}) : []
|
|
352
|
+
];
|
|
353
|
+
const dropped = Array.isArray(flat.dropped) ? flat.dropped.flatMap((entry) => {
|
|
354
|
+
if (typeof entry !== "object" || entry === null) return [];
|
|
355
|
+
const line = wordsIn(entry.line);
|
|
356
|
+
const reason = wordsIn(entry.reason);
|
|
357
|
+
return line === void 0 || reason === void 0 ? [] : [{ line, reason }];
|
|
358
|
+
}) : [];
|
|
359
|
+
const howMuch = typeof flat.how_much_was_read === "object" && flat.how_much_was_read !== null ? wordsIn(flat.how_much_was_read.reason) : void 0;
|
|
360
|
+
return {
|
|
361
|
+
...fields.length > 0 ? { fields } : {},
|
|
362
|
+
...dropped.length > 0 ? { dropped } : {},
|
|
363
|
+
...howMuch === void 0 ? {} : { howMuchWasRead: howMuch }
|
|
364
|
+
};
|
|
365
|
+
}
|
|
318
366
|
async function resolveCredential(auth2) {
|
|
319
367
|
let held;
|
|
320
368
|
try {
|
|
@@ -334,6 +382,8 @@ async function resolveCredential(auth2) {
|
|
|
334
382
|
return { status: "signed-out" };
|
|
335
383
|
case "rejected":
|
|
336
384
|
return { status: "sign-in-invalid" };
|
|
385
|
+
case "save-refused":
|
|
386
|
+
return { status: "save-refused" };
|
|
337
387
|
case "unreachable":
|
|
338
388
|
return { held: held.accessToken };
|
|
339
389
|
case "refreshed": {
|
|
@@ -353,6 +403,9 @@ async function callDazzer(request) {
|
|
|
353
403
|
if (credential.status === "sign-in-invalid") {
|
|
354
404
|
return { status: "sign-in-invalid", message: SIGN_IN_NO_LONGER_VALID };
|
|
355
405
|
}
|
|
406
|
+
if (credential.status === "save-refused") {
|
|
407
|
+
return { status: "sign-in-invalid", message: RENEWED_SIGN_IN_NOT_SAVED };
|
|
408
|
+
}
|
|
356
409
|
return { status: credential.status };
|
|
357
410
|
}
|
|
358
411
|
const fetchFn = request.fetchFn ?? fetch;
|
|
@@ -384,15 +437,99 @@ async function callDazzer(request) {
|
|
|
384
437
|
httpStatus: res.status,
|
|
385
438
|
...detail === void 0 ? {} : { detail },
|
|
386
439
|
...code === void 0 ? {} : { code },
|
|
387
|
-
...refusalPlaces(body)
|
|
440
|
+
...refusalPlaces(body),
|
|
441
|
+
...refusalAbout(body)
|
|
388
442
|
};
|
|
389
443
|
}
|
|
390
444
|
return { status: "ok", httpStatus: res.status, body };
|
|
391
445
|
}
|
|
446
|
+
var REGISTRY = "https://registry.npmjs.org";
|
|
447
|
+
var LOOK_AT_MOST_MS = 3e3;
|
|
448
|
+
var A_DAY_MS = 24 * 60 * 60 * 1e3;
|
|
449
|
+
var NEWER_TOOL_FILE = "newer-version.json";
|
|
450
|
+
function releaseOf(version2) {
|
|
451
|
+
if (typeof version2 !== "string") return null;
|
|
452
|
+
const said = /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/.exec(version2);
|
|
453
|
+
return said === null ? null : [Number(said[1]), Number(said[2]), Number(said[3])];
|
|
454
|
+
}
|
|
455
|
+
function isLater(published, mine) {
|
|
456
|
+
const theirs = releaseOf(published);
|
|
457
|
+
const own = /^(\d{1,6}\.\d{1,6}\.\d{1,6})(-[^+]+)?(\+.+)?$/.exec(mine);
|
|
458
|
+
const ours = own === null ? null : releaseOf(own[1]);
|
|
459
|
+
if (theirs === null || own === null || ours === null) return false;
|
|
460
|
+
for (let at = 0; at < 3; at += 1) {
|
|
461
|
+
if (theirs[at] !== ours[at]) return (theirs[at] ?? 0) > (ours[at] ?? 0);
|
|
462
|
+
}
|
|
463
|
+
return own[2] !== void 0;
|
|
464
|
+
}
|
|
465
|
+
async function recall(file) {
|
|
466
|
+
try {
|
|
467
|
+
const parsed = JSON.parse(await readFile(file, "utf8"));
|
|
468
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
469
|
+
} catch {
|
|
470
|
+
return {};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
async function remember(folder, file, what) {
|
|
474
|
+
try {
|
|
475
|
+
await mkdir(folder, { recursive: true });
|
|
476
|
+
await writeFile(file, `${JSON.stringify(what)}
|
|
477
|
+
`, "utf8");
|
|
478
|
+
return true;
|
|
479
|
+
} catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
var withinADay = (then, now) => typeof then === "number" && Number.isFinite(then) && then <= now && now - then < A_DAY_MS;
|
|
484
|
+
function lookForNewerTool(check) {
|
|
485
|
+
const now = check.now ?? Date.now;
|
|
486
|
+
const file = join(check.folder, NEWER_TOOL_FILE);
|
|
487
|
+
const stop = new AbortController();
|
|
488
|
+
const looked = (async () => {
|
|
489
|
+
try {
|
|
490
|
+
const before = await recall(file);
|
|
491
|
+
if (withinADay(before.looked_at, now()) || stop.signal.aborted) return;
|
|
492
|
+
const signal = AbortSignal.any([
|
|
493
|
+
stop.signal,
|
|
494
|
+
AbortSignal.timeout(check.lookAtMostMs ?? LOOK_AT_MOST_MS)
|
|
495
|
+
]);
|
|
496
|
+
const res = await (check.fetchFn ?? fetch)(
|
|
497
|
+
`${REGISTRY}/${encodeURIComponent(check.name)}/latest`,
|
|
498
|
+
{ headers: { accept: "application/json" }, signal }
|
|
499
|
+
);
|
|
500
|
+
const published = res.ok ? (await res.json()).version : void 0;
|
|
501
|
+
if (stop.signal.aborted) return;
|
|
502
|
+
const latest = releaseOf(published) === null ? void 0 : published;
|
|
503
|
+
await remember(check.folder, file, {
|
|
504
|
+
...await recall(file),
|
|
505
|
+
looked_at: now(),
|
|
506
|
+
...latest === void 0 ? {} : { latest }
|
|
507
|
+
});
|
|
508
|
+
} catch {
|
|
509
|
+
}
|
|
510
|
+
})();
|
|
511
|
+
return {
|
|
512
|
+
looked,
|
|
513
|
+
finish: async () => {
|
|
514
|
+
stop.abort();
|
|
515
|
+
try {
|
|
516
|
+
await looked;
|
|
517
|
+
const known = await recall(file);
|
|
518
|
+
const latest = known.latest;
|
|
519
|
+
if (typeof latest !== "string" || !isLater(latest, check.version)) return null;
|
|
520
|
+
if (withinADay(known.told_at, now())) return null;
|
|
521
|
+
const kept = await remember(check.folder, file, { ...known, told_at: now() });
|
|
522
|
+
return kept ? newerToolIsOut(latest) : null;
|
|
523
|
+
} catch {
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
392
529
|
|
|
393
530
|
// src/lib/config-store.ts
|
|
394
|
-
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
395
|
-
import { join } from "node:path";
|
|
531
|
+
import { mkdir as mkdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
532
|
+
import { join as join2 } from "node:path";
|
|
396
533
|
import envPaths from "env-paths";
|
|
397
534
|
|
|
398
535
|
// src/lib/discovery.ts
|
|
@@ -456,9 +593,9 @@ async function discoverEndpoints(issuer, fetchFn = fetch) {
|
|
|
456
593
|
|
|
457
594
|
// src/lib/config-store.ts
|
|
458
595
|
var paths = envPaths("dazzer", { suffix: "" });
|
|
459
|
-
var CONFIG_FILE =
|
|
596
|
+
var CONFIG_FILE = join2(paths.config, "auth.json");
|
|
460
597
|
async function ensureConfigDir() {
|
|
461
|
-
await
|
|
598
|
+
await mkdir2(paths.config, { recursive: true });
|
|
462
599
|
}
|
|
463
600
|
var MAX_CLIENT_ID_LENGTH = 512;
|
|
464
601
|
async function saveAuthMetadata(meta) {
|
|
@@ -471,12 +608,12 @@ async function saveAuthMetadata(meta) {
|
|
|
471
608
|
const savedToStore = typeof meta.savedToStore === "boolean" ? meta.savedToStore : void 0;
|
|
472
609
|
await ensureConfigDir();
|
|
473
610
|
const checked = { issuer, clientId, expiresAt, savedToStore };
|
|
474
|
-
await
|
|
611
|
+
await writeFile2(CONFIG_FILE, `${JSON.stringify(checked, null, 2)}
|
|
475
612
|
`, "utf8");
|
|
476
613
|
}
|
|
477
614
|
async function loadAuthMetadata() {
|
|
478
615
|
try {
|
|
479
|
-
const raw = await
|
|
616
|
+
const raw = await readFile2(CONFIG_FILE, "utf8");
|
|
480
617
|
const parsed = JSON.parse(raw);
|
|
481
618
|
if (typeof parsed.issuer !== "string" || typeof parsed.clientId !== "string") {
|
|
482
619
|
return null;
|
|
@@ -605,9 +742,17 @@ var KeychainTokenStore = class {
|
|
|
605
742
|
this.openEntry = openEntry;
|
|
606
743
|
}
|
|
607
744
|
openEntry;
|
|
745
|
+
/**
|
|
746
|
+
* THE REFRESH TOKEN IS WRITTEN FIRST, AND IF IT WILL NOT BE, NOTHING ELSE IS TOUCHED. After a renewal
|
|
747
|
+
* the saved refresh token is already spent at the sign-in server. Writing the access token first and
|
|
748
|
+
* then failing on the refresh token left a new access token beside that spent one, which read as a
|
|
749
|
+
* fresh sign-in until it failed to renew. In this order a refused first write changes nothing, and
|
|
750
|
+
* a refused second write leaves the new refresh token, which can still renew. Either way the
|
|
751
|
+
* refusal reaches the caller, which says so rather than calling it anything else.
|
|
752
|
+
*/
|
|
608
753
|
async save(tokens) {
|
|
609
|
-
this.openEntry(ACCESS_ACCOUNT).setPassword(tokens.accessToken);
|
|
610
754
|
this.openEntry(REFRESH_ACCOUNT).setPassword(tokens.refreshToken);
|
|
755
|
+
this.openEntry(ACCESS_ACCOUNT).setPassword(tokens.accessToken);
|
|
611
756
|
}
|
|
612
757
|
async load() {
|
|
613
758
|
const accessToken = this.openEntry(ACCESS_ACCOUNT).getPassword();
|
|
@@ -642,7 +787,7 @@ function createTokenStore() {
|
|
|
642
787
|
|
|
643
788
|
// src/lib/walk.ts
|
|
644
789
|
import { readdir, realpath, stat } from "node:fs/promises";
|
|
645
|
-
import { join as
|
|
790
|
+
import { join as join3, sep } from "node:path";
|
|
646
791
|
function inside(base, candidate) {
|
|
647
792
|
return candidate === base || candidate.startsWith(base.endsWith(sep) ? base : `${base}${sep}`);
|
|
648
793
|
}
|
|
@@ -667,7 +812,7 @@ async function walkFolder(root) {
|
|
|
667
812
|
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
668
813
|
for (const entry of entries) {
|
|
669
814
|
if (entry.name.startsWith(".")) continue;
|
|
670
|
-
const full =
|
|
815
|
+
const full = join3(dir, entry.name);
|
|
671
816
|
const relative = `${prefix}${entry.name}`;
|
|
672
817
|
if (entry.isSymbolicLink()) {
|
|
673
818
|
await followShortcut(full, relative);
|
|
@@ -774,6 +919,10 @@ function namingNoFile(keys) {
|
|
|
774
919
|
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
920
|
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
921
|
var itHadNoDescription = "It had no description.";
|
|
922
|
+
var NOTHING_DESCRIBED = "Nothing was sent. No file here has a description.";
|
|
923
|
+
var DESCRIBE_THEM = "Write what each file is in a descriptions file. Then run this again with --described-in.";
|
|
924
|
+
var notSentNoDescription = (name2) => `${name2} was not sent. It has no description.`;
|
|
925
|
+
var notAdded = (name2, because2) => [`${name2} was not added.`, ...because2].join(" ");
|
|
777
926
|
function alreadyThereDescriptions(counted) {
|
|
778
927
|
const parts = [
|
|
779
928
|
...counted.filed > 0 ? [`${counted.filed} took the description sent now`] : [],
|
|
@@ -933,6 +1082,7 @@ async function settleSpace(options, auth2, deps, voice, name2) {
|
|
|
933
1082
|
var HELD_FOR_AN_ANSWER = "same_name_different_contents";
|
|
934
1083
|
function fromAnswer(answer) {
|
|
935
1084
|
if (answer.status === "refused") {
|
|
1085
|
+
if (aboutThisTool(answer)) return { kind: "stop", answer };
|
|
936
1086
|
if (answer.code === HELD_FOR_AN_ANSWER && answer.detail !== void 0) {
|
|
937
1087
|
return { kind: "held", question: answer.detail };
|
|
938
1088
|
}
|
|
@@ -943,11 +1093,17 @@ function fromAnswer(answer) {
|
|
|
943
1093
|
// Carried, not filled in: absent stays absent, so nothing downstream can print an offer the
|
|
944
1094
|
// service did not make.
|
|
945
1095
|
...answer.choices === void 0 ? {} : { choices: answer.choices },
|
|
946
|
-
...answer.keepToYourself === void 0 ? {} : { keepToYourself: answer.keepToYourself }
|
|
1096
|
+
...answer.keepToYourself === void 0 ? {} : { keepToYourself: answer.keepToYourself },
|
|
1097
|
+
...answer.fields === void 0 ? {} : { fields: answer.fields },
|
|
1098
|
+
...answer.dropped === void 0 ? {} : { dropped: answer.dropped },
|
|
1099
|
+
...answer.howMuchWasRead === void 0 ? {} : { howMuchWasRead: answer.howMuchWasRead }
|
|
947
1100
|
};
|
|
948
1101
|
}
|
|
949
1102
|
return { kind: "stop", answer };
|
|
950
1103
|
}
|
|
1104
|
+
function aboutThisTool(answer) {
|
|
1105
|
+
return answer.status === "refused" && answer.fields?.includes(CLIENT_HEADER) === true;
|
|
1106
|
+
}
|
|
951
1107
|
async function sendOne(input) {
|
|
952
1108
|
const { deps, handle, options, voice } = input;
|
|
953
1109
|
const described = await callPaced(
|
|
@@ -1208,8 +1364,22 @@ var LINE_DROPPED = {
|
|
|
1208
1364
|
"line-verbatim-from-document": "its words are copied out of the document",
|
|
1209
1365
|
"quote-empty": "it quotes nothing",
|
|
1210
1366
|
"quote-too-long": "its quote is too long",
|
|
1367
|
+
"unstorable-characters": "it holds a character that cannot be stored",
|
|
1211
1368
|
"quote-not-in-document": "the words it quotes are not in the document"
|
|
1212
1369
|
};
|
|
1370
|
+
var A_LINE = {
|
|
1371
|
+
what_it_is: "The line saying what it is",
|
|
1372
|
+
what_it_covers: "The line saying what it covers",
|
|
1373
|
+
what_it_decides: "The line saying what it decides"
|
|
1374
|
+
};
|
|
1375
|
+
var HOW_MUCH_NOT_KEPT = {
|
|
1376
|
+
"not-said": "Its description does not say how much of the file was read.",
|
|
1377
|
+
"which-part-empty": "Its description says only part of the file was read, and not which part.",
|
|
1378
|
+
"which-part-unstorable-characters": "The part it says was read holds a character that cannot be stored.",
|
|
1379
|
+
"which-part-too-long": "The part it says was read is too long. Say it in one short sentence.",
|
|
1380
|
+
"which-part-line-break-or-marker": "The part it says was read is not one plain line."
|
|
1381
|
+
};
|
|
1382
|
+
var HOW_MUCH_NOT_KEPT_OTHERWISE = "Its description does not say how much of the file was read in a form Dazzer keeps.";
|
|
1213
1383
|
var FACT_DROPPED = {
|
|
1214
1384
|
"fact-empty": "it has no words",
|
|
1215
1385
|
"fact-too-long": "it is too long",
|
|
@@ -1231,8 +1401,44 @@ var TYPING_NOT_USED = {
|
|
|
1231
1401
|
not_used_file_has_its_own_text: "the file has words of its own",
|
|
1232
1402
|
not_used_document_already_held: "the document was already here"
|
|
1233
1403
|
};
|
|
1234
|
-
var
|
|
1235
|
-
|
|
1404
|
+
var because = (words, name2) => {
|
|
1405
|
+
const said = words[name2];
|
|
1406
|
+
return said === void 0 ? ` (${name2}).` : `, because ${said}.`;
|
|
1407
|
+
};
|
|
1408
|
+
var lineNotKept = (dropped) => `${A_LINE[dropped.line] ?? "A line of its description"} was not kept${because(LINE_DROPPED, dropped.reason)}`;
|
|
1409
|
+
var howMuchNotKept = (reason) => HOW_MUCH_NOT_KEPT[reason] ?? HOW_MUCH_NOT_KEPT_OTHERWISE;
|
|
1410
|
+
function whatToDoAbout(refused) {
|
|
1411
|
+
return refused.code === "description_missing" ? DESCRIBE_THEM : void 0;
|
|
1412
|
+
}
|
|
1413
|
+
var ADD_THE_TEXT = "Add the text you read in it to its entry.";
|
|
1414
|
+
function notAddedBecause(refused) {
|
|
1415
|
+
const dropped = (refused.dropped ?? []).map(lineNotKept);
|
|
1416
|
+
const howMuch = refused.howMuchWasRead === void 0 ? [] : [howMuchNotKept(refused.howMuchWasRead)];
|
|
1417
|
+
switch (refused.code) {
|
|
1418
|
+
case "description_missing":
|
|
1419
|
+
return ["It has no description."];
|
|
1420
|
+
case "description_not_kept": {
|
|
1421
|
+
const noLineKept = refused.fields?.includes("description") === true;
|
|
1422
|
+
const said = [...noLineKept ? ["No line of its description was kept."] : [], ...dropped];
|
|
1423
|
+
const all = [...said, ...howMuch];
|
|
1424
|
+
return all.length > 0 ? all : ["Its description was not kept."];
|
|
1425
|
+
}
|
|
1426
|
+
case "nothing_to_check_it_against":
|
|
1427
|
+
return [
|
|
1428
|
+
"Dazzer holds no words of it to check its description against.",
|
|
1429
|
+
ADD_THE_TEXT,
|
|
1430
|
+
...howMuch
|
|
1431
|
+
];
|
|
1432
|
+
case "transcription_too_long":
|
|
1433
|
+
return ["The text typed out of it is longer than Dazzer keeps for one file."];
|
|
1434
|
+
case "try_again_shortly":
|
|
1435
|
+
return ["It could not be read just now. Try again shortly."];
|
|
1436
|
+
case "could_not_read_it":
|
|
1437
|
+
return ["Dazzer could not read its words.", ADD_THE_TEXT];
|
|
1438
|
+
default:
|
|
1439
|
+
return void 0;
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1236
1442
|
function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
1237
1443
|
const lines = [];
|
|
1238
1444
|
let filing;
|
|
@@ -1240,34 +1446,25 @@ function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
|
1240
1446
|
if (description !== null) {
|
|
1241
1447
|
const state = wordsOf(description.state);
|
|
1242
1448
|
const what = wordsOf(description.what);
|
|
1243
|
-
const how = wordsOf(description.how);
|
|
1244
1449
|
if (descriptionSent && state !== void 0) {
|
|
1245
1450
|
filing = state === "filed" ? "filed" : alreadyHere && state === "not_filed" && what === void 0 ? "already-described" : "not-kept";
|
|
1246
1451
|
}
|
|
1247
1452
|
if (state === "not_filed" && what !== void 0) {
|
|
1248
|
-
lines.push(
|
|
1453
|
+
lines.push("Its description was not kept.");
|
|
1249
1454
|
} else if (state !== void 0 && state !== "filed" && state !== "none_supplied" && !(alreadyHere && state === "not_filed")) {
|
|
1250
1455
|
lines.push(
|
|
1251
|
-
state === "not_filed" ? "description not filed." : `description not filed
|
|
1456
|
+
state === "not_filed" ? "Its description was not filed." : `Its description was not filed${because(DESCRIPTION_NOT_FILED, state)}`
|
|
1252
1457
|
);
|
|
1253
1458
|
}
|
|
1254
1459
|
for (const dropped of listOf(description.dropped)) {
|
|
1255
1460
|
if (!isPlainObject(dropped)) continue;
|
|
1256
1461
|
const line = wordsOf(dropped.line);
|
|
1257
1462
|
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}`);
|
|
1463
|
+
if (line !== void 0 && reason !== void 0) lines.push(lineNotKept({ line, reason }));
|
|
1262
1464
|
}
|
|
1263
1465
|
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
|
-
);
|
|
1466
|
+
if (howMuch !== null && wordsOf(howMuch.what) !== void 0) {
|
|
1467
|
+
lines.push(howMuchNotKept(wordsOf(howMuch.reason) ?? ""));
|
|
1271
1468
|
}
|
|
1272
1469
|
}
|
|
1273
1470
|
const facts = isPlainObject(stored.facts) ? stored.facts : null;
|
|
@@ -1275,16 +1472,16 @@ function whatBecameOfIt(stored, alreadyHere, descriptionSent) {
|
|
|
1275
1472
|
if (!isPlainObject(dropped)) continue;
|
|
1276
1473
|
const reason = wordsOf(dropped.reason);
|
|
1277
1474
|
if (typeof dropped.fact === "number" && reason !== void 0) {
|
|
1278
|
-
lines.push(`
|
|
1475
|
+
lines.push(`Fact ${dropped.fact} was not kept${because(FACT_DROPPED, reason)}`);
|
|
1279
1476
|
}
|
|
1280
1477
|
}
|
|
1281
1478
|
const date = isPlainObject(stored.date_not_kept) ? wordsOf(stored.date_not_kept.reason) : void 0;
|
|
1282
1479
|
if (date !== void 0 && !(alreadyHere && date === "document-already-held")) {
|
|
1283
|
-
lines.push(`
|
|
1480
|
+
lines.push(`Its date was not kept${because(DATE_NOT_KEPT, date)}`);
|
|
1284
1481
|
}
|
|
1285
1482
|
const typed = wordsOf(stored.transcription);
|
|
1286
1483
|
if (typed !== void 0 && typed !== "used" && !(alreadyHere && typed === "not_used_document_already_held")) {
|
|
1287
|
-
lines.push(`
|
|
1484
|
+
lines.push(`The text typed out of it was not used${because(TYPING_NOT_USED, typed)}`);
|
|
1288
1485
|
}
|
|
1289
1486
|
return {
|
|
1290
1487
|
notKept: lines.map(asPrinted),
|
|
@@ -1305,6 +1502,7 @@ async function openOne(path) {
|
|
|
1305
1502
|
return err?.code === "ENOENT" ? "missing" : "cannot-open";
|
|
1306
1503
|
}
|
|
1307
1504
|
}
|
|
1505
|
+
var LEAVES_A_FILE_OUT = (skip) => skip.reason !== "already-here" && skip.reason !== "the-descriptions-file";
|
|
1308
1506
|
var REASON_ORDER = [
|
|
1309
1507
|
"type-not-readable",
|
|
1310
1508
|
"too-large",
|
|
@@ -1314,6 +1512,7 @@ var REASON_ORDER = [
|
|
|
1314
1512
|
"did-not-finish",
|
|
1315
1513
|
"leads-out",
|
|
1316
1514
|
"entry-wrong-shape",
|
|
1515
|
+
"no-description",
|
|
1317
1516
|
"the-descriptions-file"
|
|
1318
1517
|
];
|
|
1319
1518
|
var NAMED_ALWAYS = /* @__PURE__ */ new Set(["entry-wrong-shape"]);
|
|
@@ -1331,6 +1530,7 @@ var COUNTED = {
|
|
|
1331
1530
|
"did-not-finish": (many) => `${many} that didn't finish`,
|
|
1332
1531
|
"leads-out": (many) => `${many} shortcut${many === 1 ? "" : "s"} out of the folder`,
|
|
1333
1532
|
"entry-wrong-shape": (many) => `${many} with an entry in the wrong shape`,
|
|
1533
|
+
"no-description": (many) => `${many} with no description`,
|
|
1334
1534
|
"the-descriptions-file": (many) => `${many} that ${many === 1 ? "is" : "are"} the descriptions file`
|
|
1335
1535
|
};
|
|
1336
1536
|
var IN_DETAIL = {
|
|
@@ -1342,10 +1542,11 @@ var IN_DETAIL = {
|
|
|
1342
1542
|
"did-not-finish": "the send didn't finish",
|
|
1343
1543
|
"leads-out": "a shortcut out of the folder",
|
|
1344
1544
|
"entry-wrong-shape": "not sent: its entry is in the wrong shape.",
|
|
1545
|
+
"no-description": "not sent: it has no description.",
|
|
1345
1546
|
"the-descriptions-file": "the descriptions file itself, never sent"
|
|
1346
1547
|
};
|
|
1347
|
-
function countLine(added, space, skipped, why
|
|
1348
|
-
const documents = `${added} document${added === 1 ? "" : "s"} in ${named(space)}
|
|
1548
|
+
function countLine(added, space, skipped, why) {
|
|
1549
|
+
const documents = `${added} document${added === 1 ? "" : "s"} in ${named(space)}.`;
|
|
1349
1550
|
if (skipped.length === 0) return documents;
|
|
1350
1551
|
const reasons = REASON_ORDER.flatMap((reason) => {
|
|
1351
1552
|
const many = skipped.filter((skip) => skip.reason === reason).length;
|
|
@@ -1355,8 +1556,9 @@ function countLine(added, space, skipped, why, undescribed = []) {
|
|
|
1355
1556
|
return `${documents} ${skipped.length} skipped \u2014 ${reasons}.${pointAtWhy ? " Run with --why to see each." : ""}`;
|
|
1356
1557
|
}
|
|
1357
1558
|
function whyLine(skip) {
|
|
1358
|
-
|
|
1359
|
-
|
|
1559
|
+
if (skip.line !== void 0) return skip.line;
|
|
1560
|
+
const because2 = skip.said === void 0 ? IN_DETAIL[skip.reason] : skip.reason === "refused" ? `refused: ${skip.said}` : skip.said;
|
|
1561
|
+
return `${asPrinted(skip.relative)} \u2014 ${because2}`;
|
|
1360
1562
|
}
|
|
1361
1563
|
var FROM_THE_WALK = {
|
|
1362
1564
|
"leads-out": "leads-out",
|
|
@@ -1447,7 +1649,7 @@ async function addOneFile(options, deps, voice, descriptions) {
|
|
|
1447
1649
|
);
|
|
1448
1650
|
}
|
|
1449
1651
|
if (outcome.kind === "added" || outcome.kind === "already-here") {
|
|
1450
|
-
for (const line of outcome.notKept) voice.write(`${name2}
|
|
1652
|
+
for (const line of outcome.notKept) voice.write(`${name2}: ${line}`);
|
|
1451
1653
|
}
|
|
1452
1654
|
return result;
|
|
1453
1655
|
} finally {
|
|
@@ -1472,11 +1674,18 @@ function answerForOneFile(outcome, name2, voice) {
|
|
|
1472
1674
|
return { ok: false };
|
|
1473
1675
|
// ONE FILE, ONE REFUSAL, AND THE PLACES IT OFFERED. The sentence is the service's and is
|
|
1474
1676
|
// 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
|
-
|
|
1677
|
+
// that says to name one of them is a dead end for the person holding this one file. A refusal
|
|
1678
|
+
// whose sentence is written for whoever makes the call is said in this command's own words.
|
|
1679
|
+
case "refused": {
|
|
1680
|
+
const because2 = notAddedBecause(outcome);
|
|
1681
|
+
voice.write(
|
|
1682
|
+
because2 === void 0 ? outcome.said ?? wentWrong(name2) : notAdded(name2, because2)
|
|
1683
|
+
);
|
|
1684
|
+
const next = whatToDoAbout(outcome);
|
|
1685
|
+
if (next !== void 0) voice.write(next);
|
|
1478
1686
|
offerThePlaces(voice.write, outcome);
|
|
1479
1687
|
return { ok: false };
|
|
1688
|
+
}
|
|
1480
1689
|
case "did-not-finish":
|
|
1481
1690
|
voice.write(didNotFinish(name2));
|
|
1482
1691
|
return { ok: false };
|
|
@@ -1521,6 +1730,17 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1521
1730
|
);
|
|
1522
1731
|
return { ok: false };
|
|
1523
1732
|
}
|
|
1733
|
+
const worthAsking = walked.files.some((file) => {
|
|
1734
|
+
const entry = descriptions?.entries.get(asKey(file.relative));
|
|
1735
|
+
if (entry === void 0) return false;
|
|
1736
|
+
const checked = checkEntry(entry);
|
|
1737
|
+
return "wrong" in checked || "description" in checked.described;
|
|
1738
|
+
});
|
|
1739
|
+
if (walked.files.length > 0 && !worthAsking) {
|
|
1740
|
+
voice.write(NOTHING_DESCRIBED);
|
|
1741
|
+
voice.write(DESCRIBE_THEM);
|
|
1742
|
+
return { ok: false };
|
|
1743
|
+
}
|
|
1524
1744
|
const auth2 = deps.auth ?? await resolveAuth(options);
|
|
1525
1745
|
if (auth2 === null) {
|
|
1526
1746
|
voice.write(CANNOT_REACH_DAZZER);
|
|
@@ -1535,7 +1755,6 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1535
1755
|
const held = [];
|
|
1536
1756
|
let added = 0;
|
|
1537
1757
|
let ran = true;
|
|
1538
|
-
const undescribed = [];
|
|
1539
1758
|
const notKept = [];
|
|
1540
1759
|
const alreadyThere = { filed: 0, alreadyDescribed: 0, notKept: 0 };
|
|
1541
1760
|
for (const file of walked.files) {
|
|
@@ -1553,6 +1772,16 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1553
1772
|
}
|
|
1554
1773
|
described = checked.described;
|
|
1555
1774
|
}
|
|
1775
|
+
if (described === void 0 || !("description" in described)) {
|
|
1776
|
+
skipped.push(
|
|
1777
|
+
descriptions !== void 0 && await isFile(file.path, descriptions.identity) ? { relative: file.relative, reason: "the-descriptions-file" } : {
|
|
1778
|
+
relative: file.relative,
|
|
1779
|
+
reason: "no-description",
|
|
1780
|
+
line: notSentNoDescription(asPrinted(file.relative))
|
|
1781
|
+
}
|
|
1782
|
+
);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1556
1785
|
const handle = await openOne(file.path);
|
|
1557
1786
|
if (typeof handle === "string") {
|
|
1558
1787
|
skipped.push({ relative: file.relative, reason: "cannot-open" });
|
|
@@ -1589,9 +1818,6 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1589
1818
|
}
|
|
1590
1819
|
if (outcome.kind === "added") {
|
|
1591
1820
|
added += 1;
|
|
1592
|
-
if (descriptions !== void 0 && !("description" in (described ?? {}))) {
|
|
1593
|
-
undescribed.push(file.relative);
|
|
1594
|
-
}
|
|
1595
1821
|
continue;
|
|
1596
1822
|
}
|
|
1597
1823
|
if (outcome.kind === "already-here") {
|
|
@@ -1607,16 +1833,19 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1607
1833
|
continue;
|
|
1608
1834
|
}
|
|
1609
1835
|
if (outcome.kind === "refused") {
|
|
1836
|
+
const because2 = notAddedBecause(outcome);
|
|
1837
|
+
const reason = (outcome.code === void 0 ? void 0 : FROM_THE_SERVICE[outcome.code]) ?? "refused";
|
|
1610
1838
|
skipped.push({
|
|
1611
1839
|
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",
|
|
1840
|
+
reason,
|
|
1615
1841
|
...outcome.said === void 0 ? {} : { said: outcome.said },
|
|
1616
|
-
|
|
1617
|
-
//
|
|
1618
|
-
//
|
|
1619
|
-
|
|
1842
|
+
...because2 === void 0 ? {} : { line: notAdded(asPrinted(file.relative), because2) },
|
|
1843
|
+
// REFUSED ALTHOUGH IT CARRIED AN ENTRY, which every file sent now does: most likely about
|
|
1844
|
+
// what the entry says, which only the person's assistant can put right, so it is named
|
|
1845
|
+
// without anybody having to run the whole folder again with `--why`. A refusal about the
|
|
1846
|
+
// file's kind or size is about the file, not its entry, so it stays a count that `--why`
|
|
1847
|
+
// names: a folder with thirty spreadsheets in it is one line, not thirty.
|
|
1848
|
+
...reason === "refused" ? { named: true } : {}
|
|
1620
1849
|
});
|
|
1621
1850
|
continue;
|
|
1622
1851
|
}
|
|
@@ -1636,7 +1865,11 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1636
1865
|
skipped.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1637
1866
|
held.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1638
1867
|
notKept.sort((a, b) => a.relative < b.relative ? -1 : a.relative > b.relative ? 1 : 0);
|
|
1639
|
-
|
|
1868
|
+
const undescribed = skipped.filter((skip) => skip.reason === "no-description");
|
|
1869
|
+
if (undescribed.length <= NAMED_WHEN_AT_MOST) {
|
|
1870
|
+
for (const skip of undescribed) skip.named = true;
|
|
1871
|
+
}
|
|
1872
|
+
voice.write(countLine(added, settled.space, skipped, options.why === true));
|
|
1640
1873
|
if (skipped.some((skip) => skip.reason === "the-descriptions-file")) voice.write(keepItBeside);
|
|
1641
1874
|
if (alreadyThere.filed + alreadyThere.alreadyDescribed + alreadyThere.notKept > 0) {
|
|
1642
1875
|
voice.write(alreadyThereDescriptions(alreadyThere));
|
|
@@ -1647,7 +1880,7 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1647
1880
|
if (options.why === true || namedWithoutWhy(skip)) voice.write(whyLine(skip));
|
|
1648
1881
|
}
|
|
1649
1882
|
for (const one of notKept) {
|
|
1650
|
-
for (const line of one.lines) voice.write(`${asPrinted(one.relative)}
|
|
1883
|
+
for (const line of one.lines) voice.write(`${asPrinted(one.relative)}: ${line}`);
|
|
1651
1884
|
}
|
|
1652
1885
|
const onlyOne = held.length === 1 ? held[0] : void 0;
|
|
1653
1886
|
if (onlyOne !== void 0) {
|
|
@@ -1660,7 +1893,15 @@ async function addFolder(options, deps, voice, descriptions) {
|
|
|
1660
1893
|
)
|
|
1661
1894
|
);
|
|
1662
1895
|
}
|
|
1663
|
-
return { ok: ran };
|
|
1896
|
+
return { ok: ran && !skipped.some(LEAVES_A_FILE_OUT) };
|
|
1897
|
+
}
|
|
1898
|
+
async function isFile(path, identity) {
|
|
1899
|
+
try {
|
|
1900
|
+
const info = await statPath(path, { bigint: true });
|
|
1901
|
+
return sameFile({ dev: info.dev, ino: info.ino }, identity);
|
|
1902
|
+
} catch {
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1664
1905
|
}
|
|
1665
1906
|
async function aKeyItWouldMatch(files, descriptionsFile) {
|
|
1666
1907
|
for (const file of files) {
|
|
@@ -2348,6 +2589,9 @@ async function runStatus(options) {
|
|
|
2348
2589
|
case "rejected":
|
|
2349
2590
|
printAuthStatus({ state: "expired" });
|
|
2350
2591
|
return;
|
|
2592
|
+
case "save-refused":
|
|
2593
|
+
printAuthStatus({ state: "save-refused" });
|
|
2594
|
+
return;
|
|
2351
2595
|
case "unreachable":
|
|
2352
2596
|
printAuthStatus({
|
|
2353
2597
|
state: "unverified",
|
|
@@ -2399,6 +2643,15 @@ var { name, version } = require2("../package.json");
|
|
|
2399
2643
|
var client = `${name}/${version}`;
|
|
2400
2644
|
var program = new Command();
|
|
2401
2645
|
program.name("dazzer").description("Sign in to Dazzer from your terminal").version(version);
|
|
2646
|
+
var newerTool;
|
|
2647
|
+
program.hook("preAction", () => {
|
|
2648
|
+
newerTool = lookForNewerTool({ name, version, folder: getConfigDir() });
|
|
2649
|
+
});
|
|
2650
|
+
program.hook("postAction", async () => {
|
|
2651
|
+
const said = await newerTool?.finish();
|
|
2652
|
+
if (said) process.stderr.write(`${said}
|
|
2653
|
+
`);
|
|
2654
|
+
});
|
|
2402
2655
|
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
2656
|
"--described-in <file>",
|
|
2404
2657
|
"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.1",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@dazzer/auth-ui": "workspace:*",
|
|
44
|
-
"@types/node": "^26.6.
|
|
44
|
+
"@types/node": "^26.6.3",
|
|
45
45
|
"esbuild": "^0.28.2",
|
|
46
46
|
"tsx": "^4.23.15",
|
|
47
47
|
"typescript": "^5.7.2",
|