@hraness/sponge 0.6.5 → 0.6.7
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/src/cli/arguments.js +5 -3
- package/dist/src/cli/commands.js +9 -3
- package/dist/src/cli/main.js +27 -4
- package/dist/src/cli/queue.d.ts +2 -0
- package/dist/src/cli/queue.js +22 -0
- package/dist/src/cli/status.d.ts +7 -1
- package/dist/src/cli/status.js +14 -11
- package/native/runtime-manifest.json +5 -5
- package/package.json +1 -1
|
@@ -8,8 +8,10 @@ const globals = {
|
|
|
8
8
|
help: 'boolean',
|
|
9
9
|
};
|
|
10
10
|
/** A usage error names its command so the CLI can print that command's synopsis. */
|
|
11
|
-
const usage = (command) => {
|
|
12
|
-
throw new SpongeError('invalid-input', {
|
|
11
|
+
const usage = (command, unknown) => {
|
|
12
|
+
throw new SpongeError('invalid-input', {
|
|
13
|
+
detail: { usage: true, ...(command ? { command } : {}), ...(unknown ? { unknown } : {}) },
|
|
14
|
+
});
|
|
13
15
|
};
|
|
14
16
|
/** No shell grammar, implicit option coercion, duplicate flags, or unbounded argv. Commands and their rules live in the registry. */
|
|
15
17
|
export function parseArguments(argv) {
|
|
@@ -56,7 +58,7 @@ export function parseArguments(argv) {
|
|
|
56
58
|
if (!command && flags.help)
|
|
57
59
|
return { command: 'help', positional: [], flags: {}, json: false, debug: flags.debug === true };
|
|
58
60
|
if (!command || !spec)
|
|
59
|
-
return usage();
|
|
61
|
+
return usage(undefined, command);
|
|
60
62
|
if (flags.help)
|
|
61
63
|
return { command: 'help', positional: [spec.name], flags: {}, json: false, debug: flags.debug === true };
|
|
62
64
|
if (typeof spec.positional === 'number' ? positional.length !== spec.positional : !spec.positional(positional))
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -4,7 +4,7 @@ import { connectionFor, readConnections } from './config.js';
|
|
|
4
4
|
import { pdfSource } from '../pdf/index.js';
|
|
5
5
|
import { articleSource, inferKind, mediaSource } from '../app/source.js';
|
|
6
6
|
import { pdfToolsAvailable } from '../pdf/tools.js';
|
|
7
|
-
import { queueAction } from './queue.js';
|
|
7
|
+
import { queueAction, queueText } from './queue.js';
|
|
8
8
|
import { portabilityAction } from './portability.js';
|
|
9
9
|
import { restoreLibrary } from '../portability/backup.js';
|
|
10
10
|
import { workerAction, serveWorker, hasDueAcquisition, workerDoctor } from './worker.js';
|
|
@@ -234,7 +234,8 @@ export const commands = [
|
|
|
234
234
|
flags: {},
|
|
235
235
|
library: 'open',
|
|
236
236
|
handler: async (c) => {
|
|
237
|
-
c.
|
|
237
|
+
const action = c.args.positional[0], result = await queueAction(c.store, action, c.args.positional[1]);
|
|
238
|
+
c.emit(result, queueText(c.store, action, result));
|
|
238
239
|
return 0;
|
|
239
240
|
},
|
|
240
241
|
},
|
|
@@ -784,7 +785,12 @@ export const commands = [
|
|
|
784
785
|
{
|
|
785
786
|
name: 'review',
|
|
786
787
|
summary: 'Accept or reject digest fields',
|
|
787
|
-
usage: [
|
|
788
|
+
usage: [
|
|
789
|
+
[
|
|
790
|
+
'review DIGEST --accept SPEC --reject SPEC',
|
|
791
|
+
'Review fields: all, gist, idea:N, quote:N.M, as `sponge digest ITEM` labels them',
|
|
792
|
+
],
|
|
793
|
+
],
|
|
788
794
|
positional: 1,
|
|
789
795
|
flags: { accept: 'value', reject: 'value' },
|
|
790
796
|
library: 'open',
|
package/dist/src/cli/main.js
CHANGED
|
@@ -4,20 +4,43 @@ import { lstat } from 'node:fs/promises';
|
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { terminalSafe } from './terminal.js';
|
|
6
6
|
import { parseArguments } from './arguments.js';
|
|
7
|
-
import { command, usageOf } from './commands.js';
|
|
7
|
+
import { command, commands, usageOf } from './commands.js';
|
|
8
8
|
import { openLibrary } from '../store/index.js';
|
|
9
9
|
import { failure } from '../acquisition/errors.js';
|
|
10
10
|
import { SpongeError, codeOf, publicCode, exitFor, hints, detailHints, debugChain, } from '../errors.js';
|
|
11
|
+
/** Names the registered command closest to a mistyped one, never the typed text, which may hold a URL or a secret. */
|
|
12
|
+
function unknownCommandHint(typed) {
|
|
13
|
+
const distance = (a, b) => {
|
|
14
|
+
let row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
15
|
+
for (let i = 1; i <= a.length; i++) {
|
|
16
|
+
const next = [i];
|
|
17
|
+
for (let j = 1; j <= b.length; j++)
|
|
18
|
+
next[j] = Math.min(row[j] + 1, next[j - 1] + 1, row[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
19
|
+
row = next;
|
|
20
|
+
}
|
|
21
|
+
return row[b.length];
|
|
22
|
+
};
|
|
23
|
+
const [best] = commands
|
|
24
|
+
.map((c) => ({ name: c.name, d: distance(typed.toLowerCase().slice(0, 32), c.name) }))
|
|
25
|
+
.filter(({ d }) => d <= 2)
|
|
26
|
+
.sort((a, b) => a.d - b.d);
|
|
27
|
+
return `Unknown command.${best ? ` Did you mean \`sponge ${best.name}\`?` : ''} Run \`sponge help\` for the list.`;
|
|
28
|
+
}
|
|
11
29
|
/** Failures print one categorical JSON line: the public code, its specific `reason` when narrower, and a fixed hint.
|
|
12
30
|
* Causes, details, paths and upstream messages appear only with --debug or SPONGE_DEBUG=1. */
|
|
13
31
|
function report(error, requestId, debug) {
|
|
14
|
-
const reason = codeOf(error), code = publicCode(reason), issues = error?.issues,
|
|
32
|
+
const reason = codeOf(error), code = publicCode(reason), issues = error?.issues, detail = error?.detail, usage = detail?.command, unknown = detail?.unknown;
|
|
15
33
|
if (Array.isArray(issues))
|
|
16
34
|
process.stderr.write(JSON.stringify({ issues }) + '\n');
|
|
17
35
|
const hint = code === 'invalid-input' && typeof usage === 'string' && command(usage)
|
|
18
36
|
? `Usage: ${usageOf(usage).replace(/\n/g, ' | ')}`
|
|
19
|
-
:
|
|
20
|
-
|
|
37
|
+
: code === 'invalid-input' && typeof unknown === 'string'
|
|
38
|
+
? unknownCommandHint(unknown)
|
|
39
|
+
: (detailHints[reason] ?? hints[code]);
|
|
40
|
+
// A usage error happens before any acquisition starts, so it names no phase.
|
|
41
|
+
const { phase, ...envelope } = failure(requestId, code);
|
|
42
|
+
process.stderr.write(JSON.stringify({ ...envelope, ...(detail?.usage ? {} : { phase }), ...(reason !== code ? { reason } : {}), hint }) +
|
|
43
|
+
'\n');
|
|
21
44
|
if (debug)
|
|
22
45
|
process.stderr.write(debugChain(error));
|
|
23
46
|
return exitFor(code);
|
package/dist/src/cli/queue.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import type { LibraryStore } from '../store/index.js';
|
|
2
2
|
export declare function queueAction(store: LibraryStore, action: 'status' | 'cancel' | 'retry' | 'recover', jobId?: string): Promise<Record<string, unknown>>;
|
|
3
|
+
/** `sponge queue` without --json: open jobs with the IDs that cancel and retry take, then what was done. */
|
|
4
|
+
export declare function queueText(store: LibraryStore, action: 'status' | 'cancel' | 'retry' | 'recover', result: Record<string, unknown>): string;
|
package/dist/src/cli/queue.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { recoverWorker } from '../worker/custody.js';
|
|
2
2
|
import { SpongeError } from '../errors.js';
|
|
3
|
+
import { openJobs } from './status.js';
|
|
3
4
|
export async function queueAction(store, action, jobId) {
|
|
4
5
|
if (action === 'status') {
|
|
5
6
|
if (jobId)
|
|
@@ -25,3 +26,24 @@ export async function queueAction(store, action, jobId) {
|
|
|
25
26
|
throw new SpongeError('invalid-input');
|
|
26
27
|
return { jobId, action };
|
|
27
28
|
}
|
|
29
|
+
/** `sponge queue` without --json: open jobs with the IDs that cancel and retry take, then what was done. */
|
|
30
|
+
export function queueText(store, action, result) {
|
|
31
|
+
if (action === 'cancel')
|
|
32
|
+
return `Cancelled ${String(result.jobId)}`;
|
|
33
|
+
if (action === 'retry')
|
|
34
|
+
return `Queued ${String(result.jobId)} to run again`;
|
|
35
|
+
if (action === 'recover')
|
|
36
|
+
return result.recovered
|
|
37
|
+
? `Recovered the stopped worker; ${String(result.attempts)} unfinished attempts released`
|
|
38
|
+
: 'Nothing to recover';
|
|
39
|
+
const jobs = result.jobs, open = openJobs(store), titles = new Map(store.list().map((item) => [item.itemId, item.title])), lines = open.length ? open.map((job) => jobLine(job, titles.get(job.item_id))) : ['Nothing waiting'];
|
|
40
|
+
if (jobs.length > open.length)
|
|
41
|
+
lines.push(`${jobs.length - open.length} settled or replaced jobs hidden; --json lists every job`);
|
|
42
|
+
if (result.pendingCleanup)
|
|
43
|
+
lines.push(`Pending cleanup: ${String(result.pendingCleanup)}; run sponge queue recover`);
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
function jobLine(job, title) {
|
|
47
|
+
const reason = job.reason && job.reason !== job.state ? ` (${String(job.reason)})` : '';
|
|
48
|
+
return `${String(job.id)} ${String(job.operation)} ${String(job.state)}${reason} ${String(job.created_at).slice(0, 10)}${title ? ` ${title}` : ''}`;
|
|
49
|
+
}
|
package/dist/src/cli/status.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { LibraryStore } from '../store/index.js';
|
|
2
|
+
import type { Row } from '../store/context.js';
|
|
3
|
+
/**
|
|
4
|
+
* Jobs still waiting, running or needing the reader, oldest first. Settled jobs are history, and only each target's
|
|
5
|
+
* newest job counts: a failure that a later capture of the item (or index job) fixed is history too.
|
|
6
|
+
*/
|
|
7
|
+
export declare function openJobs(store: LibraryStore): Row[];
|
|
2
8
|
/** What the library holds, what is waiting or failed, and what to run next: one screen for `sponge status`. */
|
|
3
9
|
export declare function libraryStatus(store: LibraryStore): {
|
|
4
10
|
items: {
|
|
@@ -19,7 +25,7 @@ export declare function libraryStatus(store: LibraryStore): {
|
|
|
19
25
|
jobs: {
|
|
20
26
|
operation: string;
|
|
21
27
|
state: string;
|
|
22
|
-
reason: string
|
|
28
|
+
reason: string;
|
|
23
29
|
}[];
|
|
24
30
|
pendingCleanup: number;
|
|
25
31
|
};
|
package/dist/src/cli/status.js
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jobs still waiting, running or needing the reader, oldest first. Settled jobs are history, and only each target's
|
|
3
|
+
* newest job counts: a failure that a later capture of the item (or index job) fixed is history too.
|
|
4
|
+
*/
|
|
5
|
+
export function openJobs(store) {
|
|
6
|
+
const newest = new Map();
|
|
7
|
+
for (const job of store.queue.list())
|
|
8
|
+
newest.set(job.operation === 'index' ? 'index' : `${job.operation} ${job.item_id}`, job);
|
|
9
|
+
return [...newest.values()].filter((job) => !['succeeded', 'cancelled'].includes(String(job.state)));
|
|
10
|
+
}
|
|
1
11
|
const plural = (n, one, many = one + 's') => `${n} ${n === 1 ? one : many}`;
|
|
2
12
|
/** What the library holds, what is waiting or failed, and what to run next: one screen for `sponge status`. */
|
|
3
13
|
export function libraryStatus(store) {
|
|
4
14
|
const items = store.list(), kinds = {}, digests = { accepted: 0, rejected: 0, draft: 0, none: 0 };
|
|
5
15
|
for (const item of items) {
|
|
6
16
|
kinds[item.kind] = (kinds[item.kind] ?? 0) + 1;
|
|
7
|
-
const
|
|
8
|
-
digests[
|
|
17
|
+
const digest = store.derivations.current('digest', item.itemId), gist = digest?.review.gist;
|
|
18
|
+
digests[!digest ? 'none' : gist === 'accepted' || gist === 'rejected' ? gist : 'draft']++;
|
|
9
19
|
}
|
|
10
|
-
|
|
11
|
-
// Only each target's newest job counts: a failure that a later capture of the item (or index job) fixed is history.
|
|
12
|
-
const newest = new Map();
|
|
13
|
-
for (const job of [...store.queue.list()].sort((a, b) => String(a.created_at).localeCompare(String(b.created_at))))
|
|
14
|
-
newest.set(job.operation === 'index' ? 'index' : `${job.operation} ${job.item_id}`, job);
|
|
15
|
-
const jobs = [...newest.values()]
|
|
16
|
-
.filter((job) => !['succeeded', 'cancelled'].includes(String(job.state)))
|
|
17
|
-
.map((job) => ({
|
|
20
|
+
const jobs = openJobs(store).map((job) => ({
|
|
18
21
|
operation: String(job.operation),
|
|
19
22
|
state: String(job.state),
|
|
20
23
|
reason: job.reason ?? null,
|
|
@@ -38,7 +41,7 @@ export function statusText(s, worker) {
|
|
|
38
41
|
};
|
|
39
42
|
const counts = {};
|
|
40
43
|
for (const job of s.jobs) {
|
|
41
|
-
const key = `${job.operation} ${job.state}${job.reason ? ` (${job.reason})` : ''}`;
|
|
44
|
+
const key = `${job.operation} ${job.state}${job.reason && job.reason !== job.state ? ` (${job.reason})` : ''}`;
|
|
42
45
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
43
46
|
}
|
|
44
47
|
const failed = s.jobs.some((job) => !['queued', 'running'].includes(job.state));
|
|
@@ -33,16 +33,16 @@
|
|
|
33
33
|
}
|
|
34
34
|
],
|
|
35
35
|
"build": {
|
|
36
|
-
"asrSourceSha256": "
|
|
36
|
+
"asrSourceSha256": "628996352def6d14b40a212f23096045a9ac8a9cf14a8a1e11bbd4f57971c37d",
|
|
37
37
|
"clangVersion": "Apple clang version 21.0.0 (clang-2100.1.1.101)",
|
|
38
|
-
"embeddingSourceSha256": "
|
|
39
|
-
"mediaSourceSha256": "
|
|
38
|
+
"embeddingSourceSha256": "ca177874aa881521ee9235a7dedc243608be15d5a96f0fff4430f4d7a6c2c64e",
|
|
39
|
+
"mediaSourceSha256": "4b5113123a6e6f5b00de76ae42c4969f650a1093bc1f99ddf70177f815a65851",
|
|
40
40
|
"rustcVersion": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
|
|
41
41
|
"swiftVersion": "Apple Swift version 6.3.3 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)"
|
|
42
42
|
},
|
|
43
43
|
"minimumMacOS": "26.0",
|
|
44
|
-
"packageVersion": "0.6.
|
|
44
|
+
"packageVersion": "0.6.7",
|
|
45
45
|
"platform": "darwin",
|
|
46
46
|
"schemaVersion": 1,
|
|
47
|
-
"sourceCommit": "
|
|
47
|
+
"sourceCommit": "60a3273cf828ae3c4ef0c6a3db1ea15984f00d34"
|
|
48
48
|
}
|