@12ui/design 0.2.15 → 0.2.16
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 +21 -1
- package/dist/branch-execution.d.ts +5 -0
- package/dist/branch-execution.d.ts.map +1 -1
- package/dist/branch-execution.js +26 -8
- package/dist/branch-execution.js.map +1 -1
- package/dist/branch-page-conversion.d.ts +18 -2
- package/dist/branch-page-conversion.d.ts.map +1 -1
- package/dist/branch-page-conversion.js +36 -16
- package/dist/branch-page-conversion.js.map +1 -1
- package/dist/branch-run-record.d.ts +3 -0
- package/dist/branch-run-record.d.ts.map +1 -1
- package/dist/branch-run-record.js.map +1 -1
- package/dist/cli-capabilities.d.ts +15 -5
- package/dist/cli-capabilities.d.ts.map +1 -1
- package/dist/cli-capabilities.js +19 -5
- package/dist/cli-capabilities.js.map +1 -1
- package/dist/cli-draft-command.d.ts +2 -0
- package/dist/cli-draft-command.d.ts.map +1 -1
- package/dist/cli-draft-command.js +22 -4
- package/dist/cli-draft-command.js.map +1 -1
- package/dist/cli-package-command.d.ts +2 -0
- package/dist/cli-package-command.d.ts.map +1 -1
- package/dist/cli-package-command.js +11 -0
- package/dist/cli-package-command.js.map +1 -1
- package/dist/cli-skill-command.d.ts.map +1 -1
- package/dist/cli-skill-command.js +27 -12
- package/dist/cli-skill-command.js.map +1 -1
- package/dist/cli.js +7 -7
- package/dist/cli.js.map +1 -1
- package/dist/conversion-receipt-store.d.ts +75 -0
- package/dist/conversion-receipt-store.d.ts.map +1 -0
- package/dist/conversion-receipt-store.js +143 -0
- package/dist/conversion-receipt-store.js.map +1 -0
- package/dist/conversion-receipt.d.ts +139 -43
- package/dist/conversion-receipt.d.ts.map +1 -1
- package/dist/conversion-receipt.js +241 -73
- package/dist/conversion-receipt.js.map +1 -1
- package/dist/draft-adoption.d.ts +48 -0
- package/dist/draft-adoption.d.ts.map +1 -0
- package/dist/draft-adoption.js +237 -0
- package/dist/draft-adoption.js.map +1 -0
- package/dist/draft-run.d.ts +10 -0
- package/dist/draft-run.d.ts.map +1 -1
- package/dist/draft-run.js +16 -4
- package/dist/draft-run.js.map +1 -1
- package/dist/legacy-skill-catalog.d.ts.map +1 -1
- package/dist/legacy-skill-catalog.js +40 -0
- package/dist/legacy-skill-catalog.js.map +1 -1
- package/dist/package-submission.d.ts +12 -1
- package/dist/package-submission.d.ts.map +1 -1
- package/dist/package-submission.js +2 -0
- package/dist/package-submission.js.map +1 -1
- package/dist/skill-clients.d.ts +39 -0
- package/dist/skill-clients.d.ts.map +1 -0
- package/dist/skill-clients.js +156 -0
- package/dist/skill-clients.js.map +1 -0
- package/dist/skill-installer.d.ts +2 -3
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +9 -101
- package/dist/skill-installer.js.map +1 -1
- package/package.json +1 -1
- package/skills/design/SKILL.md +2 -2
- package/skills/design-branch/SKILL.md +3 -3
- package/skills/design-convert/SKILL.md +3 -3
- package/skills/design-draft/SKILL.md +3 -3
- package/skills/design-search/SKILL.md +3 -3
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readdir, readFile, rename, rm, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Where a user's conversion receipts live, and the only code that touches those
|
|
7
|
+
* files.
|
|
8
|
+
*
|
|
9
|
+
* The records are per USER, not per image: the same approved design converted
|
|
10
|
+
* from a downloads folder, a run directory, and a project checkout is the same
|
|
11
|
+
* bytes and the same paid conversion, and a receipt written beside each copy
|
|
12
|
+
* both litters the user's directories and misses every copy it was not written
|
|
13
|
+
* beside. So the store is keyed by what actually identifies the work — the
|
|
14
|
+
* digest of the submitted bytes and the model that converted them — and lives
|
|
15
|
+
* under the 12ui home.
|
|
16
|
+
*
|
|
17
|
+
* `~/.12ui` is that home. Credentials sit in the config root
|
|
18
|
+
* (`~/.config/12ui/credentials.json`) because they are configuration, and the
|
|
19
|
+
* managed CLI installs itself into the data root (`~/.local/share/12ui`)
|
|
20
|
+
* because it is installed software; a receipt is neither. It is per-user
|
|
21
|
+
* runtime evidence the CLI can always re-buy, and `~/.12ui` is where this
|
|
22
|
+
* codebase already keeps per-user 12ui runtime state (`legacy-codex-cleanup.ts`
|
|
23
|
+
* reads `~/.12ui/codex-design`). `TWELVE_UI_HOME` overrides it wholesale, which
|
|
24
|
+
* is what a test — or a user with a shared home — points somewhere else.
|
|
25
|
+
*
|
|
26
|
+
* Shape: one small JSON file per key, never one appended log. A point lookup
|
|
27
|
+
* reads exactly the record it wants instead of the whole history; a withdrawal
|
|
28
|
+
* unlinks one file instead of rewriting the log; and two CLI invocations
|
|
29
|
+
* running at once — the common case, since branch runs are launched in
|
|
30
|
+
* parallel — never contend for a single file, so no lock is needed. Every write
|
|
31
|
+
* lands through a temp file and a rename, so a reader either sees the previous
|
|
32
|
+
* record or the new one and never a half-written one.
|
|
33
|
+
*/
|
|
34
|
+
export const TWELVE_UI_HOME_ENV = 'TWELVE_UI_HOME';
|
|
35
|
+
export const TWELVE_UI_HOME_DIRECTORY_NAME = '.12ui';
|
|
36
|
+
export const CONVERSION_RECEIPT_STORE_DIRECTORY_NAME = 'conversions';
|
|
37
|
+
/** How many records one invocation is willing to inspect while pruning. */
|
|
38
|
+
export const CONVERSION_RECEIPT_PRUNE_LIMIT = 64;
|
|
39
|
+
export const resolveTwelveUiHome = (environment = process.env, homeDirectory = os.homedir()) => {
|
|
40
|
+
const explicit = environment[TWELVE_UI_HOME_ENV]?.trim();
|
|
41
|
+
return explicit
|
|
42
|
+
? path.resolve(explicit)
|
|
43
|
+
: path.join(homeDirectory, TWELVE_UI_HOME_DIRECTORY_NAME);
|
|
44
|
+
};
|
|
45
|
+
export const resolveConversionReceiptStore = (environment = process.env, homeDirectory = os.homedir()) => path.join(resolveTwelveUiHome(environment, homeDirectory), CONVERSION_RECEIPT_STORE_DIRECTORY_NAME);
|
|
46
|
+
/**
|
|
47
|
+
* The key, as a filename. The digest is already filename-safe and is checked
|
|
48
|
+
* before it is used; the model is whatever the server called itself, so it is
|
|
49
|
+
* reduced to the same alphabet rather than trusted to be one path segment.
|
|
50
|
+
*/
|
|
51
|
+
export const conversionReceiptRecordName = (key) => {
|
|
52
|
+
if (!/^[a-f0-9]{64}$/u.test(key.sha256)) {
|
|
53
|
+
throw new Error('A conversion receipt key needs the sha256 of the bytes that were submitted');
|
|
54
|
+
}
|
|
55
|
+
const model = key.model.replace(/[^A-Za-z0-9._-]/gu, '_');
|
|
56
|
+
if (!model)
|
|
57
|
+
throw new Error('A conversion receipt key needs the model that converted the bytes');
|
|
58
|
+
return `${key.sha256}.${model}.json`;
|
|
59
|
+
};
|
|
60
|
+
export const conversionReceiptRecordPath = (store, key) => path.join(store, conversionReceiptRecordName(key));
|
|
61
|
+
/** A missing, unreadable, or unparseable record is simply no record. */
|
|
62
|
+
export const readConversionReceiptRecord = async (store, key) => {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(await readFile(conversionReceiptRecordPath(store, key), 'utf8'));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Atomic replace. The temp file is created exclusively under a unique name so
|
|
72
|
+
* two invocations writing the same key at the same time cannot land in each
|
|
73
|
+
* other's buffer, and the rename is what any concurrent reader observes.
|
|
74
|
+
*/
|
|
75
|
+
export const writeConversionReceiptRecord = async (store, key, record) => {
|
|
76
|
+
const target = conversionReceiptRecordPath(store, key);
|
|
77
|
+
await mkdir(store, { recursive: true, mode: 0o700 });
|
|
78
|
+
const temporary = path.join(store, `.${randomUUID()}.tmp`);
|
|
79
|
+
try {
|
|
80
|
+
await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, {
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
flag: 'wx',
|
|
83
|
+
mode: 0o600,
|
|
84
|
+
});
|
|
85
|
+
await rename(temporary, target);
|
|
86
|
+
return target;
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
await rm(temporary, { force: true });
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/** Null when there was nothing to remove; removal is never an error. */
|
|
94
|
+
export const removeConversionReceiptRecord = async (store, key) => {
|
|
95
|
+
const target = conversionReceiptRecordPath(store, key);
|
|
96
|
+
try {
|
|
97
|
+
await rm(target);
|
|
98
|
+
return target;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Bounded expiry hygiene, run on the invocations that already touch the store.
|
|
106
|
+
*
|
|
107
|
+
* A conversion lives about a day and a store record outlives it by however long
|
|
108
|
+
* the user goes without converting, so without this the store only grows. It
|
|
109
|
+
* inspects at most `limit` records per call, starting at a rotating offset, so
|
|
110
|
+
* a large store costs the same as a small one per invocation and is still swept
|
|
111
|
+
* completely over several. Only records the caller's predicate calls expired
|
|
112
|
+
* are removed — anything this version cannot read is left alone, because an
|
|
113
|
+
* unreadable record is as likely to belong to a newer CLI as to be junk.
|
|
114
|
+
*/
|
|
115
|
+
export const pruneConversionReceiptRecords = async (args) => {
|
|
116
|
+
let names;
|
|
117
|
+
try {
|
|
118
|
+
names = (await readdir(args.store)).filter((name) => name.endsWith('.json'));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
if (names.length === 0)
|
|
124
|
+
return [];
|
|
125
|
+
const limit = Math.max(1, args.limit ?? CONVERSION_RECEIPT_PRUNE_LIMIT);
|
|
126
|
+
const start = names.length <= limit ? 0 : Math.floor(Math.random() * names.length);
|
|
127
|
+
const sampled = Array.from({ length: Math.min(limit, names.length) }, (_unused, index) => names[(start + index) % names.length]);
|
|
128
|
+
const removed = await Promise.all(sampled.map(async (name) => {
|
|
129
|
+
const file = path.join(args.store, name);
|
|
130
|
+
try {
|
|
131
|
+
const record = JSON.parse(await readFile(file, 'utf8'));
|
|
132
|
+
if (!args.expired(record))
|
|
133
|
+
return null;
|
|
134
|
+
await rm(file, { force: true });
|
|
135
|
+
return file;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
}));
|
|
141
|
+
return removed.filter((entry) => entry !== null);
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=conversion-receipt-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversion-receipt-store.js","sourceRoot":"","sources":["../src/conversion-receipt-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { ConversionOperation } from './conversion-client.js';
|
|
2
2
|
import type { ConversionOutput } from './conversion-outputs.js';
|
|
3
|
+
import { type ConversionReceiptKey } from './conversion-receipt-store.js';
|
|
4
|
+
import type { PackageSubmissionResult } from './package-submission.js';
|
|
3
5
|
/**
|
|
4
6
|
* The single owner of "an image's conversion was already bought, and here is
|
|
5
7
|
* the proof".
|
|
@@ -14,14 +16,30 @@ import type { ConversionOutput } from './conversion-outputs.js';
|
|
|
14
16
|
* the upload — so a false claim is a failed page, not a cheap win.
|
|
15
17
|
*
|
|
16
18
|
* This is therefore a receipt of a proved fact, not a cache: it records the
|
|
17
|
-
* digest of the exact bytes a succeeded conversion was admitted with
|
|
18
|
-
*
|
|
19
|
-
*
|
|
19
|
+
* digest of the exact bytes a succeeded conversion was admitted with. A later
|
|
20
|
+
* run reuses it only when the bytes it is about to submit hash identically.
|
|
21
|
+
* Absent proof it converts fresh.
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
* The records live in the per-user store (`conversion-receipt-store.ts`), keyed
|
|
24
|
+
* by that digest and the model, because the fact is about bytes and not about
|
|
25
|
+
* where a copy of them happens to sit. `12ui convert`, `12ui convert package`,
|
|
26
|
+
* and `12ui branch execute --convert` write them, `12ui branch execute
|
|
27
|
+
* --convert` reads them, and only this module decides what counts as proof.
|
|
28
|
+
*
|
|
29
|
+
* SUNSET — legacy sidecars. Receipts shipped in 0.2.15 as
|
|
30
|
+
* `<image>.conversion.json` written beside the input image. Those files are
|
|
31
|
+
* still READ beside a winner image for one release cycle and migrated into the
|
|
32
|
+
* store the first time they are seen, so an upgrade never re-buys a conversion
|
|
33
|
+
* a 0.2.15 convert already paid for. Nothing writes them any more. Delete the
|
|
34
|
+
* legacy read path — `LEGACY_CONVERSION_RECEIPT_SUFFIX`,
|
|
35
|
+
* `legacyConversionReceiptPath`, `readLegacyConversionReceipts`, and the
|
|
36
|
+
* `legacyImagePaths` argument of `invalidateConversionReceipts` — one release
|
|
37
|
+
* after 0.2.16; by then every receipt a 0.2.15 convert wrote has long outlived
|
|
38
|
+
* the ~24h conversion it names.
|
|
23
39
|
*/
|
|
24
|
-
export declare const
|
|
40
|
+
export declare const LEGACY_CONVERSION_RECEIPT_SUFFIX = ".conversion.json";
|
|
41
|
+
export type { ConversionReceiptKey } from './conversion-receipt-store.js';
|
|
42
|
+
export { resolveConversionReceiptStore } from './conversion-receipt-store.js';
|
|
25
43
|
/**
|
|
26
44
|
* The output an attachable conversion must have produced. A package converts
|
|
27
45
|
* every viewport to a LayerDoc and buys the page export separately, so the
|
|
@@ -34,8 +52,9 @@ export declare const REUSABLE_CONVERSION_OUTPUT: ConversionOutput;
|
|
|
34
52
|
* attachment expiring within this window (`PACKAGE_SOURCE_MINIMUM_REMAINING_MS`)
|
|
35
53
|
* because composition reads the source run at the END of a package drive, and
|
|
36
54
|
* that refusal fails the page after its screens are already paid for.
|
|
37
|
-
* Conversions live 24h while a receipt sits
|
|
38
|
-
* so an expiry that is not checked here is a page failure waiting
|
|
55
|
+
* Conversions live 24h while a stored receipt sits in the user's home
|
|
56
|
+
* indefinitely, so an expiry that is not checked here is a page failure waiting
|
|
57
|
+
* for day two.
|
|
39
58
|
*/
|
|
40
59
|
export declare const CONVERSION_REUSE_MINIMUM_REMAINING_MS: number;
|
|
41
60
|
export type ConversionReceipt = {
|
|
@@ -46,11 +65,12 @@ export type ConversionReceipt = {
|
|
|
46
65
|
/** A reused conversion must have run the model the package declares. */
|
|
47
66
|
model: string;
|
|
48
67
|
/**
|
|
49
|
-
* The operation that bought it: a branch run id,
|
|
50
|
-
* conversion's own id. An operation never reuses its own
|
|
51
|
-
* package key is derived per run and page, so attaching a
|
|
52
|
-
* would change the request body under a key the server
|
|
53
|
-
* is `409 idempotency_conflict` on a page that was
|
|
68
|
+
* The operation that bought it: a branch run id, a package id, or for
|
|
69
|
+
* `12ui convert` the conversion's own id. An operation never reuses its own
|
|
70
|
+
* receipt — a branch package key is derived per run and page, so attaching a
|
|
71
|
+
* reuse on a resume would change the request body under a key the server
|
|
72
|
+
* already settled, which is `409 idempotency_conflict` on a page that was
|
|
73
|
+
* already paid for.
|
|
54
74
|
*/
|
|
55
75
|
boughtBy: string;
|
|
56
76
|
/** When the referenced conversion stops being attachable at all. */
|
|
@@ -58,41 +78,50 @@ export type ConversionReceipt = {
|
|
|
58
78
|
createdAt: string;
|
|
59
79
|
};
|
|
60
80
|
export type ConversionReceiptWrite = {
|
|
61
|
-
written
|
|
62
|
-
|
|
81
|
+
/** Where the record landed, or null when nothing was written. */
|
|
82
|
+
path: string | null;
|
|
83
|
+
failure?: {
|
|
63
84
|
path: string;
|
|
64
85
|
detail: string;
|
|
65
|
-
}
|
|
86
|
+
};
|
|
66
87
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
export declare const
|
|
70
|
-
export declare const readConversionReceipts: (imagePaths: readonly string[]) => Promise<ConversionReceipt[]>;
|
|
88
|
+
/** Where 0.2.15 wrote a receipt. Read for one release cycle, never written. */
|
|
89
|
+
export declare const legacyConversionReceiptPath: (imagePath: string) => string;
|
|
90
|
+
export declare const conversionReceiptKey: (receipt: ConversionReceipt) => ConversionReceiptKey;
|
|
71
91
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
92
|
+
* The stored record for exactly these bytes and this model, or nothing.
|
|
93
|
+
*
|
|
94
|
+
* An expired record is removed as it is read: it is the one expired record this
|
|
95
|
+
* invocation is certain about, and leaving it would have the next run read it
|
|
96
|
+
* again to reach the same conclusion.
|
|
77
97
|
*/
|
|
78
|
-
export declare const
|
|
98
|
+
export declare const readStoredConversionReceipt: (key: ConversionReceiptKey, options?: {
|
|
99
|
+
store?: string;
|
|
100
|
+
now?: Date;
|
|
101
|
+
}) => Promise<ConversionReceipt | undefined>;
|
|
79
102
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
* Record a proved conversion. A write that fails is not a command failure: the
|
|
104
|
+
* receipt is an optimisation, and its absence only costs one conversion.
|
|
105
|
+
* Callers that can say so report the failure; the rest ignore it.
|
|
106
|
+
*/
|
|
107
|
+
export declare const recordConversionReceipt: (receipt: ConversionReceipt, options?: {
|
|
108
|
+
store?: string;
|
|
109
|
+
now?: Date;
|
|
110
|
+
}) => Promise<ConversionReceiptWrite>;
|
|
111
|
+
/**
|
|
112
|
+
* The 0.2.15 sidecars beside these images, carried forward.
|
|
89
113
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
114
|
+
* Every one that is still live is copied into the store the moment it is seen,
|
|
115
|
+
* so the upgrade costs nothing and the next run finds the proof by digest no
|
|
116
|
+
* matter which copy of the image it starts from. An existing store record wins:
|
|
117
|
+
* it was written by a later command about the same bytes, and overwriting it
|
|
118
|
+
* with the sidecar would replace a fresher expiry with an older one. Migration
|
|
119
|
+
* is best-effort — the receipt is still honoured this run either way.
|
|
94
120
|
*/
|
|
95
|
-
export declare const
|
|
121
|
+
export declare const readLegacyConversionReceipts: (imagePaths: readonly string[], options?: {
|
|
122
|
+
store?: string;
|
|
123
|
+
now?: Date;
|
|
124
|
+
}) => Promise<ConversionReceipt[]>;
|
|
96
125
|
/**
|
|
97
126
|
* The proof gate. Every condition is a fact about the bytes in hand, never an
|
|
98
127
|
* assumption about what an earlier command probably did.
|
|
@@ -106,22 +135,89 @@ export declare const reusableConversionId: (args: {
|
|
|
106
135
|
boughtBy: string;
|
|
107
136
|
now?: Date;
|
|
108
137
|
}) => string | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* The gate, over the store and over whatever the caller already holds.
|
|
140
|
+
*
|
|
141
|
+
* The store is consulted first because it is the record every command writes;
|
|
142
|
+
* the in-hand receipts are the 0.2.15 sidecars this run read beside the winner,
|
|
143
|
+
* which stay proof for one release cycle even if their migration failed.
|
|
144
|
+
*/
|
|
145
|
+
export declare const findReusableConversionId: (args: {
|
|
146
|
+
sha256: string;
|
|
147
|
+
model: string;
|
|
148
|
+
boughtBy: string;
|
|
149
|
+
receipts?: readonly ConversionReceipt[];
|
|
150
|
+
store?: string;
|
|
151
|
+
now?: Date;
|
|
152
|
+
}) => Promise<string | undefined>;
|
|
153
|
+
/**
|
|
154
|
+
* Withdraw a receipt the SERVER disproved, so the next run does not repeat the
|
|
155
|
+
* dance the last one already paid a retry for.
|
|
156
|
+
*
|
|
157
|
+
* The bar is deliberately narrow: only an explicit refusal of the attachment at
|
|
158
|
+
* admission — the server having read the referenced conversion's durable
|
|
159
|
+
* evidence and rejected the claim — is proof that this receipt is no longer
|
|
160
|
+
* proof of anything. A network error, a timeout, or any other transient failure
|
|
161
|
+
* says nothing about the referenced conversion, and deleting on one of those
|
|
162
|
+
* would throw away a live receipt and buy its conversion again for nothing.
|
|
163
|
+
*
|
|
164
|
+
* Only records naming the disproven conversion are withdrawn: a newer record
|
|
165
|
+
* under the same key describes different work and is untouched. Removal is
|
|
166
|
+
* best-effort, because a receipt that cannot be deleted only costs the next run
|
|
167
|
+
* one refused attachment and one retry — the same price this run paid.
|
|
168
|
+
*/
|
|
169
|
+
export declare const invalidateConversionReceipts: (args: {
|
|
170
|
+
conversionId: string;
|
|
171
|
+
/** The keys the disproven claim could have come from. */
|
|
172
|
+
keys?: readonly ConversionReceiptKey[];
|
|
173
|
+
/** Sunset: 0.2.15 sidecars beside these images, if they name it. */
|
|
174
|
+
legacyImagePaths?: readonly string[];
|
|
175
|
+
store?: string;
|
|
176
|
+
}) => Promise<string[]>;
|
|
109
177
|
/**
|
|
110
178
|
* What `12ui convert` leaves behind for the branch run that follows it.
|
|
111
179
|
*
|
|
112
180
|
* Only a succeeded image-to-LayerDoc conversion can stand in for a package
|
|
113
|
-
* viewport, so anything else
|
|
181
|
+
* viewport, so anything else records nothing at all: a receipt for an `html`
|
|
114
182
|
* conversion would be a claim the server refuses, and the refusal costs a whole
|
|
115
183
|
* page. The digest is of the bytes actually submitted, which is what `--width`
|
|
116
184
|
* normalization changes and what the server admitted the run with.
|
|
117
185
|
*/
|
|
118
186
|
export declare const recordConvertedImageReceipt: (args: {
|
|
119
|
-
/** The file the caller converted; the branch run looks for the receipt here. */
|
|
120
|
-
imagePath: string;
|
|
121
187
|
/** The EXACT bytes submitted, after any `--width` normalization. */
|
|
122
188
|
bytes: Uint8Array;
|
|
123
189
|
operation: Pick<ConversionOperation, "id" | "status" | "output" | "model" | "expires_at" | "input_kind">;
|
|
190
|
+
store?: string;
|
|
124
191
|
warn?: (message: string) => void;
|
|
125
192
|
now?: Date;
|
|
126
193
|
}) => Promise<ConversionReceipt | undefined>;
|
|
194
|
+
/**
|
|
195
|
+
* What `12ui convert package` leaves behind: one receipt per viewport whose own
|
|
196
|
+
* child conversion the package bought.
|
|
197
|
+
*
|
|
198
|
+
* A package converts each viewport to a LayerDoc and composes the page from
|
|
199
|
+
* those children, so every viewport of a submitted page is exactly the artifact
|
|
200
|
+
* a later package can attach instead of buying — the same fact the branch path
|
|
201
|
+
* records for the winner, for every screenshot the user named.
|
|
202
|
+
*
|
|
203
|
+
* A viewport that ATTACHED a conversion bought nothing, so it records nothing.
|
|
204
|
+
* The expiry reported is the package's own, which its children cannot outlive
|
|
205
|
+
* backwards, and the model is the one the server says it ran; without either
|
|
206
|
+
* fact nothing here proves a conversion is still attachable, so nothing is
|
|
207
|
+
* recorded and the caller is told once.
|
|
208
|
+
*/
|
|
209
|
+
export declare const recordPackageConversionReceipts: (args: {
|
|
210
|
+
pages: readonly {
|
|
211
|
+
id: string;
|
|
212
|
+
viewports: readonly {
|
|
213
|
+
id: string;
|
|
214
|
+
bytes: Uint8Array;
|
|
215
|
+
sourceConversionId?: string;
|
|
216
|
+
}[];
|
|
217
|
+
}[];
|
|
218
|
+
result: Pick<PackageSubmissionResult, "packageId" | "model" | "expiresAt" | "pages">;
|
|
219
|
+
store?: string;
|
|
220
|
+
warn?: (message: string) => void;
|
|
221
|
+
now?: Date;
|
|
222
|
+
}) => Promise<ConversionReceipt[]>;
|
|
127
223
|
//# sourceMappingURL=conversion-receipt.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conversion-receipt.d.ts","sourceRoot":"","sources":["../src/conversion-receipt.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"conversion-receipt.d.ts","sourceRoot":"","sources":["../src/conversion-receipt.ts"],"names":[],"mappings":"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"}
|