@rankwrangler/cli 0.12.0 → 0.14.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/dist/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v0.14.0 - 2026-10-29
6
+
7
+ ### Changed
8
+
9
+ - Breaking: move API and MCP connections to `https://rankwrangler-api.merchbase.co`. Sign in through Cloudflare Access or create a replacement Merchbase API key; old Clerk credentials stop working at cutover.
10
+ - Chrome and Safari extensions use the same account permissions while keeping credentials out of request URLs.
11
+ - Account revocations and API-key retirement reach running connections without recurring Clerk verification calls.
12
+
13
+ ## v0.13.0 - 2026-10-28
14
+
15
+ ### Added
16
+
17
+ - Rebuild the website on HeroUI Pro: Explore and Keywords grids with filter sidebars and counts,
18
+ KPI bands, BSR sparklines, a pager, and a full Product page.
19
+ - Drive keywords from Brand Analytics searches and keyword themes for every Merch product type,
20
+ with five relevant keywords selected per product and collection run as gated jobs.
21
+ - Detect Merch products by part number and keep scans Merch-only; the Merch re-check can demote
22
+ false positives.
23
+ - Push keyword, sync and log changes live over the tRPC WebSocket.
24
+
25
+ ### Changed
26
+
27
+ - Breaking: public keyword reads replace `state`, `status` and `untrackedReason` with `stage`
28
+ (`graduated`, `trial`, `dropped`, `untracked`) and `freshness` (`live`, `needsSync`, `syncing`),
29
+ and add `searchFrequencyRank`.
30
+ - Re-check graduated keywords ahead of trials, tune scan rate and depth with daily provider
31
+ ceilings, and let live searches win Keepa capacity over background work.
32
+
33
+ ### Fixed
34
+
35
+ - Answer live catalog searches before saving Product history, and record why failed searches
36
+ failed.
37
+ - Adapt Ads pacing to 429 responses and drop Keepa's no-category sentinel rank.
38
+
5
39
  ## v0.12.0 - 2026-10-26
6
40
 
7
41
  ### Added
package/dist/cli-auth.js CHANGED
@@ -2,11 +2,10 @@ import { spawnSync } from 'node:child_process';
2
2
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import readline from 'node:readline';
5
- import { MERCHBASE_API_KEY_ENV, MERCHBASE_API_KEY_KEYCHAIN_ACCOUNT, MERCHBASE_API_KEY_KEYCHAIN_SERVICE, } from '@merchbaseco/access';
6
5
  import { CliAuthError } from './cli-auth-error.js';
7
- export const API_KEY_ENV_VAR = MERCHBASE_API_KEY_ENV;
8
- export const AUTH_SERVICE_NAME = MERCHBASE_API_KEY_KEYCHAIN_SERVICE;
9
- export const AUTH_ACCOUNT_NAME = MERCHBASE_API_KEY_KEYCHAIN_ACCOUNT;
6
+ export const API_KEY_ENV_VAR = 'MERCHBASE_API_KEY';
7
+ export const AUTH_SERVICE_NAME = 'co.merchbase.cli';
8
+ export const AUTH_ACCOUNT_NAME = 'api-key';
10
9
  const INTERNAL_SECRET_STORE_DIR_ENV_VAR = 'RW_INTERNAL_CLI_SECRET_STORE_DIR';
11
10
  export const resolveApiKey = async () => {
12
11
  const envApiKey = resolveEnvApiKey();
@@ -29,6 +29,8 @@ const runKeywordSearch = async (args, client, options, fail) => {
29
29
  const text = requireKeyword(args, fail, 'keyword search');
30
30
  return await client.keyword.search.query({
31
31
  text,
32
+ stage: keywordChoice(options.stage, KEYWORD_STAGES, 'stage', fail),
33
+ freshness: keywordChoice(options.freshness, KEYWORD_FRESHNESS, 'freshness', fail),
32
34
  cursor: options.cursor ? parseKeywordCursor(options.cursor, fail) : undefined,
33
35
  limit: parseIntegerOption({
34
36
  value: options.limit,
@@ -46,15 +48,23 @@ const requireKeyword = (args, fail, commandName) => {
46
48
  }
47
49
  return keyword;
48
50
  };
51
+ const KEYWORD_STAGES = ['graduated', 'trial', 'dropped', 'untracked', 'all'];
52
+ const KEYWORD_FRESHNESS = ['live', 'needsSync', 'syncing', 'all'];
53
+ const keywordChoice = (value, choices, name, fail) => {
54
+ if (value === undefined)
55
+ return undefined;
56
+ const match = choices.find(candidate => candidate === value);
57
+ return match ?? fail('INVALID_INPUT', `--${name} must be ${choices.join(', ')}`);
58
+ };
49
59
  const parseKeywordCursor = (value, fail) => {
50
60
  try {
51
61
  const parsed = JSON.parse(value);
52
62
  const key = parsed?.key;
53
63
  if (!Array.isArray(key) || key.length !== 7 || !key.slice(0, 6).every(part => typeof part === 'number' && Number.isFinite(part))
54
- || typeof key[6] !== 'string' || typeof parsed.asOf !== 'string' || typeof parsed.trialTurn !== 'boolean'
64
+ || typeof key[6] !== 'string' || typeof parsed.asOf !== 'string'
55
65
  || (parsed.sort !== 'work' && parsed.sort !== 'newest'))
56
66
  return fail('INVALID_INPUT', '--cursor must be the JSON nextCursor from keyword search');
57
- return { key: [key[0], key[1], key[2], key[3], key[4], key[5], key[6]], asOf: parsed.asOf, trialTurn: parsed.trialTurn, sort: parsed.sort };
67
+ return { key: [key[0], key[1], key[2], key[3], key[4], key[5], key[6]], asOf: parsed.asOf, sort: parsed.sort };
58
68
  }
59
69
  catch {
60
70
  return fail('INVALID_INPUT', '--cursor must be valid JSON');
@@ -11,6 +11,8 @@ export interface CliOptionValues {
11
11
  includeOfficialBrands?: boolean;
12
12
  merch?: string;
13
13
  listingStatus?: string;
14
+ stage?: string;
15
+ freshness?: string;
14
16
  sort?: string;
15
17
  minBsr?: string;
16
18
  maxBsr?: string;
package/dist/index.js CHANGED
@@ -51,6 +51,8 @@ const { positionals, values } = parseArgs({
51
51
  includeOfficialBrands: { type: 'boolean' },
52
52
  merch: { type: 'string' },
53
53
  listingStatus: { type: 'string' },
54
+ stage: { type: 'string' },
55
+ freshness: { type: 'string' },
54
56
  sort: { type: 'string' },
55
57
  minBsr: { type: 'string' },
56
58
  maxBsr: { type: 'string' },
package/dist/usage.js CHANGED
@@ -61,6 +61,8 @@ export const printUsage = (paths) => {
61
61
  ' --facet <facet=name> Repeat for facet filters',
62
62
  ' --merch <value> true,false,unknown,all (default: true)',
63
63
  ' --listingStatus <value> active,deleted,all (default: active)',
64
+ ' --stage <value> Keyword search: graduated,trial,dropped,untracked,all (default: graduated)',
65
+ ' --freshness <value> Keyword search: live,needsSync,syncing,all (default: all)',
64
66
  ' --includeOfficialBrands Include known official brands in Product search',
65
67
  ' --stdin Read auth secret from standard input',
66
68
  '',
@@ -85,6 +87,7 @@ export const printUsage = (paths) => {
85
87
  ' rw product history B0DV53VS61 --bucket week',
86
88
  ' rw keyword get "retro gardening shirt"',
87
89
  ' rw keyword search "retro gardening" --limit 10',
90
+ ' rw keyword search "retro gardening" --stage trial',
88
91
  ' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
89
92
  ' rw --version',
90
93
  ' rw changelog',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankwrangler/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Official RankWrangler command line interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,8 +29,7 @@
29
29
  "provenance": true
30
30
  },
31
31
  "dependencies": {
32
- "@merchbaseco/access": "0.4.0",
33
- "@rankwrangler/http-client": "^0.12.0",
32
+ "@rankwrangler/http-client": "^0.14.0",
34
33
  "keytar": "7.9.0"
35
34
  },
36
35
  "devDependencies": {