@purveyors/cli 0.33.2 → 0.35.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 +176 -13
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +21 -0
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +40 -13
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/context.d.ts.map +1 -1
- package/dist/commands/context.js +2 -0
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +34 -11
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/manifest.d.ts.map +1 -1
- package/dist/commands/manifest.js +2 -0
- package/dist/commands/manifest.js.map +1 -1
- package/dist/commands/market.d.ts.map +1 -1
- package/dist/commands/market.js +40 -2
- package/dist/commands/market.js.map +1 -1
- package/dist/commands/price-index.d.ts +4 -2
- package/dist/commands/price-index.d.ts.map +1 -1
- package/dist/commands/price-index.js +148 -5
- package/dist/commands/price-index.js.map +1 -1
- package/dist/commands/reference-profile.d.ts +4 -0
- package/dist/commands/reference-profile.d.ts.map +1 -0
- package/dist/commands/reference-profile.js +200 -0
- package/dist/commands/reference-profile.js.map +1 -0
- package/dist/commands/roast.d.ts.map +1 -1
- package/dist/commands/roast.js +42 -1
- package/dist/commands/roast.js.map +1 -1
- package/dist/lib/catalog.d.ts +26 -18
- package/dist/lib/catalog.d.ts.map +1 -1
- package/dist/lib/catalog.js +38 -46
- package/dist/lib/catalog.js.map +1 -1
- package/dist/lib/cherry.d.ts +2 -16
- package/dist/lib/cherry.d.ts.map +1 -1
- package/dist/lib/cherry.js +19 -5
- package/dist/lib/cherry.js.map +1 -1
- package/dist/lib/interactive/watch.d.ts +2 -3
- package/dist/lib/interactive/watch.d.ts.map +1 -1
- package/dist/lib/interactive/watch.js +4 -26
- package/dist/lib/interactive/watch.js.map +1 -1
- package/dist/lib/inventory.d.ts +8 -1
- package/dist/lib/inventory.d.ts.map +1 -1
- package/dist/lib/inventory.js +19 -5
- package/dist/lib/inventory.js.map +1 -1
- package/dist/lib/manifest.d.ts +13 -0
- package/dist/lib/manifest.d.ts.map +1 -1
- package/dist/lib/manifest.js +498 -18
- package/dist/lib/manifest.js.map +1 -1
- package/dist/lib/market.d.ts.map +1 -1
- package/dist/lib/numeric-contracts.d.ts +8 -0
- package/dist/lib/numeric-contracts.d.ts.map +1 -1
- package/dist/lib/numeric-contracts.js +2 -0
- package/dist/lib/numeric-contracts.js.map +1 -1
- package/dist/lib/parchment.d.ts +10 -1
- package/dist/lib/parchment.d.ts.map +1 -1
- package/dist/lib/parchment.js +11 -1
- package/dist/lib/parchment.js.map +1 -1
- package/dist/lib/reference-profiles.d.ts +182 -0
- package/dist/lib/reference-profiles.d.ts.map +1 -0
- package/dist/lib/reference-profiles.js +236 -0
- package/dist/lib/reference-profiles.js.map +1 -0
- package/dist/lib/roast.d.ts +36 -2
- package/dist/lib/roast.d.ts.map +1 -1
- package/dist/lib/roast.js +23 -0
- package/dist/lib/roast.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +23 -0
- package/dist/program.js.map +1 -1
- package/package.json +2 -2
- package/dist/lib/artisan/parser.d.ts +0 -19
- package/dist/lib/artisan/parser.d.ts.map +0 -1
- package/dist/lib/artisan/parser.js +0 -376
- package/dist/lib/artisan/parser.js.map +0 -1
- package/dist/lib/artisan/temperature.d.ts +0 -52
- package/dist/lib/artisan/temperature.d.ts.map +0 -1
- package/dist/lib/artisan/temperature.js +0 -101
- package/dist/lib/artisan/temperature.js.map +0 -1
- package/dist/lib/artisan/types.d.ts +0 -195
- package/dist/lib/artisan/types.d.ts.map +0 -1
- package/dist/lib/artisan/types.js +0 -35
- package/dist/lib/artisan/types.js.map +0 -1
- package/dist/lib/artisan/validator.d.ts +0 -14
- package/dist/lib/artisan/validator.d.ts.map +0 -1
- package/dist/lib/artisan/validator.js +0 -228
- package/dist/lib/artisan/validator.js.map +0 -1
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { outputData } from '../lib/output.js';
|
|
3
3
|
import { withErrorHandling, PrvrsError } from '../lib/errors.js';
|
|
4
|
-
import { createParchmentClient, unwrapParchment } from '../lib/parchment.js';
|
|
4
|
+
import { createOptionalParchmentClient, createParchmentClient, unwrapParchment, } from '../lib/parchment.js';
|
|
5
5
|
import { CLI_NUMERIC_BOUNDS } from '../lib/numeric-contracts.js';
|
|
6
6
|
import { parseStrictPositiveCount } from '../lib/strict-number.js';
|
|
7
|
+
const COMPARISONS_WHOLESALE = ['true', 'false', 'all'];
|
|
8
|
+
const ORDERS = ['asc', 'desc'];
|
|
7
9
|
function parsePositiveInt(rawValue, flag, max) {
|
|
8
10
|
const parsed = parseStrictPositiveCount(rawValue, max);
|
|
9
11
|
if (!Number.isFinite(parsed)) {
|
|
@@ -20,13 +22,148 @@ function parseWholesale(rawValue) {
|
|
|
20
22
|
return 'false';
|
|
21
23
|
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --wholesale: "${rawValue}". Must be "true" or "false".`);
|
|
22
24
|
}
|
|
25
|
+
function parseEnum(rawValue, flag, values) {
|
|
26
|
+
const value = rawValue.trim().toLowerCase();
|
|
27
|
+
if (values.includes(value))
|
|
28
|
+
return value;
|
|
29
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid ${flag}: "${rawValue}". Must be one of: ${values.join(', ')}.`);
|
|
30
|
+
}
|
|
31
|
+
function parseIsoDate(rawValue, flag) {
|
|
32
|
+
const value = rawValue.trim();
|
|
33
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
|
34
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid ${flag}: "${rawValue}". Must be an ISO date (YYYY-MM-DD).`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function toFlag(attributeName) {
|
|
39
|
+
return `--${attributeName.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a `price-index <subcommand>` invocation's own options.
|
|
43
|
+
*
|
|
44
|
+
* The parent `price-index` command keeps its snapshot filters (`--origin`,
|
|
45
|
+
* `--from`, `--wholesale`, `--page`, `--limit`, ...), and Commander assigns a
|
|
46
|
+
* flag to the parent whenever both levels declare it. Subcommands therefore
|
|
47
|
+
* read their declared options from both levels and reject parent-only filters
|
|
48
|
+
* instead of silently ignoring them.
|
|
49
|
+
*/
|
|
50
|
+
function resolveSubcommandOptions(cmd) {
|
|
51
|
+
const own = new Set(cmd.options.map((option) => option.attributeName()));
|
|
52
|
+
const parentOptions = cmd.parent?.opts() ?? {};
|
|
53
|
+
for (const key of Object.keys(parentOptions)) {
|
|
54
|
+
if (!own.has(key)) {
|
|
55
|
+
throw new PrvrsError('INVALID_ARGUMENT', `${toFlag(key)} is not supported by \`purvey price-index ${cmd.name()}\`.`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { ...parentOptions, ...cmd.opts() };
|
|
59
|
+
}
|
|
60
|
+
function requireOption(opts, key, cmd) {
|
|
61
|
+
const value = opts[key];
|
|
62
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
63
|
+
throw new PrvrsError('INVALID_ARGUMENT', `\`purvey price-index ${cmd.name()}\` requires ${toFlag(key)}.`);
|
|
64
|
+
}
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
function buildComparisonsCommand() {
|
|
68
|
+
return new Command('comparisons')
|
|
69
|
+
.description('Available exact 30-day matched price comparisons with significance')
|
|
70
|
+
.option('--wholesale <true|false|all>', 'Market scope (API default when omitted)')
|
|
71
|
+
.addHelpText('after', `
|
|
72
|
+
Examples:
|
|
73
|
+
purvey price-index comparisons --pretty
|
|
74
|
+
purvey price-index comparisons --wholesale all --json
|
|
75
|
+
|
|
76
|
+
Notes:
|
|
77
|
+
The API discovers origins with an exact 30-day matched comparison ending on the
|
|
78
|
+
latest completed observation day; an empty list means none qualify.
|
|
79
|
+
Each comparison carries its significance read verbatim. classification is null
|
|
80
|
+
until eight baseline windows exist, which means "not enough history", not "quiet".
|
|
81
|
+
Requires Parchment Intelligence access, enforced by the API.`)
|
|
82
|
+
.action(withErrorHandling(async (_opts, cmd) => {
|
|
83
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
84
|
+
const opts = resolveSubcommandOptions(cmd);
|
|
85
|
+
const query = {};
|
|
86
|
+
if (opts.wholesale !== undefined)
|
|
87
|
+
query.wholesale = parseEnum(opts.wholesale, '--wholesale', COMPARISONS_WHOLESALE);
|
|
88
|
+
const client = await createParchmentClient('member');
|
|
89
|
+
const data = unwrapParchment(await client.priceIndex.comparisons(query), 'price-index comparisons');
|
|
90
|
+
outputData(data, globalOpts);
|
|
91
|
+
}));
|
|
92
|
+
}
|
|
93
|
+
function buildComparisonCommand() {
|
|
94
|
+
return new Command('comparison')
|
|
95
|
+
.description('Matched-listing price comparison for one origin between two exact dates')
|
|
96
|
+
.option('--origin <origin>', 'Origin to compare (required)')
|
|
97
|
+
.option('--from <date>', 'Starting UTC date, YYYY-MM-DD (required)')
|
|
98
|
+
.option('--to <date>', 'Ending UTC date, YYYY-MM-DD, within 365 days of --from (required)')
|
|
99
|
+
.option('--wholesale <true|false>', 'Market scope (API default when omitted)')
|
|
100
|
+
.addHelpText('after', `
|
|
101
|
+
Examples:
|
|
102
|
+
purvey price-index comparison --origin "Ethiopia" --from 2026-08-31 --to 2026-09-30 --pretty
|
|
103
|
+
purvey price-index comparison --origin "Colombia" --from 2026-06-01 --to 2026-09-01 --wholesale true --json
|
|
104
|
+
|
|
105
|
+
Notes:
|
|
106
|
+
Supplier-balanced fresh matched prices only; status "insufficient_fresh_coverage"
|
|
107
|
+
returns a null changePercent, never zero.
|
|
108
|
+
Requires Parchment Intelligence access, enforced by the API.`)
|
|
109
|
+
.action(withErrorHandling(async (_opts, cmd) => {
|
|
110
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
111
|
+
const opts = resolveSubcommandOptions(cmd);
|
|
112
|
+
const query = {
|
|
113
|
+
origin: requireOption(opts, 'origin', cmd),
|
|
114
|
+
from: parseIsoDate(requireOption(opts, 'from', cmd), '--from'),
|
|
115
|
+
to: parseIsoDate(requireOption(opts, 'to', cmd), '--to'),
|
|
116
|
+
};
|
|
117
|
+
if (opts.wholesale !== undefined)
|
|
118
|
+
query.wholesale = parseWholesale(opts.wholesale);
|
|
119
|
+
const client = await createParchmentClient('member');
|
|
120
|
+
const data = unwrapParchment(await client.priceIndex.comparison(query), 'price-index comparison');
|
|
121
|
+
outputData(data, globalOpts);
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
function buildHistoryCommand() {
|
|
125
|
+
const { priceIndexHistoryLimit, priceIndexHistoryWindowDays } = CLI_NUMERIC_BOUNDS;
|
|
126
|
+
return new Command('history')
|
|
127
|
+
.description('Tier-one price-index chart history (public up to 90 days)')
|
|
128
|
+
.option('--window-days <n>', `Trailing window in days (${priceIndexHistoryWindowDays.minimum}-${priceIndexHistoryWindowDays.maximum})`)
|
|
129
|
+
.option('--page <n>', '1-based page number')
|
|
130
|
+
.option('--limit <n>', `Results per page (${priceIndexHistoryLimit.minimum}-${priceIndexHistoryLimit.maximum})`)
|
|
131
|
+
.option('--order <asc|desc>', 'Snapshot ordering (API default asc)')
|
|
132
|
+
.addHelpText('after', `
|
|
133
|
+
Examples:
|
|
134
|
+
purvey price-index history --pretty
|
|
135
|
+
purvey price-index history --window-days 30 --order desc --json
|
|
136
|
+
purvey price-index history --window-days 365 --limit 500 --json
|
|
137
|
+
|
|
138
|
+
Notes:
|
|
139
|
+
Windows up to 90 days are public and run without credentials; 91-365 days
|
|
140
|
+
require Parchment Intelligence access, enforced by the API.`)
|
|
141
|
+
.action(withErrorHandling(async (_opts, cmd) => {
|
|
142
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
143
|
+
const opts = resolveSubcommandOptions(cmd);
|
|
144
|
+
const query = {};
|
|
145
|
+
if (opts.windowDays !== undefined)
|
|
146
|
+
query.windowDays = parsePositiveInt(opts.windowDays, '--window-days', priceIndexHistoryWindowDays.maximum);
|
|
147
|
+
if (opts.page !== undefined)
|
|
148
|
+
query.page = parsePositiveInt(opts.page, '--page');
|
|
149
|
+
if (opts.limit !== undefined)
|
|
150
|
+
query.limit = parsePositiveInt(opts.limit, '--limit', priceIndexHistoryLimit.maximum);
|
|
151
|
+
if (opts.order !== undefined)
|
|
152
|
+
query.order = parseEnum(opts.order, '--order', ORDERS);
|
|
153
|
+
const client = await createOptionalParchmentClient();
|
|
154
|
+
const data = unwrapParchment(await client.priceIndex.history(query), 'price-index history');
|
|
155
|
+
outputData(data, globalOpts);
|
|
156
|
+
}));
|
|
157
|
+
}
|
|
23
158
|
/**
|
|
24
|
-
* `purvey price-index` — Parchment Price Index
|
|
25
|
-
*
|
|
159
|
+
* `purvey price-index` — Parchment Price Index reads.
|
|
160
|
+
* The bare command returns aggregate snapshots (price-index entitlement);
|
|
161
|
+
* subcommands expose matched comparisons (Parchment Intelligence) and chart
|
|
162
|
+
* history (public up to 90 days). Entitlements are enforced by the API.
|
|
26
163
|
*/
|
|
27
164
|
export function buildPriceIndexCommand() {
|
|
28
165
|
const priceIndex = new Command('price-index')
|
|
29
|
-
.description('
|
|
166
|
+
.description('Parchment Price Index snapshots, matched comparisons, and chart history')
|
|
30
167
|
.option('--origin <origin>', 'Filter by origin')
|
|
31
168
|
.option('--process <method>', 'Filter by process method')
|
|
32
169
|
.option('--grade <grade>', 'Filter by grade')
|
|
@@ -41,9 +178,12 @@ Examples:
|
|
|
41
178
|
purvey price-index --origin "Ethiopia" --json
|
|
42
179
|
purvey price-index --from 2026-01-01 --to 2026-06-30 --pretty
|
|
43
180
|
purvey price-index --wholesale true --limit 25 --json
|
|
181
|
+
purvey price-index comparisons --pretty
|
|
182
|
+
purvey price-index history --window-days 30 --json
|
|
44
183
|
|
|
45
184
|
Notes:
|
|
46
|
-
|
|
185
|
+
The bare command returns aggregate snapshots and requires a Purveyors session or
|
|
186
|
+
API key with price-index (PPI) access.
|
|
47
187
|
Set PARCHMENT_API_KEY or PURVEYORS_API_KEY to use an API key instead of the logged-in session.`)
|
|
48
188
|
.action(withErrorHandling(async (opts, cmd) => {
|
|
49
189
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -69,6 +209,9 @@ Notes:
|
|
|
69
209
|
const data = unwrapParchment(result, 'price-index');
|
|
70
210
|
outputData(data, globalOpts);
|
|
71
211
|
}));
|
|
212
|
+
priceIndex.addCommand(buildComparisonsCommand());
|
|
213
|
+
priceIndex.addCommand(buildComparisonCommand());
|
|
214
|
+
priceIndex.addCommand(buildHistoryCommand());
|
|
72
215
|
return priceIndex;
|
|
73
216
|
}
|
|
74
217
|
//# sourceMappingURL=price-index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-index.js","sourceRoot":"","sources":["../../src/commands/price-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,
|
|
1
|
+
{"version":3,"file":"price-index.js","sourceRoot":"","sources":["../../src/commands/price-index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-profile.d.ts","sourceRoot":"","sources":["../../src/commands/reference-profile.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,sFAAsF;AACtF,wBAAgB,4BAA4B,IAAI,OAAO,CAsTtD"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { PrvrsError, withErrorHandling } from '../lib/errors.js';
|
|
5
|
+
import { compareProfiles, exportGeneratedReferenceProfile, getReferenceProfile, getReferenceProfileChart, importReferenceProfile, listReferenceProfiles, parseProfileComparisonSelector, parseReferenceProfileImportRequest, previewReferenceProfile, PROFILE_COMPARISON_TARGET_POINTS, profileComparisonUnits, readReferenceProfileGenerationRequest, REFERENCE_PROFILE_SOURCE_MAX_BYTES, saveGeneratedReferenceProfile, writeGeneratedReferenceFile, } from '../lib/reference-profiles.js';
|
|
6
|
+
import { normalizePathInput } from '../lib/path-input.js';
|
|
7
|
+
import { outputData } from '../lib/output.js';
|
|
8
|
+
import { parseStrictPositiveCount } from '../lib/strict-number.js';
|
|
9
|
+
function idempotencyKeyOption(command, description) {
|
|
10
|
+
return command.option('--idempotency-key <key>', `${description}; a new UUID is generated when omitted`);
|
|
11
|
+
}
|
|
12
|
+
/** `purvey reference-profile` — owner-scoped Studio plans through Parchment's SDK. */
|
|
13
|
+
export function buildReferenceProfileCommand() {
|
|
14
|
+
const referenceProfile = new Command('reference-profile').description('Compare, preview, save, and export Studio Artisan reference plans through Parchment');
|
|
15
|
+
referenceProfile
|
|
16
|
+
.command('list')
|
|
17
|
+
.description('List your Studio reference profiles')
|
|
18
|
+
.option('--include-archived', 'Include archived reference profiles')
|
|
19
|
+
.addHelpText('after', `
|
|
20
|
+
Examples:
|
|
21
|
+
purvey reference-profile list --pretty
|
|
22
|
+
purvey reference-profile list --include-archived --json
|
|
23
|
+
|
|
24
|
+
Notes:
|
|
25
|
+
Requires a member credential and Studio access; entitlement is enforced by Parchment.`)
|
|
26
|
+
.action(withErrorHandling(async (opts, cmd) => {
|
|
27
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
28
|
+
const data = await listReferenceProfiles(Boolean(opts.includeArchived));
|
|
29
|
+
outputData(data, globalOpts);
|
|
30
|
+
}));
|
|
31
|
+
referenceProfile
|
|
32
|
+
.command('get <profile-id>')
|
|
33
|
+
.description('Get one reference profile and its current revision')
|
|
34
|
+
.addHelpText('after', `
|
|
35
|
+
Example:
|
|
36
|
+
purvey reference-profile get 5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty
|
|
37
|
+
|
|
38
|
+
Use data.currentRevision.id as the revision id for chart, preview, and save.`)
|
|
39
|
+
.action(withErrorHandling(async (profileId, _opts, cmd) => {
|
|
40
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
41
|
+
const data = await getReferenceProfile(profileId);
|
|
42
|
+
outputData(data, globalOpts);
|
|
43
|
+
}));
|
|
44
|
+
referenceProfile
|
|
45
|
+
.command('chart <profile-id> <revision-id>')
|
|
46
|
+
.description('Get the typed Artisan chart for a reference revision')
|
|
47
|
+
.addHelpText('after', `
|
|
48
|
+
Example:
|
|
49
|
+
purvey reference-profile chart 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --pretty`)
|
|
50
|
+
.action(withErrorHandling(async (profileId, revisionId, _opts, cmd) => {
|
|
51
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
52
|
+
const data = await getReferenceProfileChart(profileId, revisionId);
|
|
53
|
+
outputData(data, globalOpts);
|
|
54
|
+
}));
|
|
55
|
+
referenceProfile
|
|
56
|
+
.command('compare <left> <right>')
|
|
57
|
+
.description('Compare two immutable roast or reference revisions with measured deltas')
|
|
58
|
+
.option('--unit <F|C>', 'Temperature unit for the comparison series', 'F')
|
|
59
|
+
.option('--target-points <n>', `Samples per aligned series (${PROFILE_COMPARISON_TARGET_POINTS.minimum}-${PROFILE_COMPARISON_TARGET_POINTS.maximum})`, String(PROFILE_COMPARISON_TARGET_POINTS.default))
|
|
60
|
+
.addHelpText('after', `
|
|
61
|
+
Examples:
|
|
62
|
+
purvey reference-profile compare roast:123 profile:5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty
|
|
63
|
+
purvey reference-profile compare roast:123 roast:124 --unit C --json
|
|
64
|
+
purvey reference-profile compare revision:8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 roast:123@2026-09-30T14:22:05.123456+00:00 --pretty
|
|
65
|
+
|
|
66
|
+
Selectors:
|
|
67
|
+
revision:<uuid> exact reference-profile revision
|
|
68
|
+
profile:<uuid> the reference profile's current revision
|
|
69
|
+
roast:<roast-id> the executed roast's current chart revision
|
|
70
|
+
roast:<roast-id>@<rev> an exact executed-roast chart revision (data.metadata.revision from 'purvey roast chart')
|
|
71
|
+
|
|
72
|
+
Notes:
|
|
73
|
+
Parchment aligns both sides at charge and returns measured deltas and milestones unchanged.
|
|
74
|
+
A reference profile is a plan or comparison reference, never executed roast history.
|
|
75
|
+
Requires a member credential and Studio access; entitlement is enforced by Parchment.`)
|
|
76
|
+
.action(withErrorHandling(async (left, right, opts, cmd) => {
|
|
77
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
78
|
+
const unit = String(opts.unit).trim().toUpperCase();
|
|
79
|
+
if (!profileComparisonUnits.includes(unit)) {
|
|
80
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --unit: "${String(opts.unit)}". Must be one of: ${profileComparisonUnits.join(', ')}.`);
|
|
81
|
+
}
|
|
82
|
+
const targetPoints = parseStrictPositiveCount(String(opts.targetPoints));
|
|
83
|
+
if (!Number.isFinite(targetPoints) ||
|
|
84
|
+
targetPoints < PROFILE_COMPARISON_TARGET_POINTS.minimum ||
|
|
85
|
+
targetPoints > PROFILE_COMPARISON_TARGET_POINTS.maximum) {
|
|
86
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --target-points: "${String(opts.targetPoints)}". Must be an integer between ${PROFILE_COMPARISON_TARGET_POINTS.minimum} and ${PROFILE_COMPARISON_TARGET_POINTS.maximum}.`);
|
|
87
|
+
}
|
|
88
|
+
const data = await compareProfiles({
|
|
89
|
+
left: parseProfileComparisonSelector(left, 'left'),
|
|
90
|
+
right: parseProfileComparisonSelector(right, 'right'),
|
|
91
|
+
targetUnit: unit,
|
|
92
|
+
targetPoints,
|
|
93
|
+
});
|
|
94
|
+
outputData(data, globalOpts);
|
|
95
|
+
}));
|
|
96
|
+
const importCommand = referenceProfile
|
|
97
|
+
.command('import <file>')
|
|
98
|
+
.description('Upload an Artisan reference file as a private Studio profile')
|
|
99
|
+
.option('--title <text>', 'Title for the reference profile')
|
|
100
|
+
.option('--notes <text>', 'Notes about the reference profile')
|
|
101
|
+
.addHelpText('after', `
|
|
102
|
+
Examples:
|
|
103
|
+
purvey reference-profile import ~/artisan/ethiopia.alog --title "Ethiopia baseline" --pretty
|
|
104
|
+
purvey reference-profile import ~/artisan/ethiopia.alog --idempotency-key 3f6c2a1b-8e4d-4b7a-9c05-7e1f2d3a4b5c --json
|
|
105
|
+
|
|
106
|
+
Notes:
|
|
107
|
+
Parchment parses and retains the private source (up to 10,000,000 bytes); this command does not create roast history.
|
|
108
|
+
Reuse an explicit idempotency key to safely retry the same upload.`);
|
|
109
|
+
idempotencyKeyOption(importCommand, 'Stable key for safe retries');
|
|
110
|
+
importCommand.action(withErrorHandling(async (inputPath, opts, cmd) => {
|
|
111
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
112
|
+
const filePath = normalizePathInput(inputPath);
|
|
113
|
+
if (!filePath) {
|
|
114
|
+
throw new PrvrsError('INVALID_ARGUMENT', 'The Artisan reference file path cannot be blank.');
|
|
115
|
+
}
|
|
116
|
+
const fileInfo = await stat(filePath);
|
|
117
|
+
if (!fileInfo.isFile()) {
|
|
118
|
+
throw new PrvrsError('INVALID_ARGUMENT', 'The Artisan reference path must be a file.');
|
|
119
|
+
}
|
|
120
|
+
if (fileInfo.size === 0 || fileInfo.size > REFERENCE_PROFILE_SOURCE_MAX_BYTES) {
|
|
121
|
+
throw new PrvrsError('INVALID_ARGUMENT', `The Artisan reference must be between 1 and ${REFERENCE_PROFILE_SOURCE_MAX_BYTES} bytes.`);
|
|
122
|
+
}
|
|
123
|
+
const fileContent = await readFile(filePath, 'utf8');
|
|
124
|
+
const body = parseReferenceProfileImportRequest({
|
|
125
|
+
...(opts.title !== undefined ? { title: String(opts.title) } : {}),
|
|
126
|
+
...(opts.notes !== undefined ? { notes: String(opts.notes) } : {}),
|
|
127
|
+
fileName: basename(filePath),
|
|
128
|
+
fileContent,
|
|
129
|
+
fileSize: Buffer.byteLength(fileContent, 'utf8'),
|
|
130
|
+
});
|
|
131
|
+
const data = await importReferenceProfile(body, opts.idempotencyKey ? String(opts.idempotencyKey) : undefined);
|
|
132
|
+
outputData(data, globalOpts);
|
|
133
|
+
}));
|
|
134
|
+
const previewCommand = referenceProfile
|
|
135
|
+
.command('preview <profile-id> <revision-id>')
|
|
136
|
+
.description('Preview a bounded set of temperature adjustments without saving')
|
|
137
|
+
.requiredOption('--request <file>', 'JSON file with a title and bounded temperature adjustments')
|
|
138
|
+
.addHelpText('after', `
|
|
139
|
+
Example:
|
|
140
|
+
purvey reference-profile preview 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty
|
|
141
|
+
|
|
142
|
+
The request file is also the exact body accepted by save: title, optional notes, optional
|
|
143
|
+
userGoal/modelRecommendation/userEdits provenance, and changes.temperatureAdjustments.
|
|
144
|
+
Parchment recalculates from the immutable parent; preview never changes or stores the source.`);
|
|
145
|
+
previewCommand.action(withErrorHandling(async (profileId, revisionId, opts, cmd) => {
|
|
146
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
147
|
+
const request = await readReferenceProfileGenerationRequest(String(opts.request));
|
|
148
|
+
const data = await previewReferenceProfile(profileId, revisionId, request);
|
|
149
|
+
outputData(data, globalOpts);
|
|
150
|
+
}));
|
|
151
|
+
const saveCommand = referenceProfile
|
|
152
|
+
.command('save <profile-id> <revision-id>')
|
|
153
|
+
.description('Save the previewed changes as a new immutable generated reference')
|
|
154
|
+
.requiredOption('--request <file>', 'JSON file with a title and bounded temperature adjustments');
|
|
155
|
+
idempotencyKeyOption(saveCommand, 'Stable key for safe retries');
|
|
156
|
+
saveCommand.addHelpText('after', `
|
|
157
|
+
Example:
|
|
158
|
+
purvey reference-profile save 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty
|
|
159
|
+
|
|
160
|
+
Notes:
|
|
161
|
+
Omit --idempotency-key to generate a new key for this call. Reuse an explicit key to replay a retry.
|
|
162
|
+
A generated reference is a plan, not executed roast history.`);
|
|
163
|
+
saveCommand.action(withErrorHandling(async (profileId, revisionId, opts, cmd) => {
|
|
164
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
165
|
+
const request = await readReferenceProfileGenerationRequest(String(opts.request));
|
|
166
|
+
const data = await saveGeneratedReferenceProfile(profileId, revisionId, request, opts.idempotencyKey ? String(opts.idempotencyKey) : undefined);
|
|
167
|
+
outputData(data, globalOpts);
|
|
168
|
+
}));
|
|
169
|
+
const exportCommand = referenceProfile
|
|
170
|
+
.command('export <profile-id> <revision-id>')
|
|
171
|
+
.description('Download a saved generated reference as an unsigned Artisan .alog plan')
|
|
172
|
+
.requiredOption('--output <file>', 'Destination for the generated .alog file')
|
|
173
|
+
.option('--force', 'Overwrite the destination file if it already exists')
|
|
174
|
+
.addHelpText('after', `
|
|
175
|
+
Example:
|
|
176
|
+
purvey reference-profile export 0b7e9f52-3c61-4d8a-9e24-6f1a8c3d5b90 c43a1d7e-95b2-4e06-8f7c-1d2b3a4e5f68 --output ~/artisan/next-batch.alog
|
|
177
|
+
|
|
178
|
+
Only a saved generated revision can be exported. Export writes the .alog file locally and
|
|
179
|
+
prints a JSON receipt; it does not claim verified Artisan 4.2 playback compatibility.`);
|
|
180
|
+
exportCommand.action(withErrorHandling(async (profileId, revisionId, opts, cmd) => {
|
|
181
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
182
|
+
const destination = normalizePathInput(String(opts.output ?? ''));
|
|
183
|
+
if (!destination) {
|
|
184
|
+
throw new PrvrsError('INVALID_ARGUMENT', 'The export destination path cannot be blank.');
|
|
185
|
+
}
|
|
186
|
+
const result = await exportGeneratedReferenceProfile(profileId, revisionId);
|
|
187
|
+
const outputFile = await writeGeneratedReferenceFile(result.data.fileContent, destination, Boolean(opts.force));
|
|
188
|
+
outputData({
|
|
189
|
+
data: {
|
|
190
|
+
profileId,
|
|
191
|
+
revisionId,
|
|
192
|
+
fileName: result.data.fileName,
|
|
193
|
+
fileSize: result.data.fileSize,
|
|
194
|
+
outputPath: outputFile,
|
|
195
|
+
},
|
|
196
|
+
}, globalOpts);
|
|
197
|
+
}));
|
|
198
|
+
return referenceProfile;
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=reference-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reference-profile.js","sourceRoot":"","sources":["../../src/commands/reference-profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"roast.d.ts","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,
|
|
1
|
+
{"version":3,"file":"roast.d.ts","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,OAAO,EAIL,WAAW,EAIZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAO/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAUjD,4DAA4D;AAC5D,KAAK,kBAAkB,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,qBAAqB,CAAC,CAAC;AAGvE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAkEhE;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,kBAAkB,EAC3B,gBAAgB,EAAE,MAAM,EACxB,iBAAiB,EAAE,MAAM,GACxB,iBAAiB,CAyDnB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,iBAAiB,EAAE,iBAAiB,GAAG,kBAAkB,CAwBjG;AAED,6FAA6F;AAC7F,wBAAsB,sBAAsB,CAC1C,iBAAiB,EAAE,iBAAiB,EACpC,KAAK,EAAE,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,GACvC,OAAO,CAAC,YAAY,CAAC,CAQvB;AAcD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CA+7B3C"}
|
package/dist/commands/roast.js
CHANGED
|
@@ -6,7 +6,7 @@ import { outputData, info, success } from '../lib/output.js';
|
|
|
6
6
|
import { withErrorHandling, PrvrsError, AuthError } from '../lib/errors.js';
|
|
7
7
|
import { requireAuth } from '../lib/auth-guard.js';
|
|
8
8
|
import { confirm, todayIso } from '../lib/prompts.js';
|
|
9
|
-
import { listRoasts, getRoast, createRoast, deleteRoast, updateRoast } from '../lib/roast.js';
|
|
9
|
+
import { listRoasts, getRoast, getRoastChartData, createRoast, deleteRoast, updateRoast, ROAST_CHART_TARGET_POINTS, } from '../lib/roast.js';
|
|
10
10
|
import { pickBean, guardCancel } from '../lib/interactive/forms.js';
|
|
11
11
|
import { normalizePathInput } from '../lib/path-input.js';
|
|
12
12
|
import { startWatch, loadWatchSession } from '../lib/interactive/watch.js';
|
|
@@ -29,6 +29,14 @@ function parseRoastListCount(value, flag) {
|
|
|
29
29
|
}
|
|
30
30
|
return parsed;
|
|
31
31
|
}
|
|
32
|
+
function parseRoastChartTargetPoints(value) {
|
|
33
|
+
const parsed = parseStrictPositiveCount(value);
|
|
34
|
+
const { minimum, maximum } = ROAST_CHART_TARGET_POINTS;
|
|
35
|
+
if (!Number.isFinite(parsed) || parsed < minimum || parsed > maximum) {
|
|
36
|
+
throw new PrvrsError('INVALID_ARGUMENT', `Invalid --target-points: "${value}". Must be an integer between ${minimum} and ${maximum}.`);
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
}
|
|
32
40
|
function parseWatchCommitMode(value, fallback = 'batch') {
|
|
33
41
|
if (value === undefined || value === null || String(value).trim() === '') {
|
|
34
42
|
return fallback;
|
|
@@ -273,6 +281,34 @@ Notes:
|
|
|
273
281
|
});
|
|
274
282
|
outputData(data, globalOpts);
|
|
275
283
|
}));
|
|
284
|
+
// ── roast chart <id> ──────────────────────────────────────────────────────
|
|
285
|
+
roast
|
|
286
|
+
.command('chart <id>')
|
|
287
|
+
.description('Fetch the sampled chart model for one roast (series, events, revision)')
|
|
288
|
+
.option('--target-points <n>', `Approximate samples per series (${ROAST_CHART_TARGET_POINTS.minimum}-${ROAST_CHART_TARGET_POINTS.maximum}; Parchment defaults to 400)`)
|
|
289
|
+
.addHelpText('after', `
|
|
290
|
+
Examples:
|
|
291
|
+
purvey roast chart 123 --pretty
|
|
292
|
+
purvey roast chart 123 --target-points 120 --json
|
|
293
|
+
purvey roast chart 123 | jq '.data.metadata.revision'
|
|
294
|
+
|
|
295
|
+
Notes:
|
|
296
|
+
<id> is roast_data.roast_id (integer).
|
|
297
|
+
Returns Parchment's canonical chart-data envelope unchanged: sampled series,
|
|
298
|
+
discrete events, and metadata. data.metadata.revision identifies the immutable
|
|
299
|
+
chart revision accepted by 'purvey reference-profile compare roast:<id>@<revision>'.
|
|
300
|
+
Requires authentication (member role); API keys need the roast:read scope.
|
|
301
|
+
`)
|
|
302
|
+
.action(withErrorHandling(async (id, opts, cmd) => {
|
|
303
|
+
const globalOpts = cmd.optsWithGlobals();
|
|
304
|
+
const data = await getRoastChartData({
|
|
305
|
+
id: parseRoastInt4Id(id, 'roast ID'),
|
|
306
|
+
targetPoints: opts.targetPoints === undefined
|
|
307
|
+
? undefined
|
|
308
|
+
: parseRoastChartTargetPoints(String(opts.targetPoints)),
|
|
309
|
+
});
|
|
310
|
+
outputData(data, globalOpts);
|
|
311
|
+
}));
|
|
276
312
|
// ── roast create ──────────────────────────────────────────────────────────
|
|
277
313
|
roast
|
|
278
314
|
.command('create')
|
|
@@ -283,6 +319,8 @@ Notes:
|
|
|
283
319
|
.option('--oz-out <oz>', 'Roasted weight in ounces')
|
|
284
320
|
.option('--roast-date <YYYY-MM-DD>', 'Roast date (defaults to today)')
|
|
285
321
|
.option('--notes <text>', 'Roast notes')
|
|
322
|
+
.option('--targets <text>', 'Roast targets (planning goals for this roast)')
|
|
323
|
+
.option('--roaster-type <text>', 'Roaster model or type')
|
|
286
324
|
.option('--form', 'Interactive form mode (browse and select bean)')
|
|
287
325
|
.addHelpText('after', `
|
|
288
326
|
Examples:
|
|
@@ -290,6 +328,7 @@ Examples:
|
|
|
290
328
|
purvey roast create --coffee-id 7 --batch-name "Ethiopia Guji Light" --oz-in 16
|
|
291
329
|
purvey roast create --coffee-id 42 --oz-in 12 --oz-out 9.8 --roast-date 2026-03-15
|
|
292
330
|
purvey roast create --coffee-id 7 --notes "Extended drying phase, aimed for medium roast"
|
|
331
|
+
purvey roast create --coffee-id 7 --targets "FC at 390F, 18% development" --roaster-type "Aillio Bullet"
|
|
293
332
|
purvey roast create --form # interactive wizard
|
|
294
333
|
|
|
295
334
|
Required flags: --coffee-id (green_coffee_inv.id)
|
|
@@ -387,6 +426,8 @@ Required flags: --coffee-id (green_coffee_inv.id)
|
|
|
387
426
|
ozOut,
|
|
388
427
|
roastDate: opts.roastDate ?? todayIso(),
|
|
389
428
|
notes: opts.notes,
|
|
429
|
+
targets: opts.targets,
|
|
430
|
+
roasterType: opts.roasterType,
|
|
390
431
|
});
|
|
391
432
|
success(`Roast profile ${data.roast_id} created.`);
|
|
392
433
|
outputData(data, globalOpts);
|