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.
- package/AGENTS.md +112 -318
- package/ARCHITECTURE.md +107 -0
- package/CHANGELOG.md +79 -0
- package/README.md +186 -18
- package/dist/cli.js +131 -25
- package/dist/cli.js.map +1 -1
- package/dist/commands/cart-list.js +2 -1
- package/dist/commands/cart-list.js.map +1 -1
- package/dist/commands/checkout-confirm.js +8 -8
- package/dist/commands/checkout-confirm.js.map +1 -1
- package/dist/commands/compare.js +107 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/doctor.js +64 -47
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/inbox.js +1 -1
- package/dist/commands/inbox.js.map +1 -1
- package/dist/commands/login.js +14 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.js +6 -4
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/offer.js +7 -5
- package/dist/commands/offer.js.map +1 -1
- package/dist/commands/order-list.js +4 -2
- package/dist/commands/order-list.js.map +1 -1
- package/dist/commands/order-logistics.js +4 -2
- package/dist/commands/order-logistics.js.map +1 -1
- package/dist/commands/profile.js +25 -9
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/research.js +142 -0
- package/dist/commands/research.js.map +1 -0
- package/dist/commands/search.js +59 -18
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/seller-chat.js +1 -1
- package/dist/commands/seller-chat.js.map +1 -1
- package/dist/commands/seller-inquire.js +1 -1
- package/dist/commands/seller-inquire.js.map +1 -1
- package/dist/commands/seller-messages.js +8 -5
- package/dist/commands/seller-messages.js.map +1 -1
- package/dist/commands/sourcing-utils.js +438 -0
- package/dist/commands/sourcing-utils.js.map +1 -0
- package/dist/commands/supplier-inspect.js +559 -0
- package/dist/commands/supplier-inspect.js.map +1 -0
- package/dist/commands/supplier-search.js +522 -0
- package/dist/commands/supplier-search.js.map +1 -0
- package/dist/commands/whoami.js +6 -3
- package/dist/commands/whoami.js.map +1 -1
- package/dist/daemon/client.js +10 -6
- package/dist/daemon/client.js.map +1 -1
- package/dist/daemon/manager.js +53 -37
- package/dist/daemon/manager.js.map +1 -1
- package/dist/daemon/protocol.js +2 -1
- package/dist/daemon/protocol.js.map +1 -1
- package/dist/daemon/server.js +26 -22
- package/dist/daemon/server.js.map +1 -1
- package/dist/session/context.js +1 -1
- package/dist/session/context.js.map +1 -1
- package/dist/session/dispatch.js +25 -22
- package/dist/session/dispatch.js.map +1 -1
- package/dist/session/im-ws.js +8 -5
- package/dist/session/im-ws.js.map +1 -1
- package/dist/session/lock.js +14 -14
- package/dist/session/lock.js.map +1 -1
- package/dist/session/paths.js +50 -16
- package/dist/session/paths.js.map +1 -1
- package/dist/session/search-mtop.js +53 -0
- package/dist/session/search-mtop.js.map +1 -1
- package/dist/session/shared.js +17 -7
- package/dist/session/shared.js.map +1 -1
- package/dist/session/state.js +7 -7
- package/dist/session/state.js.map +1 -1
- package/dist/session/supplier-search.js +403 -0
- package/dist/session/supplier-search.js.map +1 -0
- package/dist/util/encoding.js +8 -0
- package/dist/util/encoding.js.map +1 -0
- package/dist/util/temp.js +6 -0
- package/dist/util/temp.js.map +1 -0
- package/docs/AGENT_MAPS_PLAN.md +171 -0
- package/docs/AGENT_WORKING_PRINCIPLES.md +143 -0
- package/docs/COMMANDS.md +205 -0
- package/docs/FEATURES.md +45 -0
- package/docs/JSON_CONTRACTS.md +476 -0
- package/docs/QUALITY_SCORE.md +61 -0
- package/docs/README.md +36 -0
- package/docs/RELIABILITY.md +69 -0
- package/docs/SAFETY.md +99 -0
- package/docs/WORKFLOW.md +82 -0
- package/docs/exec-plans/README.md +9 -0
- package/docs/exec-plans/active/README.md +4 -0
- package/docs/exec-plans/completed/2026-05-28-sourcing-research-v1.md +125 -0
- package/docs/exec-plans/completed/2026-05-31-supplier-inspect-v1.md +113 -0
- package/docs/exec-plans/completed/2026-06-04-supplier-search-v1.md +81 -0
- package/docs/exec-plans/completed/2026-06-07-windows-cli-compatibility.md +138 -0
- package/docs/exec-plans/completed/2026-06-16-profile-daemon.md +146 -0
- package/docs/exec-plans/completed/README.md +4 -0
- package/docs/exec-plans/tech-debt-tracker.md +5 -0
- package/docs/generated/command-index.md +54 -0
- package/docs/generated/json-shapes.md +111 -0
- package/docs/generated/module-map.md +13 -0
- package/docs/generated/test-index.md +34 -0
- package/docs/playbooks/add-command.md +15 -0
- package/docs/playbooks/add-mtop-capture.md +13 -0
- package/docs/playbooks/change-json-output.md +11 -0
- package/docs/playbooks/debug-risk-control.md +12 -0
- package/docs/playbooks/update-cli-release.md +61 -0
- package/docs/records/release-omissions.md +34 -0
- package/docs/specs/checkout-and-orders.md +30 -0
- package/docs/specs/index.md +9 -0
- package/docs/specs/profile-daemon.md +114 -0
- package/docs/specs/seller-im.md +28 -0
- package/docs/specs/sourcing-research.md +186 -0
- package/docs/specs/supplier-inspect.md +144 -0
- package/docs/specs/supplier-search.md +179 -0
- package/docs/specs/windows-cli-compatibility.md +123 -0
- package/package.json +21 -4
- package/scripts/check_agent_map.mjs +87 -0
- package/scripts/check_release.mjs +40 -0
- package/scripts/fix_bin_mode.mjs +18 -0
- package/scripts/generate_agent_context.mjs +253 -0
- package/scripts/postinstall.mjs +12 -4
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Windows CLI Compatibility
|
|
2
|
+
|
|
3
|
+
This spec defines the compatibility baseline required before `1688-cli` can
|
|
4
|
+
claim normal Windows command-line support.
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Make the installed CLI and local development workflow work from Windows
|
|
9
|
+
PowerShell and cmd.exe for read-only and daemon-backed commands.
|
|
10
|
+
|
|
11
|
+
## Scope
|
|
12
|
+
|
|
13
|
+
- npm package install and build scripts must not depend on Unix shell commands.
|
|
14
|
+
- The daemon IPC path must work on Windows named pipes and avoid collisions
|
|
15
|
+
between different `BB1688_HOME` roots.
|
|
16
|
+
- Runtime paths, temporary files, and diagnostics must use Node path helpers
|
|
17
|
+
instead of hard-coded Unix paths where they are part of normal command code.
|
|
18
|
+
- `doctor` fix hints must be executable or meaningful on Windows.
|
|
19
|
+
- README and command docs must show Windows alternatives where Unix examples
|
|
20
|
+
use `jq`, shell assignment, `/tmp`, or `~/.1688`.
|
|
21
|
+
- Deterministic tests must cover the Windows-specific path and hint logic.
|
|
22
|
+
- CI/package verification must include a Windows-compatible build and smoke
|
|
23
|
+
path, or document the exact manual Windows checks when CI is not available
|
|
24
|
+
in the current environment.
|
|
25
|
+
|
|
26
|
+
## Non-Goals
|
|
27
|
+
|
|
28
|
+
- Do not bypass 1688 login, slider verification, or risk control.
|
|
29
|
+
- Do not automate Windows UI interaction beyond Playwright's existing browser
|
|
30
|
+
launch behavior.
|
|
31
|
+
- Do not guarantee live 1688 network/search success in CI; CI checks should be
|
|
32
|
+
deterministic and use `doctor --no-launch` unless a real account/session is
|
|
33
|
+
explicitly supplied.
|
|
34
|
+
- Do not change public JSON contracts except by additive diagnostic fields.
|
|
35
|
+
- Do not add a new installer, native binary, or Windows service wrapper.
|
|
36
|
+
|
|
37
|
+
## Behavior Contract
|
|
38
|
+
|
|
39
|
+
### Build And Install
|
|
40
|
+
|
|
41
|
+
`pnpm build` must run on Windows, macOS, and Linux. Any executable-bit fix must
|
|
42
|
+
be implemented in Node and become a no-op on Windows.
|
|
43
|
+
|
|
44
|
+
`scripts/postinstall.mjs` must:
|
|
45
|
+
|
|
46
|
+
- locate the daemon pid file under `BB1688_HOME` when set
|
|
47
|
+
- detect Windows Chrome install paths
|
|
48
|
+
- invoke `npx.cmd` on Windows and `npx` elsewhere
|
|
49
|
+
- print retry commands that are valid for the current platform
|
|
50
|
+
|
|
51
|
+
### Daemon IPC
|
|
52
|
+
|
|
53
|
+
Unix-like platforms continue to use `<BB1688_HOME>/daemon.sock`.
|
|
54
|
+
|
|
55
|
+
Windows must use a named pipe:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
\\.\pipe\1688-cli-daemon-<stable-root-hash>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The hash must be stable for a given `BB1688_HOME`/default root and different
|
|
62
|
+
for different roots so tests, profiles, and concurrent users do not collide.
|
|
63
|
+
|
|
64
|
+
### Diagnostics
|
|
65
|
+
|
|
66
|
+
`1688 doctor` must emit platform-appropriate fix hints:
|
|
67
|
+
|
|
68
|
+
- Unix-like stale lock: `rm -rf "..."`
|
|
69
|
+
- Windows stale lock: `Remove-Item -Recurse -Force "..."`
|
|
70
|
+
- Unix-like writable-directory issue: `chmod u+w "..."`
|
|
71
|
+
- Windows writable-directory issue: explain to grant write permission or choose
|
|
72
|
+
another `BB1688_HOME`
|
|
73
|
+
|
|
74
|
+
The daemon protocol documentation and README must not describe the daemon as
|
|
75
|
+
Unix-only.
|
|
76
|
+
|
|
77
|
+
### Documentation
|
|
78
|
+
|
|
79
|
+
README and command docs must include:
|
|
80
|
+
|
|
81
|
+
- PowerShell examples using built-in `--get`/`--pick` instead of requiring `jq`
|
|
82
|
+
- Windows output paths such as `$env:TEMP\suppliers.csv`
|
|
83
|
+
- Windows local state paths using `%USERPROFILE%\.1688` or `$env:USERPROFILE`
|
|
84
|
+
- named pipe note for daemon IPC on Windows
|
|
85
|
+
|
|
86
|
+
## Acceptance Criteria
|
|
87
|
+
|
|
88
|
+
- `pnpm build` succeeds without requiring `chmod` from the shell.
|
|
89
|
+
- Unit tests cover Windows named pipe generation and platform-specific doctor
|
|
90
|
+
hints.
|
|
91
|
+
- `pnpm test:unit` passes.
|
|
92
|
+
- `pnpm agent-verify` passes.
|
|
93
|
+
- `npm pack --dry-run` succeeds.
|
|
94
|
+
- README and `docs/COMMANDS.md` no longer present Unix-only examples as the
|
|
95
|
+
only way to use JSON, output files, or local paths.
|
|
96
|
+
- Manual Windows smoke checklist is documented:
|
|
97
|
+
- `npm i -g 1688-cli`
|
|
98
|
+
- `1688 --version`
|
|
99
|
+
- `1688 doctor --no-launch --json`
|
|
100
|
+
- `1688 daemon start`
|
|
101
|
+
- `1688 daemon status --json`
|
|
102
|
+
- `1688 daemon stop`
|
|
103
|
+
- `1688 search 雨伞 --max 1 --json`
|
|
104
|
+
- `1688 supplier search 键盘 --max 1 --json`
|
|
105
|
+
|
|
106
|
+
## Verification Signals
|
|
107
|
+
|
|
108
|
+
- Focused tests:
|
|
109
|
+
- `tests/paths.test.ts`
|
|
110
|
+
- `tests/doctor.test.ts` or existing doctor tests
|
|
111
|
+
- Package checks:
|
|
112
|
+
- `pnpm build`
|
|
113
|
+
- `pnpm test:unit`
|
|
114
|
+
- `pnpm agent-verify`
|
|
115
|
+
- `npm pack --dry-run`
|
|
116
|
+
- Manual Windows smoke checks listed above when a Windows machine/session is
|
|
117
|
+
available.
|
|
118
|
+
|
|
119
|
+
## Open Questions
|
|
120
|
+
|
|
121
|
+
- Whether to add GitHub Actions `windows-latest` is a repository operations
|
|
122
|
+
decision. The code/docs work should make that job straightforward, but adding
|
|
123
|
+
CI config is not required unless the repository already uses GitHub Actions.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "1688-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "1688.com CLI for humans, Codex, and Claude Code.
|
|
3
|
+
"version": "0.1.43",
|
|
4
|
+
"description": "1688.com CLI for humans, Codex, and Claude Code. Product search, supplier scraper/research, inquiry, cart, orders, logistics, and seller chat.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "nobodyjack",
|
|
7
7
|
"homepage": "https://github.com/superjack2050/1688-cli#readme",
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
"claude-code",
|
|
23
23
|
"codex",
|
|
24
24
|
"automation",
|
|
25
|
+
"scraper",
|
|
26
|
+
"supplier-scraper",
|
|
27
|
+
"supplier-search",
|
|
28
|
+
"product-scraper",
|
|
29
|
+
"sourcing",
|
|
25
30
|
"ecommerce",
|
|
26
31
|
"procurement",
|
|
27
32
|
"playwright",
|
|
@@ -36,7 +41,13 @@
|
|
|
36
41
|
"files": [
|
|
37
42
|
"dist",
|
|
38
43
|
"scripts/postinstall.mjs",
|
|
44
|
+
"scripts/fix_bin_mode.mjs",
|
|
45
|
+
"scripts/generate_agent_context.mjs",
|
|
46
|
+
"scripts/check_agent_map.mjs",
|
|
47
|
+
"scripts/check_release.mjs",
|
|
48
|
+
"docs",
|
|
39
49
|
"AGENTS.md",
|
|
50
|
+
"ARCHITECTURE.md",
|
|
40
51
|
"README.md",
|
|
41
52
|
"LICENSE",
|
|
42
53
|
"CHANGELOG.md"
|
|
@@ -46,11 +57,17 @@
|
|
|
46
57
|
},
|
|
47
58
|
"scripts": {
|
|
48
59
|
"dev": "tsx src/cli.ts",
|
|
49
|
-
"build": "tsc -p tsconfig.json &&
|
|
60
|
+
"build": "tsc -p tsconfig.json && node scripts/fix_bin_mode.mjs",
|
|
50
61
|
"test": "vitest run",
|
|
62
|
+
"test:unit": "vitest run --exclude tests/doctor-live.test.ts",
|
|
51
63
|
"test:watch": "vitest",
|
|
52
64
|
"typecheck": "tsc --noEmit",
|
|
53
|
-
"
|
|
65
|
+
"agent-context": "node scripts/generate_agent_context.mjs",
|
|
66
|
+
"docs-check": "node scripts/generate_agent_context.mjs --check",
|
|
67
|
+
"agent-map-check": "node scripts/check_agent_map.mjs",
|
|
68
|
+
"release-check": "node scripts/check_release.mjs",
|
|
69
|
+
"agent-verify": "pnpm typecheck && pnpm test:unit && pnpm docs-check && pnpm agent-map-check && pnpm release-check",
|
|
70
|
+
"prepublishOnly": "pnpm release-check && pnpm build",
|
|
54
71
|
"postinstall": "node scripts/postinstall.mjs"
|
|
55
72
|
},
|
|
56
73
|
"dependencies": {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
|
|
8
|
+
async function read(relPath) {
|
|
9
|
+
return fs.readFile(path.join(root, relPath), 'utf8');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function exists(relPath) {
|
|
13
|
+
try {
|
|
14
|
+
await fs.access(path.join(root, relPath));
|
|
15
|
+
return true;
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const requiredFiles = [
|
|
22
|
+
'AGENTS.md',
|
|
23
|
+
'ARCHITECTURE.md',
|
|
24
|
+
'docs/AGENT_WORKING_PRINCIPLES.md',
|
|
25
|
+
'docs/README.md',
|
|
26
|
+
'docs/WORKFLOW.md',
|
|
27
|
+
'docs/COMMANDS.md',
|
|
28
|
+
'docs/JSON_CONTRACTS.md',
|
|
29
|
+
'docs/SAFETY.md',
|
|
30
|
+
'docs/RELIABILITY.md',
|
|
31
|
+
'docs/QUALITY_SCORE.md',
|
|
32
|
+
'docs/FEATURES.md',
|
|
33
|
+
'docs/specs/sourcing-research.md',
|
|
34
|
+
'docs/specs/seller-im.md',
|
|
35
|
+
'docs/specs/checkout-and-orders.md',
|
|
36
|
+
'docs/playbooks/add-command.md',
|
|
37
|
+
'docs/playbooks/change-json-output.md',
|
|
38
|
+
'docs/playbooks/debug-risk-control.md',
|
|
39
|
+
'docs/playbooks/add-mtop-capture.md',
|
|
40
|
+
'docs/playbooks/update-cli-release.md',
|
|
41
|
+
'docs/records/release-omissions.md',
|
|
42
|
+
'docs/generated/command-index.md',
|
|
43
|
+
'docs/generated/module-map.md',
|
|
44
|
+
'docs/generated/test-index.md',
|
|
45
|
+
'docs/generated/json-shapes.md',
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
const missing = [];
|
|
49
|
+
for (const file of requiredFiles) {
|
|
50
|
+
if (!(await exists(file))) missing.push(file);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const failures = [];
|
|
54
|
+
if (missing.length) failures.push(`Missing files: ${missing.join(', ')}`);
|
|
55
|
+
|
|
56
|
+
const agents = await read('AGENTS.md').catch(() => '');
|
|
57
|
+
if (!agents.includes('pnpm agent-verify'))
|
|
58
|
+
failures.push('AGENTS.md must mention pnpm agent-verify.');
|
|
59
|
+
if (!agents.includes('docs/playbooks'))
|
|
60
|
+
failures.push('AGENTS.md must route work to docs/playbooks.');
|
|
61
|
+
if (!agents.includes('docs/AGENT_WORKING_PRINCIPLES.md'))
|
|
62
|
+
failures.push('AGENTS.md must link docs/AGENT_WORKING_PRINCIPLES.md.');
|
|
63
|
+
if (agents.split('\n').length > 220)
|
|
64
|
+
failures.push('AGENTS.md should stay short (<= 220 lines).');
|
|
65
|
+
|
|
66
|
+
const pkg = JSON.parse(await read('package.json'));
|
|
67
|
+
for (const scriptName of ['agent-context', 'docs-check', 'agent-map-check', 'release-check', 'agent-verify']) {
|
|
68
|
+
if (!pkg.scripts?.[scriptName]) failures.push(`package.json missing script: ${scriptName}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const docsReadme = await read('docs/README.md').catch(() => '');
|
|
72
|
+
for (const needle of [
|
|
73
|
+
'AGENT_WORKING_PRINCIPLES.md',
|
|
74
|
+
'COMMANDS.md',
|
|
75
|
+
'JSON_CONTRACTS.md',
|
|
76
|
+
'SAFETY.md',
|
|
77
|
+
'generated/',
|
|
78
|
+
]) {
|
|
79
|
+
if (!docsReadme.includes(needle)) failures.push(`docs/README.md must link ${needle}.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (failures.length) {
|
|
83
|
+
console.error(failures.join('\n'));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log('Agent map structure looks good.');
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
|
|
8
|
+
async function readJson(relPath) {
|
|
9
|
+
return JSON.parse(await fs.readFile(path.join(root, relPath), 'utf8'));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function read(relPath) {
|
|
13
|
+
return fs.readFile(path.join(root, relPath), 'utf8');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const pkg = await readJson('package.json');
|
|
17
|
+
const changelog = await read('CHANGELOG.md');
|
|
18
|
+
|
|
19
|
+
const version = pkg.version;
|
|
20
|
+
const releaseHeading = new RegExp(`^## \\[${version.replaceAll('.', '\\.')}\\] - \\d{4}-\\d{2}-\\d{2}$`, 'm');
|
|
21
|
+
const failures = [];
|
|
22
|
+
|
|
23
|
+
if (!releaseHeading.test(changelog)) {
|
|
24
|
+
failures.push(`CHANGELOG.md missing release heading for package version ${version}.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!pkg.files?.includes('CHANGELOG.md')) {
|
|
28
|
+
failures.push('package.json files must include CHANGELOG.md.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!pkg.scripts?.['release-check']) {
|
|
32
|
+
failures.push('package.json missing script: release-check.');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (failures.length) {
|
|
36
|
+
console.error(failures.join('\n'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`Release metadata looks good for ${pkg.name}@${version}.`);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export async function fixBinMode({
|
|
6
|
+
platform = process.platform,
|
|
7
|
+
target = path.join('dist', 'cli.js'),
|
|
8
|
+
} = {}) {
|
|
9
|
+
if (platform === 'win32') {
|
|
10
|
+
return { changed: false, reason: 'windows-noop', target };
|
|
11
|
+
}
|
|
12
|
+
await fs.chmod(target, 0o755);
|
|
13
|
+
return { changed: true, reason: 'chmod-755', target };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
17
|
+
await fixBinMode();
|
|
18
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
7
|
+
const outDir = path.join(root, 'docs', 'generated');
|
|
8
|
+
const checkOnly = process.argv.includes('--check');
|
|
9
|
+
|
|
10
|
+
const rel = (p) => path.relative(root, p).replaceAll(path.sep, '/');
|
|
11
|
+
const read = (p) => fs.readFile(path.join(root, p), 'utf8');
|
|
12
|
+
|
|
13
|
+
async function exists(p) {
|
|
14
|
+
try {
|
|
15
|
+
await fs.access(path.join(root, p));
|
|
16
|
+
return true;
|
|
17
|
+
} catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function walk(dir, predicate = () => true) {
|
|
23
|
+
const abs = path.join(root, dir);
|
|
24
|
+
const out = [];
|
|
25
|
+
async function visit(current) {
|
|
26
|
+
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
29
|
+
const p = path.join(current, entry.name);
|
|
30
|
+
if (entry.isDirectory()) {
|
|
31
|
+
await visit(p);
|
|
32
|
+
} else if (predicate(p)) {
|
|
33
|
+
out.push(p);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (await exists(dir)) await visit(abs);
|
|
38
|
+
return out.sort((a, b) => rel(a).localeCompare(rel(b)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function header(title) {
|
|
42
|
+
return `# ${title}\n\n_Generated by \`scripts/generate_agent_context.mjs\`._\n\n`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function cleanDescription(raw) {
|
|
46
|
+
if (!raw) return '-';
|
|
47
|
+
return raw
|
|
48
|
+
.replace(/[`'"]/g, '')
|
|
49
|
+
.replace(/\s*\+\s*/g, ' ')
|
|
50
|
+
.replace(/\s+/g, ' ')
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/\|/g, '\\|') || '-';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function compactList(items) {
|
|
56
|
+
return items.length ? items.map((x) => `\`${x}\``).join('<br>') : '-';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function commandIndex() {
|
|
60
|
+
const cli = await read('src/cli.ts');
|
|
61
|
+
const re =
|
|
62
|
+
/(?:(?:const\s+([A-Za-z0-9_]+)\s*=\s*)?([A-Za-z0-9_]+)\s*\n\s*\.command\('([^']+)'\))/g;
|
|
63
|
+
const matches = [...cli.matchAll(re)];
|
|
64
|
+
const varToCommand = new Map();
|
|
65
|
+
const rows = [];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < matches.length; i++) {
|
|
68
|
+
const m = matches[i];
|
|
69
|
+
const assignedVar = m[1];
|
|
70
|
+
const receiver = m[2];
|
|
71
|
+
const command = m[3];
|
|
72
|
+
const start = m.index ?? 0;
|
|
73
|
+
const end = matches[i + 1]?.index ?? cli.length;
|
|
74
|
+
const block = cli.slice(start, end);
|
|
75
|
+
const parent = receiver === 'program' ? '' : varToCommand.get(receiver) ?? receiver;
|
|
76
|
+
const full = parent ? `${parent} ${command}` : command;
|
|
77
|
+
if (assignedVar) varToCommand.set(assignedVar, full);
|
|
78
|
+
|
|
79
|
+
const descRaw = block.match(/\.description\(([\s\S]*?)\)\s*(?:\.|\n)/)?.[1];
|
|
80
|
+
const args = [...block.matchAll(/\.argument\(\s*(['"`])([^'"`]+)\1/g)].map(
|
|
81
|
+
(x) => x[2],
|
|
82
|
+
);
|
|
83
|
+
const opts = [
|
|
84
|
+
...block.matchAll(/\.(?:requiredOption|option)\(\s*(['"`])([^'"`]+)\1/g),
|
|
85
|
+
].map((x) => x[2]);
|
|
86
|
+
const source = block.match(/import\('\.\/commands\/([^']+)\.js'\)/)?.[1];
|
|
87
|
+
rows.push({
|
|
88
|
+
command: full,
|
|
89
|
+
source: source ? `src/commands/${source}.ts` : '-',
|
|
90
|
+
args,
|
|
91
|
+
opts,
|
|
92
|
+
description: cleanDescription(descRaw),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let out = header('Command Index');
|
|
97
|
+
out += '| Command | Source | Arguments | Options | Description |\n';
|
|
98
|
+
out += '|---|---|---|---|---|\n';
|
|
99
|
+
for (const row of rows) {
|
|
100
|
+
out += `| \`${row.command}\` | ${row.source === '-' ? '-' : `\`${row.source}\``} | ${compactList(
|
|
101
|
+
row.args,
|
|
102
|
+
)} | ${compactList(row.opts)} | ${row.description} |\n`;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function moduleMap() {
|
|
108
|
+
const files = await walk('src', (p) => p.endsWith('.ts'));
|
|
109
|
+
const tests = await walk('tests', (p) => p.endsWith('.test.ts') || p.endsWith('.spec.ts'));
|
|
110
|
+
const dirs = new Map();
|
|
111
|
+
for (const file of files) {
|
|
112
|
+
const r = rel(file);
|
|
113
|
+
const parts = r.split('/');
|
|
114
|
+
const dir = parts.length > 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
115
|
+
const entry = dirs.get(dir) ?? { files: 0, tests: 0, notes: '-' };
|
|
116
|
+
entry.files++;
|
|
117
|
+
dirs.set(dir, entry);
|
|
118
|
+
}
|
|
119
|
+
for (const file of tests) {
|
|
120
|
+
const content = await fs.readFile(file, 'utf8');
|
|
121
|
+
const target = content.match(/from ['"]\.\.\/src\/([^/'"]+)/)?.[1];
|
|
122
|
+
if (target) {
|
|
123
|
+
const key = `src/${target}`;
|
|
124
|
+
const entry = dirs.get(key) ?? { files: 0, tests: 0, notes: '-' };
|
|
125
|
+
entry.tests++;
|
|
126
|
+
dirs.set(key, entry);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const notes = {
|
|
131
|
+
'src/commands': 'command executors and renderers',
|
|
132
|
+
'src/session': 'browser/session automation helpers',
|
|
133
|
+
'src/daemon': 'background daemon client/server/protocol',
|
|
134
|
+
'src/io': 'output, prompts, and errors',
|
|
135
|
+
'src/auth': 'login/session/cookie helpers',
|
|
136
|
+
'src/util': 'shared utilities',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
let out = header('Module Map');
|
|
140
|
+
out += '| Directory | Source Files | Related Tests | Notes |\n';
|
|
141
|
+
out += '|---|---:|---:|---|\n';
|
|
142
|
+
for (const dir of [...dirs.keys()].sort()) {
|
|
143
|
+
const entry = dirs.get(dir);
|
|
144
|
+
out += `| \`${dir}\` | ${entry.files} | ${entry.tests} | ${notes[dir] ?? entry.notes} |\n`;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function testIndex() {
|
|
150
|
+
const tests = await walk('tests', (p) => p.endsWith('.test.ts') || p.endsWith('.spec.ts'));
|
|
151
|
+
let out = header('Test Index');
|
|
152
|
+
out += '| Test File | Focus | Risk Notes |\n';
|
|
153
|
+
out += '|---|---|---|\n';
|
|
154
|
+
for (const file of tests) {
|
|
155
|
+
const content = await fs.readFile(file, 'utf8');
|
|
156
|
+
const name = path.basename(file).replace(/\.(test|spec)\.ts$/, '');
|
|
157
|
+
const risks = [];
|
|
158
|
+
if (/live|doctor-live|process\.env|BB1688|1688\s/.test(content + name))
|
|
159
|
+
risks.push('live/session-sensitive');
|
|
160
|
+
if (/playwright|BrowserContext|Page|page-state|risk-control/i.test(content))
|
|
161
|
+
risks.push('browser/page-state');
|
|
162
|
+
if (/fixture|jsonp|mtop|capture/i.test(content)) risks.push('fixture/parser');
|
|
163
|
+
out += `| \`${rel(file)}\` | ${name} | ${risks.join(', ') || '-'} |\n`;
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function extractInterfaces(source, file) {
|
|
169
|
+
const lines = source.split('\n');
|
|
170
|
+
const interfaces = [];
|
|
171
|
+
for (let i = 0; i < lines.length; i++) {
|
|
172
|
+
const m = lines[i].match(/^export interface ([A-Za-z0-9_]+)/);
|
|
173
|
+
if (!m) continue;
|
|
174
|
+
const name = m[1];
|
|
175
|
+
const keep =
|
|
176
|
+
/(Args|Opts|Result|Offer|Message|CartItem|Order|Sku|Price|Attribute|Package)/.test(
|
|
177
|
+
name,
|
|
178
|
+
);
|
|
179
|
+
if (!keep) continue;
|
|
180
|
+
|
|
181
|
+
const body = [];
|
|
182
|
+
let depth = 0;
|
|
183
|
+
for (let j = i; j < lines.length; j++) {
|
|
184
|
+
const line = lines[j];
|
|
185
|
+
if (j > i) body.push(line);
|
|
186
|
+
depth += (line.match(/\{/g) ?? []).length;
|
|
187
|
+
depth -= (line.match(/\}/g) ?? []).length;
|
|
188
|
+
if (j > i && depth <= 0) break;
|
|
189
|
+
}
|
|
190
|
+
const fields = body
|
|
191
|
+
.map((line) => line.trim())
|
|
192
|
+
.filter((line) => /^[A-Za-z0-9_?]+:/.test(line) || /^\/\*\*/.test(line))
|
|
193
|
+
.slice(0, 14)
|
|
194
|
+
.map((line) => line.replace(/\|/g, '\\|'));
|
|
195
|
+
interfaces.push({ name, file, fields });
|
|
196
|
+
}
|
|
197
|
+
return interfaces;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function jsonShapes() {
|
|
201
|
+
const files = await walk('src', (p) => p.endsWith('.ts'));
|
|
202
|
+
const interfaces = [];
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
const source = await fs.readFile(file, 'utf8');
|
|
205
|
+
interfaces.push(...extractInterfaces(source, rel(file)));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let out = header('JSON Shapes');
|
|
209
|
+
out +=
|
|
210
|
+
'This is a heuristic index of exported TypeScript interfaces that are likely to matter for agent-facing JSON.\n\n';
|
|
211
|
+
out += '| Interface | File | Notable Fields |\n';
|
|
212
|
+
out += '|---|---|---|\n';
|
|
213
|
+
for (const it of interfaces.sort((a, b) => a.file.localeCompare(b.file) || a.name.localeCompare(b.name))) {
|
|
214
|
+
out += `| \`${it.name}\` | \`${it.file}\` | ${it.fields.length ? it.fields.map((x) => `\`${x}\``).join('<br>') : '-'} |\n`;
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const outputs = new Map([
|
|
220
|
+
['command-index.md', await commandIndex()],
|
|
221
|
+
['module-map.md', await moduleMap()],
|
|
222
|
+
['test-index.md', await testIndex()],
|
|
223
|
+
['json-shapes.md', await jsonShapes()],
|
|
224
|
+
]);
|
|
225
|
+
|
|
226
|
+
if (checkOnly) {
|
|
227
|
+
const mismatches = [];
|
|
228
|
+
for (const [name, content] of outputs) {
|
|
229
|
+
const target = path.join(outDir, name);
|
|
230
|
+
let current = '';
|
|
231
|
+
try {
|
|
232
|
+
current = await fs.readFile(target, 'utf8');
|
|
233
|
+
} catch {
|
|
234
|
+
mismatches.push(name);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (current !== content) mismatches.push(name);
|
|
238
|
+
}
|
|
239
|
+
if (mismatches.length) {
|
|
240
|
+
console.error(
|
|
241
|
+
`Generated agent context is stale: ${mismatches.join(', ')}. Run pnpm agent-context.`,
|
|
242
|
+
);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
console.log('Generated agent context is fresh.');
|
|
246
|
+
} else {
|
|
247
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
248
|
+
for (const [name, content] of outputs) {
|
|
249
|
+
await fs.writeFile(path.join(outDir, name), content);
|
|
250
|
+
}
|
|
251
|
+
console.log(`Generated agent context files in ${rel(outDir)}`);
|
|
252
|
+
}
|
|
253
|
+
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -21,7 +21,10 @@ if (process.env.CI || process.env.BB1688_SKIP_POSTINSTALL) {
|
|
|
21
21
|
// Otherwise the old daemon keeps serving stale code until the user runs
|
|
22
22
|
// `1688 daemon stop` manually after every upgrade.
|
|
23
23
|
try {
|
|
24
|
-
const pidFile = path.join(
|
|
24
|
+
const pidFile = path.join(
|
|
25
|
+
process.env.BB1688_HOME ?? path.join(os.homedir(), '.1688'),
|
|
26
|
+
'daemon.pid',
|
|
27
|
+
);
|
|
25
28
|
if (fs.existsSync(pidFile)) {
|
|
26
29
|
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
27
30
|
if (Number.isInteger(pid) && pid > 0) {
|
|
@@ -140,19 +143,24 @@ console.log(
|
|
|
140
143
|
const env = { ...process.env };
|
|
141
144
|
if (downloadHost) env.PLAYWRIGHT_DOWNLOAD_HOST = downloadHost;
|
|
142
145
|
|
|
143
|
-
const
|
|
146
|
+
const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
147
|
+
const res = spawnSync(npxCmd, ['playwright', 'install', 'chromium'], {
|
|
144
148
|
stdio: 'inherit',
|
|
145
149
|
env,
|
|
146
150
|
});
|
|
147
151
|
|
|
148
152
|
if (res.status !== 0) {
|
|
153
|
+
const retryCommand =
|
|
154
|
+
process.platform === 'win32'
|
|
155
|
+
? ' $env:PLAYWRIGHT_DOWNLOAD_HOST="https://npmmirror.com/mirrors/playwright"; npx playwright install chromium\n'
|
|
156
|
+
: ' PLAYWRIGHT_DOWNLOAD_HOST=https://npmmirror.com/mirrors/playwright \\\n' +
|
|
157
|
+
' npx playwright install chromium\n';
|
|
149
158
|
console.log(
|
|
150
159
|
'\n1688-cli: Chromium download failed (non-fatal).\n' +
|
|
151
160
|
' Try running manually:\n' +
|
|
152
161
|
' 1) Install Chrome from https://www.google.com/chrome/ (recommended), or\n' +
|
|
153
162
|
' 2) Force-retry with mirror:\n' +
|
|
154
|
-
|
|
155
|
-
' npx playwright install chromium\n',
|
|
163
|
+
retryCommand,
|
|
156
164
|
);
|
|
157
165
|
}
|
|
158
166
|
process.exit(0);
|