@yushaw/sanqian-sdk 0.2.10 → 0.2.12
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/index.browser.d.mts +445 -0
- package/dist/index.browser.mjs +1071 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +29 -2
- package/dist/index.d.ts +29 -2
- package/dist/index.js +345 -281
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +363 -266
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/dist/index.js
CHANGED
|
@@ -5,6 +5,12 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
11
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
12
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
13
|
+
};
|
|
8
14
|
var __export = (target, all) => {
|
|
9
15
|
for (var name in all)
|
|
10
16
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -27,290 +33,308 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
27
33
|
));
|
|
28
34
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
35
|
|
|
30
|
-
//
|
|
31
|
-
var
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
|
|
37
|
-
SDKErrorCode: () => SDKErrorCode,
|
|
38
|
-
SanqianSDK: () => SanqianSDK,
|
|
39
|
-
SanqianSDKError: () => SanqianSDKError,
|
|
40
|
-
createSDKError: () => createSDKError
|
|
36
|
+
// node_modules/isomorphic-ws/node.js
|
|
37
|
+
var require_node = __commonJS({
|
|
38
|
+
"node_modules/isomorphic-ws/node.js"(exports2, module2) {
|
|
39
|
+
"use strict";
|
|
40
|
+
module2.exports = require("ws");
|
|
41
|
+
}
|
|
41
42
|
});
|
|
42
|
-
module.exports = __toCommonJS(index_exports);
|
|
43
|
-
|
|
44
|
-
// src/client.ts
|
|
45
|
-
var import_isomorphic_ws = __toESM(require("isomorphic-ws"));
|
|
46
43
|
|
|
47
44
|
// src/discovery.ts
|
|
48
|
-
var
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
*/
|
|
76
|
-
read() {
|
|
77
|
-
const filePath = this.getConnectionFilePath();
|
|
78
|
-
if (!(0, import_fs.existsSync)(filePath)) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
83
|
-
const info = JSON.parse(content);
|
|
84
|
-
if (!info.port || !info.token || !info.pid) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
if (info.executable) {
|
|
88
|
-
this.cachedExecutable = info.executable;
|
|
89
|
-
}
|
|
90
|
-
if (!this.isProcessRunning(info.pid)) {
|
|
91
|
-
return null;
|
|
45
|
+
var discovery_exports = {};
|
|
46
|
+
__export(discovery_exports, {
|
|
47
|
+
DiscoveryManager: () => DiscoveryManager
|
|
48
|
+
});
|
|
49
|
+
var import_fs, import_os, import_path, import_child_process, DiscoveryManager;
|
|
50
|
+
var init_discovery = __esm({
|
|
51
|
+
"src/discovery.ts"() {
|
|
52
|
+
"use strict";
|
|
53
|
+
import_fs = require("fs");
|
|
54
|
+
import_os = require("os");
|
|
55
|
+
import_path = require("path");
|
|
56
|
+
import_child_process = require("child_process");
|
|
57
|
+
DiscoveryManager = class {
|
|
58
|
+
connectionInfo = null;
|
|
59
|
+
watcher = null;
|
|
60
|
+
onChange = null;
|
|
61
|
+
pollInterval = null;
|
|
62
|
+
/**
|
|
63
|
+
* Cached executable path from last successful connection.json read.
|
|
64
|
+
* Persists even when Sanqian is not running, for use in auto-launch.
|
|
65
|
+
*/
|
|
66
|
+
cachedExecutable = null;
|
|
67
|
+
/**
|
|
68
|
+
* Get the path to connection.json
|
|
69
|
+
*/
|
|
70
|
+
getConnectionFilePath() {
|
|
71
|
+
return (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime", "connection.json");
|
|
92
72
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Read and validate connection info
|
|
75
|
+
*
|
|
76
|
+
* Returns null if:
|
|
77
|
+
* - File doesn't exist
|
|
78
|
+
* - File is invalid JSON
|
|
79
|
+
* - Process is not running
|
|
80
|
+
*/
|
|
81
|
+
read() {
|
|
82
|
+
const filePath = this.getConnectionFilePath();
|
|
83
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const content = (0, import_fs.readFileSync)(filePath, "utf-8");
|
|
88
|
+
const info = JSON.parse(content);
|
|
89
|
+
if (!info.port || !info.token || !info.pid) {
|
|
90
|
+
return null;
|
|
111
91
|
}
|
|
112
|
-
|
|
92
|
+
if (info.executable) {
|
|
93
|
+
this.cachedExecutable = info.executable;
|
|
94
|
+
}
|
|
95
|
+
if (!this.isProcessRunning(info.pid)) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
this.connectionInfo = info;
|
|
99
|
+
return info;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
113
102
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.
|
|
129
|
-
this.onChange?.(newInfo);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Start watching for connection file changes
|
|
106
|
+
*/
|
|
107
|
+
startWatching(onChange) {
|
|
108
|
+
this.onChange = onChange;
|
|
109
|
+
const dir = (0, import_path.join)((0, import_os.homedir)(), ".sanqian", "runtime");
|
|
110
|
+
if (!(0, import_fs.existsSync)(dir)) {
|
|
111
|
+
this.pollInterval = setInterval(() => {
|
|
112
|
+
if ((0, import_fs.existsSync)(dir)) {
|
|
113
|
+
if (this.pollInterval) {
|
|
114
|
+
clearInterval(this.pollInterval);
|
|
115
|
+
this.pollInterval = null;
|
|
116
|
+
}
|
|
117
|
+
this.setupWatcher(dir);
|
|
130
118
|
}
|
|
131
|
-
},
|
|
119
|
+
}, 2e3);
|
|
120
|
+
return;
|
|
132
121
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Stop watching
|
|
140
|
-
*/
|
|
141
|
-
stopWatching() {
|
|
142
|
-
if (this.pollInterval) {
|
|
143
|
-
clearInterval(this.pollInterval);
|
|
144
|
-
this.pollInterval = null;
|
|
145
|
-
}
|
|
146
|
-
this.watcher?.close();
|
|
147
|
-
this.watcher = null;
|
|
148
|
-
this.onChange = null;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Check if a process is running by PID
|
|
152
|
-
*
|
|
153
|
-
* Cross-platform implementation:
|
|
154
|
-
* - macOS/Linux: process.kill(pid, 0) works correctly
|
|
155
|
-
* - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
|
|
156
|
-
* so we use tasklist command for reliable checking
|
|
157
|
-
*/
|
|
158
|
-
isProcessRunning(pid) {
|
|
159
|
-
try {
|
|
160
|
-
if ((0, import_os.platform)() === "win32") {
|
|
122
|
+
this.setupWatcher(dir);
|
|
123
|
+
}
|
|
124
|
+
setupWatcher(dir) {
|
|
161
125
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
126
|
+
this.watcher = (0, import_fs.watch)(dir, (event, filename) => {
|
|
127
|
+
if (filename === "connection.json") {
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
const newInfo = this.read();
|
|
130
|
+
const oldInfoStr = JSON.stringify(this.connectionInfo);
|
|
131
|
+
const newInfoStr = JSON.stringify(newInfo);
|
|
132
|
+
if (oldInfoStr !== newInfoStr) {
|
|
133
|
+
this.connectionInfo = newInfo;
|
|
134
|
+
this.onChange?.(newInfo);
|
|
135
|
+
}
|
|
136
|
+
}, 100);
|
|
137
|
+
}
|
|
165
138
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error("Failed to watch connection file:", e);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Stop watching
|
|
145
|
+
*/
|
|
146
|
+
stopWatching() {
|
|
147
|
+
if (this.pollInterval) {
|
|
148
|
+
clearInterval(this.pollInterval);
|
|
149
|
+
this.pollInterval = null;
|
|
150
|
+
}
|
|
151
|
+
this.watcher?.close();
|
|
152
|
+
this.watcher = null;
|
|
153
|
+
this.onChange = null;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Check if a process is running by PID
|
|
157
|
+
*
|
|
158
|
+
* Cross-platform implementation:
|
|
159
|
+
* - macOS/Linux: process.kill(pid, 0) works correctly
|
|
160
|
+
* - Windows: process.kill(pid, 0) can give false positives due to PID reuse,
|
|
161
|
+
* so we use tasklist command for reliable checking
|
|
162
|
+
*/
|
|
163
|
+
isProcessRunning(pid) {
|
|
164
|
+
try {
|
|
165
|
+
if ((0, import_os.platform)() === "win32") {
|
|
166
|
+
try {
|
|
167
|
+
const result = (0, import_child_process.execSync)(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
168
|
+
encoding: "utf-8",
|
|
169
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
170
|
+
});
|
|
171
|
+
const trimmed = result.trim();
|
|
172
|
+
if (!trimmed || trimmed.toUpperCase().startsWith("INFO")) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
process.kill(pid, 0);
|
|
181
|
+
return true;
|
|
169
182
|
}
|
|
170
|
-
return new RegExp(`\\b${pid}\\b`).test(trimmed);
|
|
171
183
|
} catch {
|
|
172
184
|
return false;
|
|
173
185
|
}
|
|
174
|
-
} else {
|
|
175
|
-
process.kill(pid, 0);
|
|
176
|
-
return true;
|
|
177
186
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
* Get cached connection info (may be stale)
|
|
184
|
-
*/
|
|
185
|
-
getCached() {
|
|
186
|
-
return this.connectionInfo;
|
|
187
|
-
}
|
|
188
|
-
/**
|
|
189
|
-
* Build WebSocket URL from connection info
|
|
190
|
-
*/
|
|
191
|
-
buildWebSocketUrl(info) {
|
|
192
|
-
const wsPath = info.ws_path || "/ws/apps";
|
|
193
|
-
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* Build HTTP base URL from connection info
|
|
197
|
-
*/
|
|
198
|
-
buildHttpUrl(info) {
|
|
199
|
-
return `http://127.0.0.1:${info.port}`;
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Find Sanqian executable path
|
|
203
|
-
* Searches in standard installation locations for each platform
|
|
204
|
-
*/
|
|
205
|
-
findSanqianPath(customPath) {
|
|
206
|
-
if (customPath) {
|
|
207
|
-
if ((0, import_fs.existsSync)(customPath)) {
|
|
208
|
-
return customPath;
|
|
187
|
+
/**
|
|
188
|
+
* Get cached connection info (may be stale)
|
|
189
|
+
*/
|
|
190
|
+
getCached() {
|
|
191
|
+
return this.connectionInfo;
|
|
209
192
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
searchPaths.push(
|
|
217
|
-
// Production: installed app
|
|
218
|
-
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
219
|
-
(0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
220
|
-
// Development: electron-builder output
|
|
221
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
222
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
223
|
-
);
|
|
224
|
-
} else if (os === "win32") {
|
|
225
|
-
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
226
|
-
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
227
|
-
const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
|
|
228
|
-
searchPaths.push(
|
|
229
|
-
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
230
|
-
(0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
231
|
-
// Legacy/alternative paths with uppercase
|
|
232
|
-
(0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
233
|
-
(0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
|
|
234
|
-
(0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
235
|
-
// Development: electron-builder output
|
|
236
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
237
|
-
);
|
|
238
|
-
} else {
|
|
239
|
-
searchPaths.push(
|
|
240
|
-
"/usr/bin/sanqian",
|
|
241
|
-
"/usr/local/bin/sanqian",
|
|
242
|
-
(0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
|
|
243
|
-
"/opt/Sanqian/sanqian",
|
|
244
|
-
// Development
|
|
245
|
-
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
for (const path of searchPaths) {
|
|
249
|
-
if ((0, import_fs.existsSync)(path)) {
|
|
250
|
-
return path;
|
|
193
|
+
/**
|
|
194
|
+
* Build WebSocket URL from connection info
|
|
195
|
+
*/
|
|
196
|
+
buildWebSocketUrl(info) {
|
|
197
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
198
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
251
199
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
if (customPath) {
|
|
267
|
-
sanqianPath = this.findSanqianPath(customPath);
|
|
268
|
-
} else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
|
|
269
|
-
sanqianPath = this.cachedExecutable;
|
|
270
|
-
console.log(`[SDK] Using cached executable: ${sanqianPath}`);
|
|
271
|
-
} else {
|
|
272
|
-
sanqianPath = this.findSanqianPath();
|
|
273
|
-
}
|
|
274
|
-
if (!sanqianPath) {
|
|
275
|
-
console.error("[SDK] Sanqian executable not found");
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
279
|
-
try {
|
|
280
|
-
const os = (0, import_os.platform)();
|
|
281
|
-
if (os === "darwin") {
|
|
282
|
-
const appPath = sanqianPath.replace(
|
|
283
|
-
"/Contents/MacOS/Sanqian",
|
|
284
|
-
""
|
|
285
|
-
);
|
|
286
|
-
(0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
|
|
287
|
-
detached: true,
|
|
288
|
-
stdio: "ignore"
|
|
289
|
-
}).unref();
|
|
290
|
-
} else {
|
|
291
|
-
(0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
|
|
292
|
-
detached: true,
|
|
293
|
-
stdio: "ignore",
|
|
294
|
-
// On Windows, hide the console window
|
|
295
|
-
...os === "win32" && {
|
|
296
|
-
windowsHide: true,
|
|
297
|
-
shell: false
|
|
200
|
+
/**
|
|
201
|
+
* Build HTTP base URL from connection info
|
|
202
|
+
*/
|
|
203
|
+
buildHttpUrl(info) {
|
|
204
|
+
return `http://127.0.0.1:${info.port}`;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Find Sanqian executable path
|
|
208
|
+
* Searches in standard installation locations for each platform
|
|
209
|
+
*/
|
|
210
|
+
findSanqianPath(customPath) {
|
|
211
|
+
if (customPath) {
|
|
212
|
+
if ((0, import_fs.existsSync)(customPath)) {
|
|
213
|
+
return customPath;
|
|
298
214
|
}
|
|
299
|
-
|
|
215
|
+
console.warn(`[SDK] Custom Sanqian path not found: ${customPath}`);
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const os = (0, import_os.platform)();
|
|
219
|
+
const searchPaths = [];
|
|
220
|
+
if (os === "darwin") {
|
|
221
|
+
searchPaths.push(
|
|
222
|
+
// Production: installed app
|
|
223
|
+
"/Applications/Sanqian.app/Contents/MacOS/Sanqian",
|
|
224
|
+
(0, import_path.join)((0, import_os.homedir)(), "Applications/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
225
|
+
// Development: electron-builder output
|
|
226
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac-arm64/Sanqian.app/Contents/MacOS/Sanqian"),
|
|
227
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/mac/Sanqian.app/Contents/MacOS/Sanqian")
|
|
228
|
+
);
|
|
229
|
+
} else if (os === "win32") {
|
|
230
|
+
const programFiles = process.env.PROGRAMFILES || "C:\\Program Files";
|
|
231
|
+
const programFilesX86 = process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)";
|
|
232
|
+
const localAppData = process.env.LOCALAPPDATA || (0, import_path.join)((0, import_os.homedir)(), "AppData", "Local");
|
|
233
|
+
searchPaths.push(
|
|
234
|
+
// Production: NSIS installer uses lowercase directory name from package.json "name"
|
|
235
|
+
(0, import_path.join)(localAppData, "Programs", "sanqian", "Sanqian.exe"),
|
|
236
|
+
// Legacy/alternative paths with uppercase
|
|
237
|
+
(0, import_path.join)(localAppData, "Programs", "Sanqian", "Sanqian.exe"),
|
|
238
|
+
(0, import_path.join)(programFiles, "Sanqian", "Sanqian.exe"),
|
|
239
|
+
(0, import_path.join)(programFilesX86, "Sanqian", "Sanqian.exe"),
|
|
240
|
+
// Development: electron-builder output
|
|
241
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev", "sanqian", "dist", "win-unpacked", "Sanqian.exe")
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
searchPaths.push(
|
|
245
|
+
"/usr/bin/sanqian",
|
|
246
|
+
"/usr/local/bin/sanqian",
|
|
247
|
+
(0, import_path.join)((0, import_os.homedir)(), ".local/bin/sanqian"),
|
|
248
|
+
"/opt/Sanqian/sanqian",
|
|
249
|
+
// Development
|
|
250
|
+
(0, import_path.join)((0, import_os.homedir)(), "dev/sanqian/dist/linux-unpacked/sanqian")
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
for (const path of searchPaths) {
|
|
254
|
+
if ((0, import_fs.existsSync)(path)) {
|
|
255
|
+
return path;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
300
259
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Launch Sanqian in hidden/tray mode
|
|
262
|
+
* Returns true if launch was initiated successfully
|
|
263
|
+
*
|
|
264
|
+
* Priority for finding Sanqian executable:
|
|
265
|
+
* 1. customPath parameter (if provided)
|
|
266
|
+
* 2. Cached executable from connection.json (most reliable)
|
|
267
|
+
* 3. Search in standard installation locations (fallback)
|
|
268
|
+
*/
|
|
269
|
+
launchSanqian(customPath) {
|
|
270
|
+
let sanqianPath = null;
|
|
271
|
+
if (customPath) {
|
|
272
|
+
sanqianPath = this.findSanqianPath(customPath);
|
|
273
|
+
} else if (this.cachedExecutable && (0, import_fs.existsSync)(this.cachedExecutable)) {
|
|
274
|
+
sanqianPath = this.cachedExecutable;
|
|
275
|
+
console.log(`[SDK] Using cached executable: ${sanqianPath}`);
|
|
276
|
+
} else {
|
|
277
|
+
sanqianPath = this.findSanqianPath();
|
|
278
|
+
}
|
|
279
|
+
if (!sanqianPath) {
|
|
280
|
+
console.error("[SDK] Sanqian executable not found");
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
console.log(`[SDK] Launching Sanqian from: ${sanqianPath}`);
|
|
284
|
+
try {
|
|
285
|
+
const os = (0, import_os.platform)();
|
|
286
|
+
if (os === "darwin") {
|
|
287
|
+
const appPath = sanqianPath.replace(
|
|
288
|
+
"/Contents/MacOS/Sanqian",
|
|
289
|
+
""
|
|
290
|
+
);
|
|
291
|
+
(0, import_child_process.spawn)("open", ["-g", "-a", appPath, "--args", "--hidden"], {
|
|
292
|
+
detached: true,
|
|
293
|
+
stdio: "ignore"
|
|
294
|
+
}).unref();
|
|
295
|
+
} else {
|
|
296
|
+
(0, import_child_process.spawn)(sanqianPath, ["--hidden"], {
|
|
297
|
+
detached: true,
|
|
298
|
+
stdio: "ignore",
|
|
299
|
+
// On Windows, hide the console window
|
|
300
|
+
...os === "win32" && {
|
|
301
|
+
windowsHide: true,
|
|
302
|
+
shell: false
|
|
303
|
+
}
|
|
304
|
+
}).unref();
|
|
305
|
+
}
|
|
306
|
+
return true;
|
|
307
|
+
} catch (e) {
|
|
308
|
+
console.error("[SDK] Failed to launch Sanqian:", e);
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Check if Sanqian is running
|
|
314
|
+
*/
|
|
315
|
+
isSanqianRunning() {
|
|
316
|
+
return this.read() !== null;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
312
319
|
}
|
|
313
|
-
};
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// src/index.ts
|
|
323
|
+
var src_exports = {};
|
|
324
|
+
__export(src_exports, {
|
|
325
|
+
Conversation: () => Conversation,
|
|
326
|
+
DiscoveryManager: () => DiscoveryManager,
|
|
327
|
+
ErrorMessages: () => ErrorMessages,
|
|
328
|
+
SANQIAN_WEBSITE: () => SANQIAN_WEBSITE,
|
|
329
|
+
SDKErrorCode: () => SDKErrorCode,
|
|
330
|
+
SanqianSDK: () => SanqianSDK,
|
|
331
|
+
SanqianSDKError: () => SanqianSDKError,
|
|
332
|
+
createSDKError: () => createSDKError
|
|
333
|
+
});
|
|
334
|
+
module.exports = __toCommonJS(src_exports);
|
|
335
|
+
|
|
336
|
+
// src/client.ts
|
|
337
|
+
var import_isomorphic_ws = __toESM(require_node());
|
|
314
338
|
|
|
315
339
|
// src/errors.ts
|
|
316
340
|
var SANQIAN_WEBSITE = "https://sanqian.io";
|
|
@@ -481,7 +505,8 @@ function createSDKError(code, details) {
|
|
|
481
505
|
// src/client.ts
|
|
482
506
|
var SanqianSDK = class _SanqianSDK {
|
|
483
507
|
config;
|
|
484
|
-
|
|
508
|
+
// DiscoveryManager is only initialized in Node.js environments (when connectionInfo is not provided)
|
|
509
|
+
discovery = null;
|
|
485
510
|
ws = null;
|
|
486
511
|
connectionInfo = null;
|
|
487
512
|
state = {
|
|
@@ -537,11 +562,10 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
537
562
|
autoLaunchSanqian: true,
|
|
538
563
|
...config
|
|
539
564
|
};
|
|
540
|
-
this.discovery = new DiscoveryManager();
|
|
541
565
|
for (const tool of config.tools) {
|
|
542
566
|
this.toolHandlers.set(tool.name, tool.handler);
|
|
543
567
|
}
|
|
544
|
-
this.launchedBySanqian = typeof process !== "undefined" && process.env?.SANQIAN_NO_RECONNECT === "1";
|
|
568
|
+
this.launchedBySanqian = typeof process !== "undefined" && (process.env?.SANQIAN_LAUNCHED === "1" || process.env?.SANQIAN_NO_RECONNECT === "1");
|
|
545
569
|
if (this.launchedBySanqian) {
|
|
546
570
|
this.log("Detected launch by Sanqian, will connect immediately without auto-reconnect");
|
|
547
571
|
setTimeout(() => {
|
|
@@ -551,6 +575,26 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
551
575
|
}, 0);
|
|
552
576
|
}
|
|
553
577
|
}
|
|
578
|
+
/**
|
|
579
|
+
* Build WebSocket URL from connection info
|
|
580
|
+
* This is inlined to avoid importing DiscoveryManager in browser environments
|
|
581
|
+
*/
|
|
582
|
+
buildWebSocketUrl(info) {
|
|
583
|
+
const wsPath = info.ws_path || "/ws/apps";
|
|
584
|
+
return `ws://127.0.0.1:${info.port}${wsPath}?token=${info.token}`;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Lazily initialize DiscoveryManager (only in Node.js environments)
|
|
588
|
+
* This uses dynamic import to avoid bundling Node.js APIs in browser builds
|
|
589
|
+
*/
|
|
590
|
+
async getDiscovery() {
|
|
591
|
+
if (this.discovery) {
|
|
592
|
+
return this.discovery;
|
|
593
|
+
}
|
|
594
|
+
const { DiscoveryManager: DiscoveryManager2 } = await Promise.resolve().then(() => (init_discovery(), discovery_exports));
|
|
595
|
+
this.discovery = new DiscoveryManager2();
|
|
596
|
+
return this.discovery;
|
|
597
|
+
}
|
|
554
598
|
// ============================================
|
|
555
599
|
// Lifecycle
|
|
556
600
|
// ============================================
|
|
@@ -574,7 +618,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
574
618
|
*/
|
|
575
619
|
async connectWithInfo(info) {
|
|
576
620
|
this.connectionInfo = info;
|
|
577
|
-
const url = this.
|
|
621
|
+
const url = this.buildWebSocketUrl(info);
|
|
578
622
|
return new Promise((resolve, reject) => {
|
|
579
623
|
this.log(`Connecting to ${url}`);
|
|
580
624
|
this.ws = new import_isomorphic_ws.default(url);
|
|
@@ -624,7 +668,7 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
624
668
|
async disconnect() {
|
|
625
669
|
this.stopHeartbeat();
|
|
626
670
|
this.stopReconnect();
|
|
627
|
-
this.discovery
|
|
671
|
+
this.discovery?.stopWatching();
|
|
628
672
|
if (this.ws) {
|
|
629
673
|
this.ws.close(1e3, "Client disconnect");
|
|
630
674
|
this.ws = null;
|
|
@@ -715,22 +759,23 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
715
759
|
case "text":
|
|
716
760
|
handler.onEvent({ type: "text", content });
|
|
717
761
|
break;
|
|
762
|
+
case "thinking":
|
|
763
|
+
handler.onEvent({ type: "thinking", content });
|
|
764
|
+
break;
|
|
718
765
|
case "tool_call":
|
|
719
766
|
handler.onEvent({ type: "tool_call", tool_call });
|
|
720
767
|
break;
|
|
721
768
|
case "tool_result":
|
|
722
769
|
if (tool_result) {
|
|
723
770
|
handler.onEvent({
|
|
724
|
-
type: "
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
name: "tool_result",
|
|
730
|
-
arguments: JSON.stringify(tool_result)
|
|
731
|
-
}
|
|
732
|
-
}
|
|
771
|
+
type: "tool_result",
|
|
772
|
+
tool_call_id: tool_result.call_id,
|
|
773
|
+
result: tool_result.result,
|
|
774
|
+
success: tool_result.success,
|
|
775
|
+
error: tool_result.error
|
|
733
776
|
});
|
|
777
|
+
} else {
|
|
778
|
+
this.log("Received tool_result event without tool_result data");
|
|
734
779
|
}
|
|
735
780
|
break;
|
|
736
781
|
case "done":
|
|
@@ -1005,11 +1050,12 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1005
1050
|
this.log("Using pre-configured connection info (browser mode)");
|
|
1006
1051
|
info = this.config.connectionInfo;
|
|
1007
1052
|
} else {
|
|
1008
|
-
|
|
1053
|
+
const discovery = await this.getDiscovery();
|
|
1054
|
+
info = discovery.read();
|
|
1009
1055
|
if (!info) {
|
|
1010
1056
|
if (this.config.autoLaunchSanqian) {
|
|
1011
1057
|
this.log("Sanqian not running, attempting to launch...");
|
|
1012
|
-
const launched =
|
|
1058
|
+
const launched = discovery.launchSanqian(this.config.sanqianPath);
|
|
1013
1059
|
if (!launched) {
|
|
1014
1060
|
throw createSDKError("NOT_INSTALLED" /* NOT_INSTALLED */);
|
|
1015
1061
|
}
|
|
@@ -1028,8 +1074,9 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1028
1074
|
async waitForSanqianStartup(timeout = 12e4) {
|
|
1029
1075
|
const startTime = Date.now();
|
|
1030
1076
|
const pollInterval = 500;
|
|
1077
|
+
const discovery = await this.getDiscovery();
|
|
1031
1078
|
while (Date.now() - startTime < timeout) {
|
|
1032
|
-
const info =
|
|
1079
|
+
const info = discovery.read();
|
|
1033
1080
|
if (info) {
|
|
1034
1081
|
this.log("Sanqian started, connection info available");
|
|
1035
1082
|
return info;
|
|
@@ -1378,6 +1425,20 @@ var SanqianSDK = class _SanqianSDK {
|
|
|
1378
1425
|
});
|
|
1379
1426
|
return this.on(event, onceWrapper);
|
|
1380
1427
|
}
|
|
1428
|
+
/**
|
|
1429
|
+
* Remove all event listeners
|
|
1430
|
+
*
|
|
1431
|
+
* Call this to prevent memory leaks when disposing the SDK instance.
|
|
1432
|
+
* If event is specified, only removes listeners for that event.
|
|
1433
|
+
*/
|
|
1434
|
+
removeAllListeners(event) {
|
|
1435
|
+
if (event) {
|
|
1436
|
+
this.eventListeners.delete(event);
|
|
1437
|
+
} else {
|
|
1438
|
+
this.eventListeners.clear();
|
|
1439
|
+
}
|
|
1440
|
+
return this;
|
|
1441
|
+
}
|
|
1381
1442
|
emit(event, ...args) {
|
|
1382
1443
|
const listeners = this.eventListeners.get(event);
|
|
1383
1444
|
if (listeners) {
|
|
@@ -1478,6 +1539,9 @@ var Conversation = class {
|
|
|
1478
1539
|
});
|
|
1479
1540
|
}
|
|
1480
1541
|
};
|
|
1542
|
+
|
|
1543
|
+
// src/index.ts
|
|
1544
|
+
init_discovery();
|
|
1481
1545
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1482
1546
|
0 && (module.exports = {
|
|
1483
1547
|
Conversation,
|