1688-cli 0.1.41 → 0.1.43

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.
Files changed (119) hide show
  1. package/AGENTS.md +112 -318
  2. package/ARCHITECTURE.md +107 -0
  3. package/CHANGELOG.md +79 -0
  4. package/README.md +186 -18
  5. package/dist/cli.js +131 -25
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/cart-list.js +2 -1
  8. package/dist/commands/cart-list.js.map +1 -1
  9. package/dist/commands/checkout-confirm.js +8 -8
  10. package/dist/commands/checkout-confirm.js.map +1 -1
  11. package/dist/commands/compare.js +107 -0
  12. package/dist/commands/compare.js.map +1 -0
  13. package/dist/commands/doctor.js +64 -47
  14. package/dist/commands/doctor.js.map +1 -1
  15. package/dist/commands/inbox.js +1 -1
  16. package/dist/commands/inbox.js.map +1 -1
  17. package/dist/commands/login.js +14 -14
  18. package/dist/commands/login.js.map +1 -1
  19. package/dist/commands/logout.js +6 -4
  20. package/dist/commands/logout.js.map +1 -1
  21. package/dist/commands/offer.js +7 -5
  22. package/dist/commands/offer.js.map +1 -1
  23. package/dist/commands/order-list.js +4 -2
  24. package/dist/commands/order-list.js.map +1 -1
  25. package/dist/commands/order-logistics.js +4 -2
  26. package/dist/commands/order-logistics.js.map +1 -1
  27. package/dist/commands/profile.js +25 -9
  28. package/dist/commands/profile.js.map +1 -1
  29. package/dist/commands/research.js +142 -0
  30. package/dist/commands/research.js.map +1 -0
  31. package/dist/commands/search.js +59 -18
  32. package/dist/commands/search.js.map +1 -1
  33. package/dist/commands/seller-chat.js +1 -1
  34. package/dist/commands/seller-chat.js.map +1 -1
  35. package/dist/commands/seller-inquire.js +1 -1
  36. package/dist/commands/seller-inquire.js.map +1 -1
  37. package/dist/commands/seller-messages.js +8 -5
  38. package/dist/commands/seller-messages.js.map +1 -1
  39. package/dist/commands/sourcing-utils.js +438 -0
  40. package/dist/commands/sourcing-utils.js.map +1 -0
  41. package/dist/commands/supplier-inspect.js +559 -0
  42. package/dist/commands/supplier-inspect.js.map +1 -0
  43. package/dist/commands/supplier-search.js +522 -0
  44. package/dist/commands/supplier-search.js.map +1 -0
  45. package/dist/commands/whoami.js +6 -3
  46. package/dist/commands/whoami.js.map +1 -1
  47. package/dist/daemon/client.js +10 -6
  48. package/dist/daemon/client.js.map +1 -1
  49. package/dist/daemon/manager.js +53 -37
  50. package/dist/daemon/manager.js.map +1 -1
  51. package/dist/daemon/protocol.js +2 -1
  52. package/dist/daemon/protocol.js.map +1 -1
  53. package/dist/daemon/server.js +26 -22
  54. package/dist/daemon/server.js.map +1 -1
  55. package/dist/session/context.js +1 -1
  56. package/dist/session/context.js.map +1 -1
  57. package/dist/session/dispatch.js +25 -22
  58. package/dist/session/dispatch.js.map +1 -1
  59. package/dist/session/im-ws.js +8 -5
  60. package/dist/session/im-ws.js.map +1 -1
  61. package/dist/session/lock.js +14 -14
  62. package/dist/session/lock.js.map +1 -1
  63. package/dist/session/paths.js +50 -16
  64. package/dist/session/paths.js.map +1 -1
  65. package/dist/session/search-mtop.js +53 -0
  66. package/dist/session/search-mtop.js.map +1 -1
  67. package/dist/session/shared.js +17 -7
  68. package/dist/session/shared.js.map +1 -1
  69. package/dist/session/state.js +7 -7
  70. package/dist/session/state.js.map +1 -1
  71. package/dist/session/supplier-search.js +403 -0
  72. package/dist/session/supplier-search.js.map +1 -0
  73. package/dist/util/encoding.js +8 -0
  74. package/dist/util/encoding.js.map +1 -0
  75. package/dist/util/temp.js +6 -0
  76. package/dist/util/temp.js.map +1 -0
  77. package/docs/AGENT_MAPS_PLAN.md +171 -0
  78. package/docs/AGENT_WORKING_PRINCIPLES.md +143 -0
  79. package/docs/COMMANDS.md +205 -0
  80. package/docs/FEATURES.md +45 -0
  81. package/docs/JSON_CONTRACTS.md +476 -0
  82. package/docs/QUALITY_SCORE.md +61 -0
  83. package/docs/README.md +36 -0
  84. package/docs/RELIABILITY.md +69 -0
  85. package/docs/SAFETY.md +99 -0
  86. package/docs/WORKFLOW.md +82 -0
  87. package/docs/exec-plans/README.md +9 -0
  88. package/docs/exec-plans/active/README.md +4 -0
  89. package/docs/exec-plans/completed/2026-05-28-sourcing-research-v1.md +125 -0
  90. package/docs/exec-plans/completed/2026-05-31-supplier-inspect-v1.md +113 -0
  91. package/docs/exec-plans/completed/2026-06-04-supplier-search-v1.md +81 -0
  92. package/docs/exec-plans/completed/2026-06-07-windows-cli-compatibility.md +138 -0
  93. package/docs/exec-plans/completed/2026-06-16-profile-daemon.md +146 -0
  94. package/docs/exec-plans/completed/README.md +4 -0
  95. package/docs/exec-plans/tech-debt-tracker.md +5 -0
  96. package/docs/generated/command-index.md +54 -0
  97. package/docs/generated/json-shapes.md +111 -0
  98. package/docs/generated/module-map.md +13 -0
  99. package/docs/generated/test-index.md +34 -0
  100. package/docs/playbooks/add-command.md +15 -0
  101. package/docs/playbooks/add-mtop-capture.md +13 -0
  102. package/docs/playbooks/change-json-output.md +11 -0
  103. package/docs/playbooks/debug-risk-control.md +12 -0
  104. package/docs/playbooks/update-cli-release.md +61 -0
  105. package/docs/records/release-omissions.md +34 -0
  106. package/docs/specs/checkout-and-orders.md +30 -0
  107. package/docs/specs/index.md +9 -0
  108. package/docs/specs/profile-daemon.md +114 -0
  109. package/docs/specs/seller-im.md +28 -0
  110. package/docs/specs/sourcing-research.md +186 -0
  111. package/docs/specs/supplier-inspect.md +144 -0
  112. package/docs/specs/supplier-search.md +179 -0
  113. package/docs/specs/windows-cli-compatibility.md +123 -0
  114. package/package.json +21 -4
  115. package/scripts/check_agent_map.mjs +87 -0
  116. package/scripts/check_release.mjs +40 -0
  117. package/scripts/fix_bin_mode.mjs +18 -0
  118. package/scripts/generate_agent_context.mjs +253 -0
  119. package/scripts/postinstall.mjs +12 -4
@@ -0,0 +1,114 @@
1
+ # Spec: Profile Daemon
2
+
3
+ > Status: draft
4
+ > Product: 1688-cli daemon/session runtime
5
+ > Scope: profile-scoped daemon processes, browser contexts, locks, runtime artifacts, and diagnostics
6
+
7
+ ## 1. Summary
8
+
9
+ `1688-cli` supports multiple local profiles, but the warm daemon runtime is
10
+ currently global/default-oriented. This spec changes daemon ownership to one
11
+ daemon per profile so commands run against the daemon for their selected
12
+ profile, different profiles can operate in parallel, and default-profile
13
+ behavior remains compatible for users who do not pass `--profile`.
14
+
15
+ ## 2. Goals
16
+
17
+ - Resolve every command profile to `default` when no profile is supplied.
18
+ - Support `1688 serve --profile <name>`.
19
+ - Support `1688 daemon start|stop|status|reload --profile <name>`.
20
+ - Route ordinary dispatched commands such as `search --profile acc-a` to the
21
+ daemon for `acc-a` when that daemon is reachable.
22
+ - Keep headed mode and explicitly daemon-disabled runs inline.
23
+ - Use one persistent browser context per daemon, bound to that daemon profile.
24
+ - Use profile-scoped lock, socket or named pipe, pid, version, log, and state
25
+ artifacts.
26
+ - Allow different profiles to run concurrently without sharing one process lock.
27
+ - Surface profile names in daemon, lock, and diagnostic error messages.
28
+ - Preserve default behavior for commands that omit `--profile`.
29
+
30
+ ## 3. Non-goals
31
+
32
+ - Do not build one daemon that multiplexes multiple profiles.
33
+ - Do not change checkout confirmation safety or route `checkout confirm`
34
+ through the daemon.
35
+ - Do not run live 1688 login, search, or browser mutation checks as automated
36
+ verification.
37
+ - Do not change command result contracts except for additive diagnostic fields
38
+ on daemon/profile/doctor surfaces.
39
+ - Do not migrate or delete historical global daemon artifacts automatically
40
+ beyond normal stale-artifact cleanup for the selected profile.
41
+
42
+ ## 4. Behavior contract
43
+
44
+ - `defaultProfileName(profile)` resolves missing, empty, or whitespace-only
45
+ profile input to `default`.
46
+ - Runtime artifact helpers accept an optional profile argument and use the
47
+ resolved profile:
48
+ - `socketPath(profile)`
49
+ - `pidFile(profile)`
50
+ - `daemonVersionFile(profile)`
51
+ - `daemonLogFile(profile)`
52
+ - `lockFile(profile)`
53
+ - `stateFile(profile)`
54
+ - Windows named pipe names include both the root hash and a profile-derived
55
+ segment so two profiles under the same `BB1688_HOME` do not collide.
56
+ - Non-Windows daemon sockets are profile-scoped filesystem paths under the
57
+ selected profile/runtime area.
58
+ - `acquireLock(profile)` locks only the selected profile.
59
+ - Inline sessions use `profilePath(profile)` and `acquireLock(profile)`.
60
+ - A daemon process is bound to exactly one profile at startup.
61
+ - `getSharedContext(profile)` creates or reuses the browser context only for
62
+ the daemon-bound profile and stores cookies/session in that profile's
63
+ persistent context directory.
64
+ - `runOnSharedCtx` serializes operations within one daemon process only.
65
+ - `dispatch(name, args, { profile })` attempts the selected profile daemon
66
+ unless headed, no-daemon, or `BB1688_NO_DAEMON=1` is set.
67
+ - If inline fallback is needed, dispatch pauses only the selected profile
68
+ daemon, not daemons for other profiles.
69
+ - `login --profile <name>` writes identity state for that profile and, unless
70
+ `--no-daemon` is set, attempts to start that profile daemon after login or
71
+ after detecting an already-logged-in profile.
72
+ - `doctor --profile <name>` checks the selected profile's directory, lock,
73
+ state, daemon, and live daemon socket status.
74
+ - `profile status <name>` reports the selected profile's profile directory,
75
+ profile-scoped lock, profile-scoped state, recent event, and daemon status.
76
+ - Error messages for daemon running, start timeout, lock busy, stale daemon, and
77
+ daemon pause identify the affected profile.
78
+
79
+ ## 5. Verification
80
+
81
+ - Focused unit coverage for profile-scoped paths, including Windows pipe names.
82
+ - Focused unit coverage for profile status using profile-scoped locks/states.
83
+ - Focused unit coverage for doctor platform helpers and profile-aware daemon
84
+ checks where deterministic.
85
+ - Typecheck with `pnpm typecheck`.
86
+ - Deterministic tests with `pnpm test:unit`.
87
+ - Regenerate generated docs with `pnpm agent-context` after command/source/test
88
+ changes.
89
+ - Run `pnpm agent-verify` as the final local gate.
90
+
91
+ ## 6. Acceptance criteria
92
+
93
+ - `1688 daemon start --profile acc-a` starts only the `acc-a` daemon.
94
+ - `1688 daemon start --profile acc-b` can coexist with `acc-a`.
95
+ - `1688 daemon status --profile acc-a` and `--profile acc-b` inspect different
96
+ artifacts and sockets.
97
+ - `1688 serve --profile acc-a` binds a daemon to `acc-a`.
98
+ - `1688 search "..." --profile acc-a` tries the `acc-a` daemon first.
99
+ - `1688 search "..." --profile acc-b` tries the `acc-b` daemon first.
100
+ - A lock held by `acc-a` does not make `acc-b` report `LOCK_BUSY`.
101
+ - A daemon pause or risk-control state in one profile is represented in that
102
+ profile daemon status and does not stop another profile daemon.
103
+ - Commands without `--profile` continue to use the `default` profile.
104
+
105
+ ## 7. Assumptions and open questions
106
+
107
+ - [ASSUMED] Profile names used by real workflows are simple names such as
108
+ `default`, `acc-a`, or `work`, without path separators.
109
+ - [ASSUMED] Moving default daemon artifacts behind profile-aware helpers is
110
+ compatible because all public commands use those helpers rather than fixed
111
+ artifact paths.
112
+ - [ASSUMED] Historical global `state.json` does not need an automated migration
113
+ for this change; a profile-specific state file is authoritative once this
114
+ version runs.
@@ -0,0 +1,28 @@
1
+ # Seller IM
2
+
3
+ Seller IM commands cover pre-sale inquiry and post-sale follow-up through
4
+ Wangwang/1688 chat.
5
+
6
+ ## Commands
7
+
8
+ ```bash
9
+ 1688 seller inquire <offerId> <message>
10
+ 1688 seller chat <orderId|loginId> <message>
11
+ 1688 seller messages --offer <offerId>
12
+ 1688 seller messages <orderId|loginId>
13
+ 1688 seller messages ... --watch
14
+ 1688 inbox
15
+ ```
16
+
17
+ ## Safety
18
+
19
+ Sending messages contacts real suppliers. Follow `docs/SAFETY.md` before
20
+ sending agent-authored text.
21
+
22
+ ## Agent Requirements
23
+
24
+ - Preserve line-delimited JSON in watch mode.
25
+ - Deduplicate messages by server-side `messageId` when available.
26
+ - Preserve `kind` and `card` fields so agents can distinguish text, offer
27
+ cards, order cards, images, and auto replies.
28
+ - Keep order/offer scoped conversations attached to the right context.
@@ -0,0 +1,186 @@
1
+ # Sourcing Research
2
+
3
+ This spec defines the first durable sourcing-research layer for `1688-cli`.
4
+ It turns search results into procurement decisions while preserving the CLI's
5
+ buyer-workflow identity: human-paced, logged-in, safe for a real account, and
6
+ agent-friendly JSON.
7
+
8
+ ## Goal
9
+
10
+ Help an agent or buyer answer:
11
+
12
+ - Which offers have demand?
13
+ - Which suppliers look trustworthy?
14
+ - Which offers are cheap enough for the target range?
15
+ - Which offers deserve detail-page enrichment?
16
+ - Which offer IDs should be compared before inquiry/cart/checkout?
17
+
18
+ ## Non-Goals
19
+
20
+ - Do not build a bulk scraping farm.
21
+ - Do not bypass login, risk control, or slider verification.
22
+ - Do not add multi-account orchestration.
23
+ - Do not claim supplier scores, repurchase rate, or service guarantees unless
24
+ the current 1688 payload exposes them reliably.
25
+ - Do not make ordinary `search` slow by fetching every detail page.
26
+
27
+ ## Commands
28
+
29
+ ### `search`
30
+
31
+ Add research-oriented read-only controls to ordinary keyword search:
32
+
33
+ ```bash
34
+ 1688 search <keyword> \
35
+ --sort relevance|best-selling|price-asc|price-desc \
36
+ --price-min 1 \
37
+ --price-max 50 \
38
+ --province 广东 \
39
+ --city 深圳 \
40
+ --verified any|factory|business|super-factory \
41
+ --min-turnover 100 \
42
+ --exclude-ads
43
+ ```
44
+
45
+ `search` remains fast. Filters and local sort apply to the collected result
46
+ set. Remote sort parameters may be added to the search URL when known, but the
47
+ command must still locally normalize output ordering for deterministic agent
48
+ behavior.
49
+
50
+ ### `research`
51
+
52
+ Add a multi-keyword research command:
53
+
54
+ ```bash
55
+ 1688 research <keyword...> \
56
+ --max-per-query 60 \
57
+ --sort best-selling \
58
+ --price-max 50 \
59
+ --verified super-factory \
60
+ --enrich top:10 \
61
+ --jsonl
62
+ ```
63
+
64
+ `research` runs keyword searches one by one, applies the same filters, scores
65
+ offers, deduplicates by `offerId`, and optionally enriches only the top N
66
+ results by calling `offer`.
67
+
68
+ Supported export modes:
69
+
70
+ - default: human table
71
+ - JSON: normal automatic JSON when stdout is piped or `--json` is used
72
+ - `--jsonl`: one research item per line
73
+ - `--csv`: comma-separated table
74
+ - optional `--output <file>`: write export to a file
75
+
76
+ ### `compare`
77
+
78
+ Add a read-only offer comparison command:
79
+
80
+ ```bash
81
+ 1688 compare <offerId...>
82
+ ```
83
+
84
+ It fetches each offer detail, computes comparable fields and a sourcing score,
85
+ and shows price, MOQ, sale count, SKU count, stock, supplier, freight/package
86
+ hints, and detail fetch errors.
87
+
88
+ ### `supplier inspect`
89
+
90
+ Supplier-level inspection now lives in
91
+ [`supplier-inspect.md`](supplier-inspect.md). V1 supports offerId, offer URL,
92
+ `b2b-*` memberId, and factory-card URL. Direct loginId lookup remains out of
93
+ scope because live probing showed it can resolve to the wrong factory.
94
+
95
+ ### `supplier search` / `supplier research`
96
+
97
+ Supplier discovery from 1688's company search is specified separately in
98
+ [`supplier-search.md`](supplier-search.md). These commands must use company
99
+ search payloads and must not build supplier lists by aggregating offer-search
100
+ results.
101
+
102
+ ## Data Model
103
+
104
+ ### Search Item
105
+
106
+ Each `search` offer may include the existing fields plus additive research
107
+ signals:
108
+
109
+ ```ts
110
+ {
111
+ offerId: string,
112
+ title: string,
113
+ price: { text: string, min: number | null, max: number | null },
114
+ supplier: { name: string | null, shopUrl: string | null, years: number | null },
115
+ verified: { factory: boolean, business: boolean, superFactory: boolean },
116
+ tags: string[],
117
+ isP4P: boolean,
118
+ turnover: string | null,
119
+ demand?: {
120
+ orderCountText: string | null,
121
+ orderCount: number | null,
122
+ repurchaseRateText: string | null,
123
+ repurchaseRate: number | null,
124
+ },
125
+ serviceTags?: string[],
126
+ productBadges?: string[],
127
+ }
128
+ ```
129
+
130
+ ### Research Item
131
+
132
+ ```ts
133
+ {
134
+ sourceKeyword: string,
135
+ sourceRank: number,
136
+ globalRank: number,
137
+ offer: Offer,
138
+ demand: {
139
+ turnoverText: string | null,
140
+ orderCount: number | null,
141
+ repurchaseRate: number | null,
142
+ },
143
+ supplier: {
144
+ years: number | null,
145
+ verified: Offer["verified"],
146
+ tags: string[],
147
+ isAd: boolean,
148
+ },
149
+ score: number,
150
+ scoreBreakdown: Array<{ name: string, points: number, reason: string }>,
151
+ enriched?: OfferDetailSummary,
152
+ error?: { code: string, message: string },
153
+ }
154
+ ```
155
+
156
+ ## Sourcing Score V1
157
+
158
+ The score is explainable and bounded to 100.
159
+
160
+ - Price: up to 25 points for a valid low price.
161
+ - Demand: up to 25 points from turnover/order count.
162
+ - Supplier tenure: up to 15 points from shop years.
163
+ - Verification: up to 15 points for super factory, factory, or business
164
+ verification.
165
+ - Service tags: up to 10 points from tags/service badges.
166
+ - Organic result: up to 10 points when the offer is not P4P/ad.
167
+
168
+ The score is a ranking aid, not a truth claim.
169
+
170
+ ## Failure Semantics
171
+
172
+ For `research` and `compare`, distinguish:
173
+
174
+ - run-level failure: login expired, risk control, browser/network failure that
175
+ prevents the command from continuing.
176
+ - item-level failure: one offer detail fails during enrichment or comparison.
177
+
178
+ Item-level failures stay attached to the item and should not fail the whole
179
+ run unless every item fails.
180
+
181
+ ## Verification
182
+
183
+ - Unit tests cover sorting, filters, score calculation, export formatting, and
184
+ enrichment option parsing.
185
+ - `pnpm agent-context` refreshes generated command and JSON-shape indexes.
186
+ - `pnpm agent-verify` is the default gate.
@@ -0,0 +1,144 @@
1
+ # Supplier Inspect
2
+
3
+ This spec defines the first reliable supplier-level inspection command for
4
+ `1688-cli`. It is read-only and aimed at sourcing decisions after a buyer or
5
+ agent has found an offer or supplier `memberId`.
6
+
7
+ ## Goal
8
+
9
+ Help an agent or buyer answer:
10
+
11
+ - Who is the supplier behind this offer?
12
+ - Does the supplier expose factory/trust/service signals?
13
+ - What factory card data is available: location, years, authentication,
14
+ production scope, staff/scale hints, and available offer count?
15
+ - Which fields are observed from 1688 payloads versus unavailable?
16
+
17
+ ## Non-Goals
18
+
19
+ - Do not bypass login, risk control, or slider verification.
20
+ - Do not bulk scrape supplier catalogs.
21
+ - Do not claim loginId lookup is reliable when the current site cannot resolve
22
+ it deterministically.
23
+ - Do not perform write actions such as inquiry, favorite, cart, or checkout.
24
+ - Do not invent scores that are not backed by observed payload fields.
25
+
26
+ ## Probe Findings
27
+
28
+ Live headed probe on 2026-05-31 found these useful sources:
29
+
30
+ - Offer detail page:
31
+ - `window.context.result.global.globalData.model.sellerModel`
32
+ - `mtop.1688.moga.pc.shopcard`
33
+ - Factory card page:
34
+ - `https://sale.1688.com/factory/card.html?memberId=<memberId>`
35
+ - `mtop.com.alibaba.china.factory.card.common.fn.mtop.tpp.faas`
36
+ - Factory card DOM text can expose a visible available-offer count such as
37
+ `共34个商品`.
38
+
39
+ Direct `loginId` factory-card lookup is not reliable. A probe with
40
+ `loginId=<sellerLoginId>` returned a different factory, so V1 must reject
41
+ loginId-only input with a clear error instead of returning possibly wrong data.
42
+
43
+ ## Command
44
+
45
+ ```bash
46
+ 1688 supplier inspect <offerId|memberId|offerUrl|factoryCardUrl>
47
+ ```
48
+
49
+ Supported target forms:
50
+
51
+ - numeric `offerId`
52
+ - `https://detail.1688.com/offer/<offerId>.html`
53
+ - `b2b-*` supplier `memberId`
54
+ - factory-card URL with a `memberId` query parameter
55
+
56
+ Unsupported in V1:
57
+
58
+ - loginId-only input, because live probe showed it can misresolve
59
+
60
+ Options:
61
+
62
+ ```bash
63
+ --profile <name>
64
+ --headed
65
+ ```
66
+
67
+ ## JSON Contract
68
+
69
+ ```ts
70
+ {
71
+ target: {
72
+ input: string,
73
+ type: "offerId" | "memberId",
74
+ offerId: string | null,
75
+ memberId: string | null,
76
+ },
77
+ supplier: {
78
+ name: string | null,
79
+ loginId: string | null,
80
+ memberId: string | null,
81
+ userId: string | null,
82
+ companyId: string | null,
83
+ shopUrl: string | null,
84
+ shopUrls: Record<string, string>,
85
+ identity: string | null,
86
+ signs: Record<string, boolean>,
87
+ },
88
+ factory: {
89
+ isFactory: boolean,
90
+ superFactory: boolean,
91
+ tpYears: number | null,
92
+ medalLevel: string | null,
93
+ thirdPartyAuthProvider: string | null,
94
+ establishedAtText: string | null,
95
+ location: string | null,
96
+ address: string | null,
97
+ coordinates: { latitude: number | null, longitude: number | null },
98
+ productionService: string | null,
99
+ employeeScale: string | null,
100
+ workerCount: string | null,
101
+ profile: string | null,
102
+ tags: string[],
103
+ },
104
+ trust: {
105
+ companyLabel: string | null,
106
+ retentionRate: number | null,
107
+ companyIcons: Array<{ title: string; link: string | null }>,
108
+ shopTags: string[],
109
+ serviceScores: Array<{ key: string; label: string; score: number | null }>,
110
+ },
111
+ offers: {
112
+ availableCount: number | null,
113
+ source: "factory-card-dom" | null,
114
+ },
115
+ sources: {
116
+ offerUrl: string | null,
117
+ factoryCardUrl: string | null,
118
+ shopcardCaptured: boolean,
119
+ factoryCardCaptured: boolean,
120
+ },
121
+ warnings: string[],
122
+ }
123
+ ```
124
+
125
+ All fields are additive and nullable. Missing values mean the current page or
126
+ payload did not expose the signal.
127
+
128
+ ## Failure Semantics
129
+
130
+ - `BAD_INPUT`: target is empty, malformed, or loginId-only.
131
+ - `NOT_LOGGED_IN`: session expired.
132
+ - `RISK_CONTROL`: 1688 risk challenge appeared; retry with `--headed`.
133
+ - `NETWORK_ERROR`: navigation failed.
134
+ - `SUPPLIER_NOT_FOUND`: no supplier identity could be read from a supported
135
+ target.
136
+
137
+ ## Verification
138
+
139
+ - Unit tests cover target normalization and payload assembly helpers.
140
+ - A live smoke test should inspect a known offerId and confirm:
141
+ - supplier identity is present
142
+ - memberId is present
143
+ - factory-card capture succeeds when memberId exists
144
+ - `loginId` direct input fails with `BAD_INPUT`
@@ -0,0 +1,179 @@
1
+ # Supplier Search And Research
2
+
3
+ This spec defines supplier discovery that starts from 1688's company search,
4
+ not product-offer aggregation.
5
+
6
+ ## Goal
7
+
8
+ Help a buyer or agent answer:
9
+
10
+ - Which suppliers match this category keyword?
11
+ - Which matching suppliers expose factory/trust/service signals?
12
+ - Which suppliers deserve deeper `supplier inspect` enrichment?
13
+ - Which suppliers should be contacted or compared after product discovery?
14
+
15
+ ## Source Boundary
16
+
17
+ `supplier search` and `supplier research` must use 1688 company search. The
18
+ known durable business endpoint from live probing is:
19
+
20
+ ```text
21
+ search.1688.com/service/companySearchBusinessService
22
+ ```
23
+
24
+ The page entry URL is:
25
+
26
+ ```text
27
+ https://s.1688.com/company/company_search.htm?keywords=<GBK-percent-keyword>
28
+ ```
29
+
30
+ Important encoding rule: `s.1688.com` expects GBK percent-encoded keywords.
31
+ UTF-8 percent-encoding can search for mojibake and return zero or irrelevant
32
+ results.
33
+
34
+ Do not implement supplier discovery by running offer search and grouping
35
+ offers by supplier. That is a different signal and can hide suppliers that are
36
+ available in company search.
37
+
38
+ ## Commands
39
+
40
+ ### `supplier search`
41
+
42
+ ```bash
43
+ 1688 supplier search <keyword...> \
44
+ --max 20 \
45
+ --factory-only \
46
+ --province 广东 \
47
+ --city 深圳 \
48
+ --min-years 3 \
49
+ --min-repeat-rate 0.4 \
50
+ --min-response-rate 0.6 \
51
+ --enrich 0
52
+ ```
53
+
54
+ Default behavior is supplier discovery only. `--enrich` is optional and
55
+ defaults to `0`.
56
+
57
+ ### `supplier research`
58
+
59
+ ```bash
60
+ 1688 supplier research <keyword...> \
61
+ --max 20 \
62
+ --factory-only \
63
+ --enrich top:10 \
64
+ --jsonl
65
+ ```
66
+
67
+ `supplier research` uses the same company-search source and scoring, but
68
+ defaults to `--enrich top:10`. Enrichment calls `supplier inspect` with the
69
+ company-search `memberId` when present.
70
+
71
+ Supported export modes:
72
+
73
+ - default: human table
74
+ - JSON: automatic when stdout is piped or `--json` is used
75
+ - `--jsonl`: one supplier item per line
76
+ - `--csv`: comma-separated table
77
+ - `--output <file>`: write JSONL/CSV to a file
78
+
79
+ ## Data Model
80
+
81
+ Each item records source keyword/rank, normalized company-search supplier
82
+ signals, score, and optional inspect enrichment:
83
+
84
+ ```ts
85
+ {
86
+ sourceKeyword: string,
87
+ sourceRank: number,
88
+ globalRank: number,
89
+ supplier: {
90
+ companyName: string,
91
+ loginId: string | null,
92
+ memberId: string | null,
93
+ enterpriseId: string | null,
94
+ realUserId: string | null,
95
+ companyId: string | null,
96
+ shopUrl: string | null,
97
+ factoryCardUrl: string | null,
98
+ location: { province: string | null, city: string | null, address: string | null },
99
+ productionService: string | null,
100
+ tp: { serviceYears: number | null, memberLevel: string | null },
101
+ factory: {
102
+ isFactory: boolean,
103
+ factoryTag: string | null,
104
+ factoryLevel: string | null,
105
+ superFactory: boolean,
106
+ businessInspection: boolean,
107
+ factoryInspection: boolean,
108
+ },
109
+ service: {
110
+ compositeScore: number | null,
111
+ wwResponseRate: number | null,
112
+ repeatRate: number | null,
113
+ },
114
+ demand: {
115
+ payOrderCount3m: number | null,
116
+ payAmount3m: number | null,
117
+ fuzzyPayAmount3m: string | null,
118
+ saleQuantity3m: number | null,
119
+ },
120
+ tags: string[],
121
+ offersPreview: SupplierOfferPreview[],
122
+ },
123
+ score: number,
124
+ scoreBreakdown: Array<{ name: string, points: number, reason: string }>,
125
+ inspect?: SupplierInspectResult,
126
+ error?: { code: string, message: string },
127
+ }
128
+ ```
129
+
130
+ The top-level result includes:
131
+
132
+ ```ts
133
+ source: {
134
+ kind: "company-search",
135
+ endpoint: "companySearchBusinessService",
136
+ offerAggregation: false,
137
+ }
138
+ ```
139
+
140
+ ## Score V1
141
+
142
+ The supplier score is a ranking aid, not a truth claim.
143
+
144
+ - Company-search demand: up to 25 points from 3-month pay order count.
145
+ - Supplier tenure: up to 15 points from service years.
146
+ - Factory/trust: up to 20 points from factory/super-factory/inspection flags.
147
+ - Service rates: up to 15 points from repeat and Wangwang response rates.
148
+ - Composite score: up to 10 points.
149
+ - Offer preview depth: up to 10 points from company-search previews.
150
+
151
+ ## Failure Semantics
152
+
153
+ - Run-level failure: login expired, risk control, browser/network failure that
154
+ prevents company search from loading.
155
+ - Empty result: company search loads but returns no supplier payload.
156
+ - Item-level enrichment failure: `supplier inspect` fails for one supplier;
157
+ keep the supplier item and attach `error`.
158
+
159
+ If a command exits with risk-control code `4`, retry once with `--headed` and
160
+ solve the slider manually.
161
+
162
+ ## V1 Boundaries
163
+
164
+ Live probing on 2026-06-04 showed the company search page emits
165
+ `companySearchBusinessService` with `companyWithOfferLists`. A typical first
166
+ page async response used `startIndex=6&asyncCount=14`; this likely means some
167
+ top-page suppliers may be server-rendered before the async service response.
168
+ V1 uses the stable browser-emitted business response and keeps the largest
169
+ captured company-search payload. A later V2 can add HTML/DOM extraction for
170
+ server-rendered supplier cards if we need exact 20-per-page completeness.
171
+
172
+ ## Verification
173
+
174
+ - Unit tests cover GBK company-search URL construction.
175
+ - Unit tests cover `companySearchBusinessService` parsing and offer previews.
176
+ - Unit tests cover capture `keep: "largest"` behavior.
177
+ - Unit tests cover enrich option parsing and CSV escaping.
178
+ - `pnpm agent-context` refreshes generated command and JSON-shape indexes.
179
+ - `pnpm agent-verify` is the default gate.