svamp-cli 0.1.23 → 0.1.28
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/bin/svamp-agent.mjs +34 -0
- package/dist/agent-cli.mjs +453 -0
- package/dist/cli.mjs +242 -18
- package/dist/commands-BOeSil-P.mjs +459 -0
- package/dist/commands-BU4GZQuH.mjs +481 -0
- package/dist/commands-Ba66PxtQ.mjs +481 -0
- package/dist/commands-CNqOjR1y.mjs +481 -0
- package/dist/commands-CcWIvCA4.mjs +481 -0
- package/dist/commands-DCNO2m66.mjs +471 -0
- package/dist/commands-DRIFvhmC.mjs +481 -0
- package/dist/commands-DnDd4Sew.mjs +481 -0
- package/dist/commands-Do-TVYFm.mjs +481 -0
- package/dist/hyphaClient-DLkclazm.mjs +39 -0
- package/dist/index.mjs +2 -1
- package/dist/package-CC5d8_0L.mjs +57 -0
- package/dist/package-CCJ045H0.mjs +60 -0
- package/dist/package-CS219SXn.mjs +57 -0
- package/dist/package-Cd-9ktpd.mjs +60 -0
- package/dist/package-CgBD49cA.mjs +57 -0
- package/dist/package-CvnNnsm7.mjs +60 -0
- package/dist/package-DpqWz9Cr.mjs +60 -0
- package/dist/package-nzkXV1aM.mjs +57 -0
- package/dist/package-pNo6GC3a.mjs +60 -0
- package/dist/run-BglwnB-A.mjs +3889 -0
- package/dist/run-BjVWuitO.mjs +3919 -0
- package/dist/run-CL-FS4Yc.mjs +3933 -0
- package/dist/run-CW26vPqj.mjs +3919 -0
- package/dist/run-CkTufc0D.mjs +3875 -0
- package/dist/run-Cmostc0S.mjs +3902 -0
- package/dist/run-DYhBROuo.mjs +3934 -0
- package/dist/run-DjfPjgOb.mjs +3904 -0
- package/dist/run-M_SMt96j.mjs +3913 -0
- package/dist/run-h37iSCUB.mjs +3934 -0
- package/dist/run-lpV0oguG.mjs +3897 -0
- package/package.json +7 -4
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-
|
|
1
|
+
import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-DjfPjgOb.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -6,6 +6,7 @@ import 'path';
|
|
|
6
6
|
import 'url';
|
|
7
7
|
import 'child_process';
|
|
8
8
|
import 'crypto';
|
|
9
|
+
import './hyphaClient-DLkclazm.mjs';
|
|
9
10
|
import 'node:crypto';
|
|
10
11
|
import 'node:fs';
|
|
11
12
|
import 'node:path';
|
|
@@ -65,6 +66,12 @@ async function main() {
|
|
|
65
66
|
} else if (daemonSubcommand === "status") {
|
|
66
67
|
daemonStatus();
|
|
67
68
|
process.exit(0);
|
|
69
|
+
} else if (daemonSubcommand === "install") {
|
|
70
|
+
await installDaemonService();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
} else if (daemonSubcommand === "uninstall") {
|
|
73
|
+
await uninstallDaemonService();
|
|
74
|
+
process.exit(0);
|
|
68
75
|
} else {
|
|
69
76
|
printDaemonHelp();
|
|
70
77
|
}
|
|
@@ -75,7 +82,7 @@ async function main() {
|
|
|
75
82
|
} else if (subcommand === "--help" || subcommand === "-h" || !subcommand) {
|
|
76
83
|
printHelp();
|
|
77
84
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
78
|
-
const pkg = await import('./package-
|
|
85
|
+
const pkg = await import('./package-CvnNnsm7.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
79
86
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
80
87
|
} else {
|
|
81
88
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -90,7 +97,7 @@ async function handleAgentCommand() {
|
|
|
90
97
|
return;
|
|
91
98
|
}
|
|
92
99
|
if (agentArgs[0] === "list") {
|
|
93
|
-
const { KNOWN_ACP_AGENTS } = await import('./run-
|
|
100
|
+
const { KNOWN_ACP_AGENTS } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.e; });
|
|
94
101
|
console.log("Known ACP agents:");
|
|
95
102
|
for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
|
|
96
103
|
console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")}`);
|
|
@@ -99,10 +106,10 @@ async function handleAgentCommand() {
|
|
|
99
106
|
console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
|
|
100
107
|
return;
|
|
101
108
|
}
|
|
102
|
-
const { resolveAcpAgentConfig } = await import('./run-
|
|
103
|
-
const { AcpBackend } = await import('./run-
|
|
104
|
-
const { GeminiTransport } = await import('./run-
|
|
105
|
-
const { DefaultTransport } = await import('./run-
|
|
109
|
+
const { resolveAcpAgentConfig } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.e; });
|
|
110
|
+
const { AcpBackend } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.c; });
|
|
111
|
+
const { GeminiTransport } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.G; });
|
|
112
|
+
const { DefaultTransport } = await import('./run-DjfPjgOb.mjs').then(function (n) { return n.D; });
|
|
106
113
|
let cwd = process.cwd();
|
|
107
114
|
const filteredArgs = [];
|
|
108
115
|
for (let i = 0; i < agentArgs.length; i++) {
|
|
@@ -231,7 +238,7 @@ async function handleSessionCommand() {
|
|
|
231
238
|
printSessionHelp();
|
|
232
239
|
return;
|
|
233
240
|
}
|
|
234
|
-
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach } = await import('./commands-
|
|
241
|
+
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach } = await import('./commands-BOeSil-P.mjs');
|
|
235
242
|
if (sessionSubcommand === "list" || sessionSubcommand === "ls") {
|
|
236
243
|
await sessionList();
|
|
237
244
|
} else if (sessionSubcommand === "spawn") {
|
|
@@ -297,16 +304,13 @@ async function loginToHypha() {
|
|
|
297
304
|
}
|
|
298
305
|
const token = await hyphaWebsocketClient.login({
|
|
299
306
|
server_url: serverUrl,
|
|
307
|
+
login_timeout: 120,
|
|
300
308
|
login_callback: (context) => {
|
|
301
309
|
console.log(`
|
|
302
310
|
Please open this URL in your browser:
|
|
303
311
|
${context.login_url}
|
|
304
312
|
`);
|
|
305
|
-
|
|
306
|
-
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
307
|
-
exec(`${cmd} "${context.login_url}"`);
|
|
308
|
-
}).catch(() => {
|
|
309
|
-
});
|
|
313
|
+
console.log("Waiting for you to complete login in the browser...\n");
|
|
310
314
|
}
|
|
311
315
|
});
|
|
312
316
|
if (!token) {
|
|
@@ -314,13 +318,13 @@ Please open this URL in your browser:
|
|
|
314
318
|
process.exit(1);
|
|
315
319
|
}
|
|
316
320
|
const connectToServer = mod.connectToServer || mod.default && mod.default.connectToServer || hyphaWebsocketClient && hyphaWebsocketClient.connectToServer;
|
|
317
|
-
const server = await connectToServer({ server_url: serverUrl, token
|
|
321
|
+
const server = await connectToServer({ server_url: serverUrl, token });
|
|
318
322
|
const workspace = server.config.workspace;
|
|
319
323
|
const userId = server.config.user?.id || workspace;
|
|
320
324
|
let longLivedToken = token;
|
|
321
325
|
try {
|
|
322
326
|
const sixMonthsInSeconds = 6 * 30 * 24 * 60 * 60;
|
|
323
|
-
longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds });
|
|
327
|
+
longLivedToken = await server.generateToken({ expires_in: sixMonthsInSeconds, workspace });
|
|
324
328
|
console.log("Generated long-lived token (expires in ~6 months)");
|
|
325
329
|
} catch (err) {
|
|
326
330
|
console.warn("Could not generate long-lived token, using login token:", err.message || err);
|
|
@@ -364,6 +368,222 @@ You can now start the daemon: svamp daemon start`);
|
|
|
364
368
|
process.exit(1);
|
|
365
369
|
}
|
|
366
370
|
}
|
|
371
|
+
const LAUNCHD_LABEL = "io.hypha.svamp.daemon";
|
|
372
|
+
async function installDaemonService() {
|
|
373
|
+
const os = await import('os');
|
|
374
|
+
const { join, dirname } = await import('path');
|
|
375
|
+
const fs = await import('fs');
|
|
376
|
+
const { execSync } = await import('child_process');
|
|
377
|
+
const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
378
|
+
const logsDir = join(svampHome, "logs");
|
|
379
|
+
let svampBinary = process.argv[1];
|
|
380
|
+
try {
|
|
381
|
+
const resolved = execSync(`which svamp`, { encoding: "utf-8" }).trim();
|
|
382
|
+
if (resolved) svampBinary = resolved;
|
|
383
|
+
} catch {
|
|
384
|
+
}
|
|
385
|
+
if (!fs.existsSync(svampBinary)) {
|
|
386
|
+
console.error(`Cannot find svamp binary at: ${svampBinary}`);
|
|
387
|
+
console.error("Please install svamp-cli globally first.");
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
const nodeBinDir = dirname(process.execPath);
|
|
391
|
+
const shellPath = process.env.PATH || "";
|
|
392
|
+
const supervisedPath = shellPath.includes(nodeBinDir) ? shellPath : [nodeBinDir, shellPath].filter(Boolean).join(":");
|
|
393
|
+
if (!fs.existsSync(logsDir)) fs.mkdirSync(logsDir, { recursive: true });
|
|
394
|
+
if (process.platform === "darwin") {
|
|
395
|
+
const launchAgentsDir = join(os.homedir(), "Library", "LaunchAgents");
|
|
396
|
+
const plistPath = join(launchAgentsDir, `${LAUNCHD_LABEL}.plist`);
|
|
397
|
+
if (!fs.existsSync(launchAgentsDir)) fs.mkdirSync(launchAgentsDir, { recursive: true });
|
|
398
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
399
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
400
|
+
<plist version="1.0">
|
|
401
|
+
<dict>
|
|
402
|
+
<key>Label</key>
|
|
403
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
404
|
+
<key>ProgramArguments</key>
|
|
405
|
+
<array>
|
|
406
|
+
<string>${svampBinary}</string>
|
|
407
|
+
<string>daemon</string>
|
|
408
|
+
<string>start-sync</string>
|
|
409
|
+
</array>
|
|
410
|
+
<key>RunAtLoad</key>
|
|
411
|
+
<true/>
|
|
412
|
+
<key>KeepAlive</key>
|
|
413
|
+
<dict>
|
|
414
|
+
<!-- Only restart on crash (non-zero exit). svamp daemon stop exits 0. -->
|
|
415
|
+
<key>SuccessfulExit</key>
|
|
416
|
+
<false/>
|
|
417
|
+
</dict>
|
|
418
|
+
<key>ThrottleInterval</key>
|
|
419
|
+
<integer>10</integer>
|
|
420
|
+
<key>StandardOutPath</key>
|
|
421
|
+
<string>${logsDir}/daemon-supervised.log</string>
|
|
422
|
+
<key>StandardErrorPath</key>
|
|
423
|
+
<string>${logsDir}/daemon-supervised.log</string>
|
|
424
|
+
<key>EnvironmentVariables</key>
|
|
425
|
+
<dict>
|
|
426
|
+
<key>PATH</key>
|
|
427
|
+
<string>${supervisedPath}</string>
|
|
428
|
+
<key>SVAMP_HOME</key>
|
|
429
|
+
<string>${svampHome}</string>
|
|
430
|
+
<key>SVAMP_SUPERVISED</key>
|
|
431
|
+
<string>1</string>
|
|
432
|
+
</dict>
|
|
433
|
+
</dict>
|
|
434
|
+
</plist>
|
|
435
|
+
`;
|
|
436
|
+
fs.writeFileSync(plistPath, plist, "utf-8");
|
|
437
|
+
console.log(`Plist written: ${plistPath}`);
|
|
438
|
+
try {
|
|
439
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
|
|
440
|
+
} catch {
|
|
441
|
+
}
|
|
442
|
+
execSync(`launchctl load "${plistPath}"`);
|
|
443
|
+
console.log(`
|
|
444
|
+
Svamp daemon service installed and started!`);
|
|
445
|
+
console.log(` Service: ${LAUNCHD_LABEL}`);
|
|
446
|
+
console.log(` Auto-restart on crash: enabled`);
|
|
447
|
+
console.log(` Log file: ${logsDir}/daemon-supervised.log`);
|
|
448
|
+
console.log(`
|
|
449
|
+
To stop the service: svamp daemon stop`);
|
|
450
|
+
console.log(`To uninstall: svamp daemon uninstall`);
|
|
451
|
+
} else if (process.platform === "linux") {
|
|
452
|
+
const hasSystemd = (() => {
|
|
453
|
+
try {
|
|
454
|
+
execSync("systemctl --user status 2>/dev/null", { stdio: "pipe" });
|
|
455
|
+
return true;
|
|
456
|
+
} catch {
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
})();
|
|
460
|
+
if (hasSystemd) {
|
|
461
|
+
const systemdUserDir = join(os.homedir(), ".config", "systemd", "user");
|
|
462
|
+
const unitPath = join(systemdUserDir, "svamp-daemon.service");
|
|
463
|
+
if (!fs.existsSync(systemdUserDir)) fs.mkdirSync(systemdUserDir, { recursive: true });
|
|
464
|
+
const unit = `[Unit]
|
|
465
|
+
Description=Svamp Daemon \u2014 AI workspace on Hypha Cloud
|
|
466
|
+
After=network-online.target
|
|
467
|
+
Wants=network-online.target
|
|
468
|
+
|
|
469
|
+
[Service]
|
|
470
|
+
ExecStart=${svampBinary} daemon start-sync
|
|
471
|
+
Restart=on-failure
|
|
472
|
+
RestartSec=10
|
|
473
|
+
Environment=PATH=${supervisedPath}
|
|
474
|
+
Environment=SVAMP_HOME=${svampHome}
|
|
475
|
+
Environment=SVAMP_SUPERVISED=1
|
|
476
|
+
StandardOutput=append:${logsDir}/daemon-supervised.log
|
|
477
|
+
StandardError=append:${logsDir}/daemon-supervised.log
|
|
478
|
+
|
|
479
|
+
[Install]
|
|
480
|
+
WantedBy=default.target
|
|
481
|
+
`;
|
|
482
|
+
fs.writeFileSync(unitPath, unit, "utf-8");
|
|
483
|
+
execSync("systemctl --user daemon-reload");
|
|
484
|
+
execSync("systemctl --user enable --now svamp-daemon.service");
|
|
485
|
+
console.log(`
|
|
486
|
+
Svamp daemon service installed and started!`);
|
|
487
|
+
console.log(` Service: svamp-daemon.service (systemd user)`);
|
|
488
|
+
console.log(` Unit file: ${unitPath}`);
|
|
489
|
+
console.log(` Auto-restart on crash: enabled`);
|
|
490
|
+
console.log(` Log file: ${logsDir}/daemon-supervised.log`);
|
|
491
|
+
console.log(`
|
|
492
|
+
To stop the service: svamp daemon stop`);
|
|
493
|
+
console.log(`To uninstall: svamp daemon uninstall`);
|
|
494
|
+
} else {
|
|
495
|
+
const wrapperPath = join(svampHome, "daemon-supervisor.sh");
|
|
496
|
+
const wrapper = `#!/bin/sh
|
|
497
|
+
# Svamp daemon supervisor \u2014 restarts on crash. Run this as your container CMD
|
|
498
|
+
# or in a background process. Stop with: svamp daemon stop
|
|
499
|
+
export PATH="${supervisedPath}"
|
|
500
|
+
export SVAMP_HOME="${svampHome}"
|
|
501
|
+
export SVAMP_SUPERVISED=1
|
|
502
|
+
|
|
503
|
+
mkdir -p "${logsDir}"
|
|
504
|
+
echo "[svamp-supervisor] Starting svamp daemon supervisor" >> "${logsDir}/daemon-supervised.log"
|
|
505
|
+
|
|
506
|
+
while true; do
|
|
507
|
+
"${svampBinary}" daemon start-sync >> "${logsDir}/daemon-supervised.log" 2>&1
|
|
508
|
+
EXIT_CODE=$?
|
|
509
|
+
# Exit code 0 = clean stop (svamp daemon stop). Do not restart.
|
|
510
|
+
if [ "$EXIT_CODE" -eq 0 ]; then
|
|
511
|
+
echo "[svamp-supervisor] Daemon stopped cleanly (exit 0). Not restarting." >> "${logsDir}/daemon-supervised.log"
|
|
512
|
+
break
|
|
513
|
+
fi
|
|
514
|
+
echo "[svamp-supervisor] Daemon exited with code $EXIT_CODE. Restarting in 10s..." >> "${logsDir}/daemon-supervised.log"
|
|
515
|
+
sleep 10
|
|
516
|
+
done
|
|
517
|
+
`;
|
|
518
|
+
fs.writeFileSync(wrapperPath, wrapper, { mode: 493 });
|
|
519
|
+
console.log(`
|
|
520
|
+
Svamp daemon supervisor script written!`);
|
|
521
|
+
console.log(` Script: ${wrapperPath}`);
|
|
522
|
+
console.log(` Log file: ${logsDir}/daemon-supervised.log`);
|
|
523
|
+
console.log(`
|
|
524
|
+
To use in Docker, set your container CMD to:`);
|
|
525
|
+
console.log(` ${wrapperPath}`);
|
|
526
|
+
console.log(`
|
|
527
|
+
Or run in the background:`);
|
|
528
|
+
console.log(` nohup ${wrapperPath} &`);
|
|
529
|
+
console.log(`
|
|
530
|
+
To stop the daemon: svamp daemon stop`);
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
console.error(`Daemon service install is not supported on ${process.platform}.`);
|
|
534
|
+
console.error("Supported platforms: macOS (launchd), Linux (systemd or wrapper script).");
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
async function uninstallDaemonService() {
|
|
539
|
+
const os = await import('os');
|
|
540
|
+
const { join } = await import('path');
|
|
541
|
+
const fs = await import('fs');
|
|
542
|
+
const { execSync } = await import('child_process');
|
|
543
|
+
if (process.platform === "darwin") {
|
|
544
|
+
const plistPath = join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
545
|
+
if (!fs.existsSync(plistPath)) {
|
|
546
|
+
console.log("Svamp daemon service is not installed.");
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
execSync(`launchctl unload "${plistPath}"`, { stdio: "pipe" });
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
fs.unlinkSync(plistPath);
|
|
554
|
+
console.log("Svamp daemon service uninstalled (launchd).");
|
|
555
|
+
console.log("The daemon has been stopped and will no longer auto-start.");
|
|
556
|
+
} else if (process.platform === "linux") {
|
|
557
|
+
const unitPath = join(os.homedir(), ".config", "systemd", "user", "svamp-daemon.service");
|
|
558
|
+
const svampHome = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
559
|
+
const wrapperPath = join(svampHome, "daemon-supervisor.sh");
|
|
560
|
+
let removed = false;
|
|
561
|
+
if (fs.existsSync(unitPath)) {
|
|
562
|
+
try {
|
|
563
|
+
execSync("systemctl --user disable --now svamp-daemon.service", { stdio: "pipe" });
|
|
564
|
+
} catch {
|
|
565
|
+
}
|
|
566
|
+
fs.unlinkSync(unitPath);
|
|
567
|
+
try {
|
|
568
|
+
execSync("systemctl --user daemon-reload", { stdio: "pipe" });
|
|
569
|
+
} catch {
|
|
570
|
+
}
|
|
571
|
+
console.log("Svamp daemon service uninstalled (systemd).");
|
|
572
|
+
removed = true;
|
|
573
|
+
}
|
|
574
|
+
if (fs.existsSync(wrapperPath)) {
|
|
575
|
+
fs.unlinkSync(wrapperPath);
|
|
576
|
+
if (!removed) console.log("Svamp daemon supervisor script removed.");
|
|
577
|
+
removed = true;
|
|
578
|
+
}
|
|
579
|
+
if (!removed) {
|
|
580
|
+
console.log("Svamp daemon service is not installed.");
|
|
581
|
+
}
|
|
582
|
+
} else {
|
|
583
|
+
console.error(`Daemon service uninstall is not supported on ${process.platform}.`);
|
|
584
|
+
process.exit(1);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
367
587
|
function printHelp() {
|
|
368
588
|
console.log(`
|
|
369
589
|
svamp \u2014 Svamp CLI with Hypha transport
|
|
@@ -373,6 +593,8 @@ Usage:
|
|
|
373
593
|
svamp daemon start Start the daemon (detached)
|
|
374
594
|
svamp daemon stop Stop the daemon
|
|
375
595
|
svamp daemon status Show daemon status
|
|
596
|
+
svamp daemon install Install as system service (launchd/systemd/wrapper)
|
|
597
|
+
svamp daemon uninstall Remove system service
|
|
376
598
|
svamp session list List active daemon sessions
|
|
377
599
|
svamp session spawn Spawn a new session on the daemon
|
|
378
600
|
svamp session attach <id> Attach to a session (interactive)
|
|
@@ -396,9 +618,11 @@ function printDaemonHelp() {
|
|
|
396
618
|
svamp daemon \u2014 Daemon management
|
|
397
619
|
|
|
398
620
|
Usage:
|
|
399
|
-
svamp daemon start
|
|
400
|
-
svamp daemon stop
|
|
401
|
-
svamp daemon status
|
|
621
|
+
svamp daemon start Start the daemon (detached)
|
|
622
|
+
svamp daemon stop Stop the daemon
|
|
623
|
+
svamp daemon status Show daemon status
|
|
624
|
+
svamp daemon install Install as launchd service (macOS) \u2014 auto-restart on crash
|
|
625
|
+
svamp daemon uninstall Remove launchd service
|
|
402
626
|
`);
|
|
403
627
|
}
|
|
404
628
|
function printSessionHelp() {
|