@ystemsrx/cfshare 0.1.2 → 0.1.4
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/README.md +36 -6
- package/README.zh.md +36 -6
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +373 -0
- package/dist/src/manager.d.ts +142 -0
- package/dist/src/manager.d.ts.map +1 -0
- package/dist/src/manager.js +2251 -0
- package/dist/src/policy.d.ts +15 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +130 -0
- package/dist/src/schemas.d.ts +97 -0
- package/dist/src/schemas.d.ts.map +1 -0
- package/dist/src/schemas.js +149 -0
- package/dist/src/templates/fileExplorerTemplate.d.ts +12 -0
- package/dist/src/templates/fileExplorerTemplate.d.ts.map +1 -0
- package/dist/src/templates/fileExplorerTemplate.js +1340 -0
- package/dist/src/templates/markdownPreviewTemplate.d.ts +5 -0
- package/dist/src/templates/markdownPreviewTemplate.d.ts.map +1 -0
- package/dist/src/templates/markdownPreviewTemplate.js +243 -0
- package/dist/src/tools.d.ts +3 -0
- package/dist/src/tools.d.ts.map +1 -0
- package/dist/src/tools.js +138 -0
- package/dist/src/types.d.ts +100 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/package.json +10 -1
- package/src/cli.ts +491 -0
- package/src/manager.ts +9 -4
- package/src/shims.d.ts +55 -0
- package/src/tools.ts +11 -2
package/src/cli.ts
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import process from "node:process";
|
|
7
|
+
import { CfshareManager, type CfshareRuntimeApi } from "./manager.js";
|
|
8
|
+
import type { CfsharePluginConfig } from "./types.js";
|
|
9
|
+
|
|
10
|
+
type CliOptions = {
|
|
11
|
+
command?: string;
|
|
12
|
+
paramsJson?: string;
|
|
13
|
+
paramsFile?: string;
|
|
14
|
+
configJson?: string;
|
|
15
|
+
configFile?: string;
|
|
16
|
+
workspaceDir?: string;
|
|
17
|
+
keepAlive?: boolean;
|
|
18
|
+
compact?: boolean;
|
|
19
|
+
help?: boolean;
|
|
20
|
+
version?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const TOOL_NAMES = new Set([
|
|
24
|
+
"env_check",
|
|
25
|
+
"expose_port",
|
|
26
|
+
"expose_files",
|
|
27
|
+
"exposure_list",
|
|
28
|
+
"exposure_get",
|
|
29
|
+
"exposure_stop",
|
|
30
|
+
"exposure_logs",
|
|
31
|
+
"maintenance",
|
|
32
|
+
"audit_query",
|
|
33
|
+
"audit_export",
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
const CLI_DEFAULT_STATE_DIR = "~/.cfshare";
|
|
37
|
+
|
|
38
|
+
function normalizeCommand(input: string): string {
|
|
39
|
+
return input.trim().toLowerCase().replace(/-/g, "_");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function expandHome(input: string): string {
|
|
43
|
+
if (input === "~") {
|
|
44
|
+
return os.homedir();
|
|
45
|
+
}
|
|
46
|
+
if (input.startsWith("~/")) {
|
|
47
|
+
return path.join(os.homedir(), input.slice(2));
|
|
48
|
+
}
|
|
49
|
+
return input;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function resolvePathFromCwd(input: string): string {
|
|
53
|
+
const expanded = expandHome(input);
|
|
54
|
+
return path.isAbsolute(expanded) ? path.normalize(expanded) : path.resolve(process.cwd(), expanded);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function printHelp() {
|
|
58
|
+
const lines = [
|
|
59
|
+
"CFShare CLI",
|
|
60
|
+
"",
|
|
61
|
+
"Usage:",
|
|
62
|
+
" cfshare <tool> [params-json] [options]",
|
|
63
|
+
"",
|
|
64
|
+
"Tools:",
|
|
65
|
+
" env_check",
|
|
66
|
+
" expose_port",
|
|
67
|
+
" expose_files",
|
|
68
|
+
" exposure_list",
|
|
69
|
+
" exposure_get",
|
|
70
|
+
" exposure_stop",
|
|
71
|
+
" exposure_logs",
|
|
72
|
+
" maintenance",
|
|
73
|
+
" audit_query",
|
|
74
|
+
" audit_export",
|
|
75
|
+
"",
|
|
76
|
+
"Options:",
|
|
77
|
+
" --params <json> Tool parameters as JSON",
|
|
78
|
+
" --params-file <path> Read tool parameters from JSON file",
|
|
79
|
+
" --config <json> Runtime config JSON (same as plugin config)",
|
|
80
|
+
" --config-file <path> Read runtime config from JSON file",
|
|
81
|
+
" --workspace-dir <dir> Workspace dir for expose_files context",
|
|
82
|
+
" --keep-alive Keep process running after expose_*",
|
|
83
|
+
" --no-keep-alive Exit immediately after expose_* result",
|
|
84
|
+
" --compact Compact JSON output",
|
|
85
|
+
" -h, --help Show help",
|
|
86
|
+
" -v, --version Show version",
|
|
87
|
+
"",
|
|
88
|
+
"Examples:",
|
|
89
|
+
" cfshare env_check",
|
|
90
|
+
" cfshare expose_port '{\"port\":3000,\"opts\":{\"access\":\"token\"}}'",
|
|
91
|
+
" cfshare expose_files --params '{\"paths\":[\"./build\"],\"opts\":{\"access\":\"none\"}}'",
|
|
92
|
+
" cfshare exposure_stop --params '{\"id\":\"all\"}'",
|
|
93
|
+
];
|
|
94
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function assertValue(args: string[], index: number, flag: string): string {
|
|
98
|
+
const value = args[index];
|
|
99
|
+
if (!value || value.startsWith("-")) {
|
|
100
|
+
throw new Error(`missing value for ${flag}`);
|
|
101
|
+
}
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseArgs(argv: string[]): CliOptions {
|
|
106
|
+
const opts: CliOptions = {};
|
|
107
|
+
const positionals: string[] = [];
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
110
|
+
const token = argv[i];
|
|
111
|
+
if (!token) {
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (token === "-h" || token === "--help") {
|
|
115
|
+
opts.help = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (token === "-v" || token === "--version") {
|
|
119
|
+
opts.version = true;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (token === "--params") {
|
|
123
|
+
opts.paramsJson = assertValue(argv, i + 1, token);
|
|
124
|
+
i += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (token === "--params-file") {
|
|
128
|
+
opts.paramsFile = assertValue(argv, i + 1, token);
|
|
129
|
+
i += 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (token === "--config") {
|
|
133
|
+
opts.configJson = assertValue(argv, i + 1, token);
|
|
134
|
+
i += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (token === "--config-file") {
|
|
138
|
+
opts.configFile = assertValue(argv, i + 1, token);
|
|
139
|
+
i += 1;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (token === "--workspace-dir") {
|
|
143
|
+
opts.workspaceDir = assertValue(argv, i + 1, token);
|
|
144
|
+
i += 1;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (token === "--keep-alive") {
|
|
148
|
+
opts.keepAlive = true;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (token === "--no-keep-alive") {
|
|
152
|
+
opts.keepAlive = false;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (token === "--compact") {
|
|
156
|
+
opts.compact = true;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (token.startsWith("-")) {
|
|
160
|
+
throw new Error(`unknown option: ${token}`);
|
|
161
|
+
}
|
|
162
|
+
positionals.push(token);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (positionals.length > 0) {
|
|
166
|
+
opts.command = positionals[0];
|
|
167
|
+
}
|
|
168
|
+
if (positionals.length > 1) {
|
|
169
|
+
if (opts.paramsJson || opts.paramsFile) {
|
|
170
|
+
throw new Error("params-json conflicts with --params/--params-file");
|
|
171
|
+
}
|
|
172
|
+
opts.paramsJson = positionals[1];
|
|
173
|
+
}
|
|
174
|
+
if (positionals.length > 2) {
|
|
175
|
+
throw new Error("too many positional arguments");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return opts;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function parseJsonInput(source: string, label: string): Promise<unknown> {
|
|
182
|
+
try {
|
|
183
|
+
return JSON.parse(source);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
throw new Error(`failed to parse ${label}: ${String(error)}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function parseJsonFile(filePath: string, label: string): Promise<unknown> {
|
|
190
|
+
const resolved = resolvePathFromCwd(filePath);
|
|
191
|
+
const content = await fs.readFile(resolved, "utf8");
|
|
192
|
+
return await parseJsonInput(content, `${label} (${resolved})`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function asObject(input: unknown, label: string): Record<string, unknown> {
|
|
196
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
197
|
+
throw new Error(`${label} must be a JSON object`);
|
|
198
|
+
}
|
|
199
|
+
return input as Record<string, unknown>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function createRuntimeApi(config: CfsharePluginConfig): CfshareRuntimeApi {
|
|
203
|
+
const stringifyArgs = (args: unknown[]) =>
|
|
204
|
+
args
|
|
205
|
+
.map((value) => {
|
|
206
|
+
if (typeof value === "string") {
|
|
207
|
+
return value;
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
return JSON.stringify(value);
|
|
211
|
+
} catch {
|
|
212
|
+
return String(value);
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
.join(" ");
|
|
216
|
+
|
|
217
|
+
const logger = {
|
|
218
|
+
info: (...args: unknown[]) => {
|
|
219
|
+
if (process.env.CFSHARE_LOG_LEVEL === "info" || process.env.CFSHARE_LOG_LEVEL === "debug") {
|
|
220
|
+
process.stderr.write(`[cfshare] ${stringifyArgs(args)}\n`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
warn: (...args: unknown[]) => {
|
|
224
|
+
process.stderr.write(`[cfshare][warn] ${stringifyArgs(args)}\n`);
|
|
225
|
+
},
|
|
226
|
+
error: (...args: unknown[]) => {
|
|
227
|
+
process.stderr.write(`[cfshare][error] ${stringifyArgs(args)}\n`);
|
|
228
|
+
},
|
|
229
|
+
debug: (...args: unknown[]) => {
|
|
230
|
+
if (process.env.CFSHARE_LOG_LEVEL === "debug") {
|
|
231
|
+
process.stderr.write(`[cfshare][debug] ${stringifyArgs(args)}\n`);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
} as unknown as CfshareRuntimeApi["logger"];
|
|
235
|
+
|
|
236
|
+
const runtimeConfig: CfsharePluginConfig = {
|
|
237
|
+
stateDir: CLI_DEFAULT_STATE_DIR,
|
|
238
|
+
...config,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
logger,
|
|
243
|
+
resolvePath: resolvePathFromCwd,
|
|
244
|
+
pluginConfig: runtimeConfig,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function shouldKeepAlive(command: string, keepAliveFlag: boolean | undefined): boolean {
|
|
249
|
+
if (typeof keepAliveFlag === "boolean") {
|
|
250
|
+
return keepAliveFlag;
|
|
251
|
+
}
|
|
252
|
+
return command === "expose_port" || command === "expose_files";
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function waitUntilExposureStops(manager: CfshareManager, id: string): Promise<void> {
|
|
256
|
+
await new Promise<void>((resolve, reject) => {
|
|
257
|
+
let stopping = false;
|
|
258
|
+
let interval: NodeJS.Timeout | undefined;
|
|
259
|
+
|
|
260
|
+
const shutdown = async (reason: string) => {
|
|
261
|
+
if (stopping) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
stopping = true;
|
|
265
|
+
if (interval) {
|
|
266
|
+
clearInterval(interval);
|
|
267
|
+
}
|
|
268
|
+
process.removeListener("SIGINT", onSigint);
|
|
269
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
270
|
+
try {
|
|
271
|
+
await manager.stopExposure(id, { reason });
|
|
272
|
+
} catch {
|
|
273
|
+
// best effort cleanup on signal
|
|
274
|
+
} finally {
|
|
275
|
+
resolve();
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const onSigint = () => {
|
|
280
|
+
void shutdown("cli interrupted");
|
|
281
|
+
};
|
|
282
|
+
const onSigterm = () => {
|
|
283
|
+
void shutdown("cli terminated");
|
|
284
|
+
};
|
|
285
|
+
process.once("SIGINT", onSigint);
|
|
286
|
+
process.once("SIGTERM", onSigterm);
|
|
287
|
+
|
|
288
|
+
interval = setInterval(async () => {
|
|
289
|
+
try {
|
|
290
|
+
const detail = (await manager.exposureGet({ id })) as { status?: unknown };
|
|
291
|
+
const statusValue = detail.status;
|
|
292
|
+
const state =
|
|
293
|
+
typeof statusValue === "string"
|
|
294
|
+
? statusValue
|
|
295
|
+
: typeof statusValue === "object" && statusValue && "state" in statusValue
|
|
296
|
+
? String((statusValue as { state?: unknown }).state ?? "")
|
|
297
|
+
: "";
|
|
298
|
+
if (state === "stopped" || state === "expired" || state === "error" || state === "not_found") {
|
|
299
|
+
clearInterval(interval);
|
|
300
|
+
process.removeListener("SIGINT", onSigint);
|
|
301
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
302
|
+
resolve();
|
|
303
|
+
}
|
|
304
|
+
} catch (error) {
|
|
305
|
+
clearInterval(interval);
|
|
306
|
+
process.removeListener("SIGINT", onSigint);
|
|
307
|
+
process.removeListener("SIGTERM", onSigterm);
|
|
308
|
+
reject(error);
|
|
309
|
+
}
|
|
310
|
+
}, 1000);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function runTool(
|
|
315
|
+
manager: CfshareManager,
|
|
316
|
+
command: string,
|
|
317
|
+
params: Record<string, unknown>,
|
|
318
|
+
opts: CliOptions,
|
|
319
|
+
): Promise<unknown> {
|
|
320
|
+
if (command === "env_check") {
|
|
321
|
+
return await manager.envCheck();
|
|
322
|
+
}
|
|
323
|
+
if (command === "expose_port") {
|
|
324
|
+
return await manager.exposePort(params as { port: number; opts?: Record<string, unknown> });
|
|
325
|
+
}
|
|
326
|
+
if (command === "expose_files") {
|
|
327
|
+
const ctx = opts.workspaceDir ? { workspaceDir: opts.workspaceDir } : undefined;
|
|
328
|
+
return await manager.exposeFiles(
|
|
329
|
+
params as {
|
|
330
|
+
paths: string[];
|
|
331
|
+
opts?: {
|
|
332
|
+
mode?: "normal" | "zip";
|
|
333
|
+
presentation?: "download" | "preview" | "raw";
|
|
334
|
+
ttl_seconds?: number;
|
|
335
|
+
access?: "token" | "basic" | "none";
|
|
336
|
+
max_downloads?: number;
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
ctx,
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
if (command === "exposure_list") {
|
|
343
|
+
return manager.exposureList();
|
|
344
|
+
}
|
|
345
|
+
if (command === "exposure_get") {
|
|
346
|
+
return await manager.exposureGet(
|
|
347
|
+
params as {
|
|
348
|
+
id?: string;
|
|
349
|
+
ids?: string[];
|
|
350
|
+
filter?: {
|
|
351
|
+
status?: "starting" | "running" | "stopped" | "error" | "expired";
|
|
352
|
+
type?: "port" | "files";
|
|
353
|
+
};
|
|
354
|
+
fields?: Array<
|
|
355
|
+
| "id"
|
|
356
|
+
| "type"
|
|
357
|
+
| "status"
|
|
358
|
+
| "port"
|
|
359
|
+
| "public_url"
|
|
360
|
+
| "expires_at"
|
|
361
|
+
| "local_url"
|
|
362
|
+
| "stats"
|
|
363
|
+
| "file_sharing"
|
|
364
|
+
| "last_error"
|
|
365
|
+
| "manifest"
|
|
366
|
+
| "created_at"
|
|
367
|
+
>;
|
|
368
|
+
opts?: {
|
|
369
|
+
probe_public?: boolean;
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
if (command === "exposure_stop") {
|
|
375
|
+
const stopParams = params as { id?: string; ids?: string[]; opts?: { reason?: string } };
|
|
376
|
+
const target = stopParams.ids ?? stopParams.id;
|
|
377
|
+
if (!target) {
|
|
378
|
+
throw new Error("exposure_stop requires id or ids");
|
|
379
|
+
}
|
|
380
|
+
return await manager.stopExposure(target, stopParams.opts);
|
|
381
|
+
}
|
|
382
|
+
if (command === "exposure_logs") {
|
|
383
|
+
const logParams = params as {
|
|
384
|
+
id?: string;
|
|
385
|
+
ids?: string[];
|
|
386
|
+
opts?: { lines?: number; since_seconds?: number; component?: "tunnel" | "origin" | "all" };
|
|
387
|
+
};
|
|
388
|
+
const target = logParams.ids ?? logParams.id;
|
|
389
|
+
if (!target) {
|
|
390
|
+
throw new Error("exposure_logs requires id or ids");
|
|
391
|
+
}
|
|
392
|
+
return manager.exposureLogs(target, logParams.opts);
|
|
393
|
+
}
|
|
394
|
+
if (command === "maintenance") {
|
|
395
|
+
const maintenanceParams = params as {
|
|
396
|
+
action: "start_guard" | "run_gc" | "set_policy";
|
|
397
|
+
opts?: { policy?: unknown; ignore_patterns?: string[] };
|
|
398
|
+
};
|
|
399
|
+
return await manager.maintenance(maintenanceParams.action, maintenanceParams.opts);
|
|
400
|
+
}
|
|
401
|
+
if (command === "audit_query") {
|
|
402
|
+
const queryParams = params as {
|
|
403
|
+
filters?: {
|
|
404
|
+
id?: string;
|
|
405
|
+
event?: string;
|
|
406
|
+
type?: "port" | "files";
|
|
407
|
+
from_ts?: string;
|
|
408
|
+
to_ts?: string;
|
|
409
|
+
limit?: number;
|
|
410
|
+
};
|
|
411
|
+
};
|
|
412
|
+
return await manager.auditQuery(queryParams.filters);
|
|
413
|
+
}
|
|
414
|
+
if (command === "audit_export") {
|
|
415
|
+
const exportParams = params as {
|
|
416
|
+
range?: {
|
|
417
|
+
from_ts?: string;
|
|
418
|
+
to_ts?: string;
|
|
419
|
+
id?: string;
|
|
420
|
+
event?: string;
|
|
421
|
+
type?: "port" | "files";
|
|
422
|
+
output_path?: string;
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
return await manager.auditExport(exportParams.range);
|
|
426
|
+
}
|
|
427
|
+
throw new Error(`unsupported command: ${command}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function readVersion(): Promise<string> {
|
|
431
|
+
const packagePath = new URL("../../package.json", import.meta.url);
|
|
432
|
+
const content = await fs.readFile(packagePath, "utf8");
|
|
433
|
+
const parsed = JSON.parse(content) as { version?: string };
|
|
434
|
+
return parsed.version ?? "unknown";
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async function main() {
|
|
438
|
+
const options = parseArgs(process.argv.slice(2));
|
|
439
|
+
|
|
440
|
+
if (options.version) {
|
|
441
|
+
process.stdout.write(`${await readVersion()}\n`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (options.help || !options.command) {
|
|
446
|
+
printHelp();
|
|
447
|
+
process.exit(options.help ? 0 : 1);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const command = normalizeCommand(options.command);
|
|
451
|
+
if (!TOOL_NAMES.has(command)) {
|
|
452
|
+
throw new Error(`unknown tool: ${options.command}`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const paramsInput =
|
|
456
|
+
options.paramsJson !== undefined
|
|
457
|
+
? await parseJsonInput(options.paramsJson, "--params")
|
|
458
|
+
: options.paramsFile
|
|
459
|
+
? await parseJsonFile(options.paramsFile, "--params-file")
|
|
460
|
+
: {};
|
|
461
|
+
const configInput =
|
|
462
|
+
options.configJson !== undefined
|
|
463
|
+
? await parseJsonInput(options.configJson, "--config")
|
|
464
|
+
: options.configFile
|
|
465
|
+
? await parseJsonFile(options.configFile, "--config-file")
|
|
466
|
+
: {};
|
|
467
|
+
|
|
468
|
+
const params = asObject(paramsInput, "params");
|
|
469
|
+
const config = asObject(configInput, "config") as CfsharePluginConfig;
|
|
470
|
+
const manager = new CfshareManager(createRuntimeApi(config));
|
|
471
|
+
|
|
472
|
+
const result = await runTool(manager, command, params, options);
|
|
473
|
+
process.stdout.write(`${JSON.stringify(result, null, options.compact ? undefined : 2)}\n`);
|
|
474
|
+
|
|
475
|
+
if (shouldKeepAlive(command, options.keepAlive)) {
|
|
476
|
+
const exposureId = typeof result === "object" && result ? (result as { id?: unknown }).id : undefined;
|
|
477
|
+
if (typeof exposureId !== "string" || !exposureId) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
process.stderr.write(
|
|
481
|
+
`cfshare: exposure ${exposureId} is running. Press Ctrl+C to stop or use --no-keep-alive.\n`,
|
|
482
|
+
);
|
|
483
|
+
await waitUntilExposureStops(manager, exposureId);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
void main().catch((error) => {
|
|
488
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
489
|
+
process.stderr.write(`cfshare error: ${message}\n`);
|
|
490
|
+
process.exit(1);
|
|
491
|
+
});
|
package/src/manager.ts
CHANGED
|
@@ -30,6 +30,8 @@ import type {
|
|
|
30
30
|
RateLimitPolicy,
|
|
31
31
|
} from "./types.js";
|
|
32
32
|
|
|
33
|
+
export type CfshareRuntimeApi = Pick<OpenClawPluginApi, "logger" | "resolvePath" | "pluginConfig">;
|
|
34
|
+
|
|
33
35
|
const MAX_LOG_LINES = 4000;
|
|
34
36
|
const MAX_RESPONSE_MANIFEST_ITEMS = 200;
|
|
35
37
|
const MAX_RESPONSE_MANIFEST_ITEMS_MULTI_GET = 20;
|
|
@@ -715,7 +717,7 @@ function matchAuditFilters(
|
|
|
715
717
|
}
|
|
716
718
|
|
|
717
719
|
export class CfshareManager {
|
|
718
|
-
private readonly logger:
|
|
720
|
+
private readonly logger: CfshareRuntimeApi["logger"];
|
|
719
721
|
private readonly resolvePath: (input: string) => string;
|
|
720
722
|
private readonly pluginConfig: CfsharePluginConfig;
|
|
721
723
|
private readonly cloudflaredPathInput: string;
|
|
@@ -736,7 +738,7 @@ export class CfshareManager {
|
|
|
736
738
|
private guardTimer?: NodeJS.Timeout;
|
|
737
739
|
private readonly sessions = new Map<string, ExposureSession>();
|
|
738
740
|
|
|
739
|
-
constructor(api:
|
|
741
|
+
constructor(api: CfshareRuntimeApi) {
|
|
740
742
|
this.logger = api.logger;
|
|
741
743
|
this.resolvePath = api.resolvePath;
|
|
742
744
|
this.pluginConfig = (api.pluginConfig ?? {}) as CfsharePluginConfig;
|
|
@@ -893,7 +895,7 @@ export class CfshareManager {
|
|
|
893
895
|
return queryToken === access.token || headerToken === access.token || bearer === access.token;
|
|
894
896
|
}
|
|
895
897
|
const basic = parseBasicAuth(req.headers.authorization);
|
|
896
|
-
return basic?.username === access.username && basic
|
|
898
|
+
return basic?.username === access.username && basic?.password === access.password;
|
|
897
899
|
}
|
|
898
900
|
|
|
899
901
|
private async startReverseProxy(params: {
|
|
@@ -1609,7 +1611,7 @@ export class CfshareManager {
|
|
|
1609
1611
|
this.appendLog(session, "tunnel", `spawn: ${cloudflaredBin} ${args.join(" ")}`);
|
|
1610
1612
|
|
|
1611
1613
|
const proc = spawn(cloudflaredBin, args, {
|
|
1612
|
-
stdio: ["
|
|
1614
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1613
1615
|
});
|
|
1614
1616
|
|
|
1615
1617
|
let settled = false;
|
|
@@ -1725,6 +1727,9 @@ export class CfshareManager {
|
|
|
1725
1727
|
return;
|
|
1726
1728
|
}
|
|
1727
1729
|
const pid = proc.pid;
|
|
1730
|
+
if (!pid) {
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1728
1733
|
try {
|
|
1729
1734
|
process.kill(pid, 0);
|
|
1730
1735
|
} catch {
|
package/src/shims.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
declare module "openclaw/plugin-sdk" {
|
|
2
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
3
|
+
|
|
4
|
+
export type OpenClawToolContext = {
|
|
5
|
+
workspaceDir?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type OpenClawToolDefinition = {
|
|
9
|
+
name: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description: string;
|
|
12
|
+
parameters?: TSchema | Record<string, unknown>;
|
|
13
|
+
execute: (...args: any[]) => unknown | Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type OpenClawPluginApi = {
|
|
17
|
+
logger: {
|
|
18
|
+
info: (...args: unknown[]) => void;
|
|
19
|
+
warn: (...args: unknown[]) => void;
|
|
20
|
+
error: (...args: unknown[]) => void;
|
|
21
|
+
debug: (...args: unknown[]) => void;
|
|
22
|
+
};
|
|
23
|
+
resolvePath: (input: string) => string;
|
|
24
|
+
pluginConfig?: Record<string, unknown>;
|
|
25
|
+
registerTool: (
|
|
26
|
+
factory:
|
|
27
|
+
| ((ctx: OpenClawToolContext) => OpenClawToolDefinition[])
|
|
28
|
+
| ((ctx: OpenClawToolContext) => Promise<OpenClawToolDefinition[]>),
|
|
29
|
+
options?: {
|
|
30
|
+
names?: string[];
|
|
31
|
+
},
|
|
32
|
+
) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function emptyPluginConfigSchema(): Record<string, unknown>;
|
|
36
|
+
export function jsonResult<T>(value: T): T;
|
|
37
|
+
export function stringEnum<const T extends readonly string[]>(
|
|
38
|
+
values: T,
|
|
39
|
+
options?: Record<string, unknown>,
|
|
40
|
+
): TSchema;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
declare module "yazl" {
|
|
44
|
+
class ZipFile {
|
|
45
|
+
outputStream: NodeJS.ReadableStream;
|
|
46
|
+
addFile(realPath: string, metadataPath: string, options?: Record<string, unknown>): void;
|
|
47
|
+
end(options?: Record<string, unknown>, callback?: () => void): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const yazl: {
|
|
51
|
+
ZipFile: typeof ZipFile;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default yazl;
|
|
55
|
+
}
|
package/src/tools.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TSchema } from "@sinclair/typebox";
|
|
1
2
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
3
|
import { jsonResult } from "openclaw/plugin-sdk";
|
|
3
4
|
import { CfshareManager } from "./manager.js";
|
|
@@ -27,7 +28,15 @@ type ToolContext = {
|
|
|
27
28
|
workspaceDir?: string;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
type RegisteredTool = {
|
|
32
|
+
name: string;
|
|
33
|
+
label: string;
|
|
34
|
+
description: string;
|
|
35
|
+
parameters: TSchema | Record<string, unknown>;
|
|
36
|
+
execute: (...args: any[]) => Promise<unknown>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function registerToolsForContext(api: OpenClawPluginApi, ctx: ToolContext): RegisteredTool[] {
|
|
31
40
|
const manager = getManager(api);
|
|
32
41
|
|
|
33
42
|
return [
|
|
@@ -250,7 +259,7 @@ export function registerCfshareTools(api: OpenClawPluginApi) {
|
|
|
250
259
|
"audit_export",
|
|
251
260
|
];
|
|
252
261
|
|
|
253
|
-
api.registerTool((ctx) => registerToolsForContext(api, ctx), {
|
|
262
|
+
api.registerTool((ctx: ToolContext) => registerToolsForContext(api, ctx), {
|
|
254
263
|
names,
|
|
255
264
|
});
|
|
256
265
|
}
|