@jackwener/opencli 1.6.10 → 1.7.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.
Files changed (36) hide show
  1. package/README.md +15 -2
  2. package/README.zh-CN.md +15 -2
  3. package/dist/clis/jianyu/detail.js +20 -0
  4. package/dist/clis/jianyu/search.d.ts +41 -4
  5. package/dist/clis/jianyu/search.js +458 -96
  6. package/dist/clis/jianyu/search.test.js +105 -0
  7. package/dist/clis/jianyu/shared/china-bid-search.d.ts +12 -0
  8. package/dist/clis/jianyu/shared/china-bid-search.js +165 -0
  9. package/dist/clis/jianyu/shared/procurement-contract.d.ts +68 -0
  10. package/dist/clis/jianyu/shared/procurement-contract.js +324 -0
  11. package/dist/clis/jianyu/shared/procurement-contract.test.d.ts +1 -0
  12. package/dist/clis/jianyu/shared/procurement-contract.test.js +72 -0
  13. package/dist/clis/jianyu/shared/procurement-detail.d.ts +6 -0
  14. package/dist/clis/jianyu/shared/procurement-detail.js +92 -0
  15. package/dist/clis/jianyu/shared/procurement-detail.test.d.ts +1 -0
  16. package/dist/clis/jianyu/shared/procurement-detail.test.js +72 -0
  17. package/dist/clis/xiaoe/catalog.js +36 -0
  18. package/dist/src/browser/bridge.js +1 -1
  19. package/dist/src/browser/daemon-client.d.ts +2 -1
  20. package/dist/src/browser/daemon-client.js +3 -1
  21. package/dist/src/browser/daemon-client.test.js +0 -3
  22. package/dist/src/browser.test.js +0 -1
  23. package/dist/src/cli.js +1 -9
  24. package/dist/src/commands/daemon.d.ts +2 -6
  25. package/dist/src/commands/daemon.js +2 -58
  26. package/dist/src/commands/daemon.test.js +24 -120
  27. package/dist/src/constants.d.ts +0 -2
  28. package/dist/src/constants.js +0 -2
  29. package/dist/src/daemon.d.ts +1 -1
  30. package/dist/src/daemon.js +2 -15
  31. package/dist/src/execution.js +5 -1
  32. package/package.json +2 -1
  33. package/dist/src/daemon.test.js +0 -65
  34. package/dist/src/idle-manager.d.ts +0 -19
  35. package/dist/src/idle-manager.js +0 -54
  36. /package/dist/{src/daemon.test.d.ts → clis/jianyu/detail.d.ts} +0 -0
@@ -3,8 +3,6 @@
3
3
  */
4
4
  /** Default daemon port for HTTP/WebSocket communication with browser extension */
5
5
  export const DEFAULT_DAEMON_PORT = 19825;
6
- /** Default idle timeout before daemon auto-exits (ms). Override via OPENCLI_DAEMON_TIMEOUT env var. */
7
- export const DEFAULT_DAEMON_IDLE_TIMEOUT = 4 * 60 * 60 * 1000; // 4 hours
8
6
  /** URL query params that are volatile/ephemeral and should be stripped from patterns */
9
7
  export const VOLATILE_PARAMS = new Set([
10
8
  'w_rid', 'wts', '_', 'callback', 'timestamp', 't', 'nonce', 'sign',
@@ -15,7 +15,7 @@
15
15
  *
16
16
  * Lifecycle:
17
17
  * - Auto-spawned by opencli on first browser command
18
- * - Auto-exits after idle timeout (default 4h, configurable via OPENCLI_DAEMON_TIMEOUT)
18
+ * - Persistent stays alive until explicit shutdown, SIGTERM, or uninstall
19
19
  * - Listens on localhost:19825
20
20
  */
21
21
  export {};
@@ -15,16 +15,14 @@
15
15
  *
16
16
  * Lifecycle:
17
17
  * - Auto-spawned by opencli on first browser command
18
- * - Auto-exits after idle timeout (default 4h, configurable via OPENCLI_DAEMON_TIMEOUT)
18
+ * - Persistent stays alive until explicit shutdown, SIGTERM, or uninstall
19
19
  * - Listens on localhost:19825
20
20
  */
21
21
  import { createServer } from 'node:http';
22
22
  import { WebSocketServer, WebSocket } from 'ws';
23
- import { DEFAULT_DAEMON_PORT, DEFAULT_DAEMON_IDLE_TIMEOUT } from './constants.js';
23
+ import { DEFAULT_DAEMON_PORT } from './constants.js';
24
24
  import { EXIT_CODES } from './errors.js';
25
- import { IdleManager } from './idle-manager.js';
26
25
  const PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
27
- const IDLE_TIMEOUT = Number(process.env.OPENCLI_DAEMON_TIMEOUT ?? DEFAULT_DAEMON_IDLE_TIMEOUT);
28
26
  // ─── State ───────────────────────────────────────────────────────────
29
27
  let extensionWs = null;
30
28
  let extensionVersion = null;
@@ -36,11 +34,6 @@ function pushLog(entry) {
36
34
  if (logBuffer.length > LOG_BUFFER_SIZE)
37
35
  logBuffer.shift();
38
36
  }
39
- // ─── Idle auto-exit ──────────────────────────────────────────────────
40
- const idleManager = new IdleManager(IDLE_TIMEOUT, () => {
41
- console.error('[daemon] Idle timeout (no CLI requests + no Extension), shutting down');
42
- process.exit(EXIT_CODES.SUCCESS);
43
- });
44
37
  // ─── HTTP Server ─────────────────────────────────────────────────────
45
38
  const MAX_BODY = 1024 * 1024; // 1 MB — commands are tiny; this prevents OOM
46
39
  function readBody(req) {
@@ -118,7 +111,6 @@ async function handleRequest(req, res) {
118
111
  extensionConnected: extensionWs?.readyState === WebSocket.OPEN,
119
112
  extensionVersion,
120
113
  pending: pending.size,
121
- lastCliRequestTime: idleManager.lastCliRequestTime,
122
114
  memoryMB: Math.round(mem.rss / 1024 / 1024 * 10) / 10,
123
115
  port: PORT,
124
116
  });
@@ -144,7 +136,6 @@ async function handleRequest(req, res) {
144
136
  return;
145
137
  }
146
138
  if (req.method === 'POST' && url === '/command') {
147
- idleManager.onCliRequest();
148
139
  try {
149
140
  const body = JSON.parse(await readBody(req));
150
141
  if (!body.id) {
@@ -196,7 +187,6 @@ wss.on('connection', (ws) => {
196
187
  console.error('[daemon] Extension connected');
197
188
  extensionWs = ws;
198
189
  extensionVersion = null; // cleared until hello message arrives
199
- idleManager.setExtensionConnected(true);
200
190
  // ── Heartbeat: ping every 15s, close if 2 pongs missed ──
201
191
  let missedPongs = 0;
202
192
  const heartbeatInterval = setInterval(() => {
@@ -249,7 +239,6 @@ wss.on('connection', (ws) => {
249
239
  if (extensionWs === ws) {
250
240
  extensionWs = null;
251
241
  extensionVersion = null;
252
- idleManager.setExtensionConnected(false);
253
242
  // Reject all pending requests since the extension is gone
254
243
  for (const [id, p] of pending) {
255
244
  clearTimeout(p.timer);
@@ -263,7 +252,6 @@ wss.on('connection', (ws) => {
263
252
  if (extensionWs === ws) {
264
253
  extensionWs = null;
265
254
  extensionVersion = null;
266
- idleManager.setExtensionConnected(false);
267
255
  // Reject pending requests in case 'close' does not follow this 'error'
268
256
  for (const [, p] of pending) {
269
257
  clearTimeout(p.timer);
@@ -276,7 +264,6 @@ wss.on('connection', (ws) => {
276
264
  // ─── Start ───────────────────────────────────────────────────────────
277
265
  httpServer.listen(PORT, '127.0.0.1', () => {
278
266
  console.error(`[daemon] Listening on http://127.0.0.1:${PORT}`);
279
- idleManager.onCliRequest();
280
267
  });
281
268
  httpServer.on('error', (err) => {
282
269
  if (err.code === 'EADDRINUSE') {
@@ -167,10 +167,14 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
167
167
  }
168
168
  }
169
169
  try {
170
- return await runWithTimeout(runCommand(cmd, page, kwargs, debug), {
170
+ const result = await runWithTimeout(runCommand(cmd, page, kwargs, debug), {
171
171
  timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
172
172
  label: fullName(cmd),
173
173
  });
174
+ // Adapter commands are one-shot — close the automation window immediately
175
+ // instead of waiting for the 30s idle timeout.
176
+ await page.closeWindow?.().catch(() => { });
177
+ return result;
174
178
  }
175
179
  catch (err) {
176
180
  // Collect diagnostic while page is still alive (before browserSession closes it).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.6.10",
3
+ "version": "1.7.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -48,6 +48,7 @@
48
48
  "copy-yaml": "node scripts/copy-yaml.cjs",
49
49
  "start": "node dist/src/main.js",
50
50
  "start:bun": "bun dist/src/main.js",
51
+ "preuninstall": "node -e \"fetch('http://127.0.0.1:19825/shutdown',{method:'POST',headers:{'X-OpenCLI':'1'},signal:AbortSignal.timeout(3000)}).catch(()=>{})\" || true",
51
52
  "postinstall": "node scripts/postinstall.js || true; node scripts/fetch-adapters.js || true",
52
53
  "typecheck": "tsc --noEmit",
53
54
  "lint": "tsc --noEmit",
@@ -1,65 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { IdleManager } from './idle-manager.js';
3
- describe('IdleManager', () => {
4
- beforeEach(() => {
5
- vi.useFakeTimers();
6
- });
7
- afterEach(() => {
8
- vi.useRealTimers();
9
- });
10
- it('does not start timer when extension is connected', () => {
11
- const exit = vi.fn();
12
- const mgr = new IdleManager(300_000, exit);
13
- mgr.setExtensionConnected(true);
14
- mgr.onCliRequest();
15
- vi.advanceTimersByTime(300_000 + 1000);
16
- expect(exit).not.toHaveBeenCalled();
17
- });
18
- it('starts timer when extension disconnects and CLI is idle', () => {
19
- const exit = vi.fn();
20
- const mgr = new IdleManager(300_000, exit);
21
- mgr.onCliRequest();
22
- mgr.setExtensionConnected(true);
23
- mgr.setExtensionConnected(false);
24
- expect(exit).not.toHaveBeenCalled();
25
- vi.advanceTimersByTime(300_000 + 1000);
26
- expect(exit).toHaveBeenCalledTimes(1);
27
- });
28
- it('exits immediately on extension disconnect if CLI has been idle past timeout', () => {
29
- const exit = vi.fn();
30
- const mgr = new IdleManager(300_000, exit);
31
- mgr.onCliRequest();
32
- mgr.setExtensionConnected(true); // connect before timeout elapses
33
- vi.advanceTimersByTime(400_000); // CLI idle time exceeds timeout, but extension is connected so no exit
34
- expect(exit).not.toHaveBeenCalled();
35
- mgr.setExtensionConnected(false); // disconnect → should exit immediately since CLI idle > timeout
36
- expect(exit).toHaveBeenCalledTimes(1);
37
- });
38
- it('resets timer on new CLI request', () => {
39
- const exit = vi.fn();
40
- const mgr = new IdleManager(300_000, exit);
41
- mgr.onCliRequest();
42
- vi.advanceTimersByTime(200_000);
43
- mgr.onCliRequest();
44
- vi.advanceTimersByTime(200_000);
45
- expect(exit).not.toHaveBeenCalled();
46
- vi.advanceTimersByTime(100_001);
47
- expect(exit).toHaveBeenCalledTimes(1);
48
- });
49
- it('does not exit when timeout is 0 (disabled)', () => {
50
- const exit = vi.fn();
51
- const mgr = new IdleManager(0, exit);
52
- mgr.onCliRequest();
53
- vi.advanceTimersByTime(24 * 60 * 60 * 1000);
54
- expect(exit).not.toHaveBeenCalled();
55
- });
56
- it('clears timer when extension connects', () => {
57
- const exit = vi.fn();
58
- const mgr = new IdleManager(300_000, exit);
59
- mgr.onCliRequest();
60
- vi.advanceTimersByTime(200_000);
61
- mgr.setExtensionConnected(true);
62
- vi.advanceTimersByTime(200_000);
63
- expect(exit).not.toHaveBeenCalled();
64
- });
65
- });
@@ -1,19 +0,0 @@
1
- /**
2
- * Manages daemon idle timeout with dual-condition logic:
3
- * exits only when BOTH CLI is idle AND Extension is disconnected.
4
- */
5
- export declare class IdleManager {
6
- private _timer;
7
- private _lastCliRequestTime;
8
- private _extensionConnected;
9
- private _timeoutMs;
10
- private _onExit;
11
- constructor(timeoutMs: number, onExit: () => void);
12
- get lastCliRequestTime(): number;
13
- /** Call when an HTTP request arrives from CLI */
14
- onCliRequest(): void;
15
- /** Call when Extension WebSocket connects or disconnects */
16
- setExtensionConnected(connected: boolean): void;
17
- private _clearTimer;
18
- private _resetTimer;
19
- }
@@ -1,54 +0,0 @@
1
- /**
2
- * Manages daemon idle timeout with dual-condition logic:
3
- * exits only when BOTH CLI is idle AND Extension is disconnected.
4
- */
5
- export class IdleManager {
6
- _timer = null;
7
- _lastCliRequestTime = Date.now();
8
- _extensionConnected = false;
9
- _timeoutMs;
10
- _onExit;
11
- constructor(timeoutMs, onExit) {
12
- this._timeoutMs = timeoutMs;
13
- this._onExit = onExit;
14
- }
15
- get lastCliRequestTime() {
16
- return this._lastCliRequestTime;
17
- }
18
- /** Call when an HTTP request arrives from CLI */
19
- onCliRequest() {
20
- this._lastCliRequestTime = Date.now();
21
- this._resetTimer();
22
- }
23
- /** Call when Extension WebSocket connects or disconnects */
24
- setExtensionConnected(connected) {
25
- this._extensionConnected = connected;
26
- if (connected) {
27
- this._clearTimer();
28
- }
29
- else {
30
- this._resetTimer();
31
- }
32
- }
33
- _clearTimer() {
34
- if (this._timer) {
35
- clearTimeout(this._timer);
36
- this._timer = null;
37
- }
38
- }
39
- _resetTimer() {
40
- this._clearTimer();
41
- if (this._timeoutMs <= 0)
42
- return;
43
- if (this._extensionConnected)
44
- return;
45
- const elapsed = Date.now() - this._lastCliRequestTime;
46
- if (elapsed >= this._timeoutMs) {
47
- this._onExit();
48
- return;
49
- }
50
- this._timer = setTimeout(() => {
51
- this._onExit();
52
- }, this._timeoutMs - elapsed);
53
- }
54
- }