dsh-deeptutor 0.1.10 → 0.2.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.
- package/.agents/skills/deeptutor/README.md +17 -36
- package/.agents/skills/deeptutor/SKILL.md +14 -14
- package/README.md +35 -26
- package/README.zh.md +14 -12
- package/lib/cli-exec.js +13 -2
- package/lib/config.d.ts +6 -6
- package/lib/config.js +16 -17
- package/lib/index.d.ts +8 -9
- package/lib/index.js +5 -111
- package/lib/turn.d.ts +2 -4
- package/lib/turn.js +4 -95
- package/lib/win-cmd.d.ts +27 -0
- package/lib/win-cmd.js +108 -0
- package/package.json +4 -4
- package/scripts/check-dsh-compat.mjs +191 -0
- package/src/cli-exec.ts +18 -7
- package/src/config.ts +16 -17
- package/src/index.ts +13 -136
- package/src/turn.ts +5 -94
- package/src/win-cmd.ts +129 -0
- package/lib/http-api.d.ts +0 -10
- package/lib/http-api.js +0 -94
- package/scripts/sync-skills.mjs +0 -47
- package/src/http-api.ts +0 -98
package/lib/http-api.js
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepTutor bridge: HTTP/WS transport (API probing, SSH tunnel management, JSON calls).
|
|
3
|
-
*/
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { API_BASE, SSH_HOST, TUNNEL_PORT } from "./config.js";
|
|
6
|
-
let tunnelProc = null;
|
|
7
|
-
/** Kill the SSH tunnel process, if one is running (call on plugin disposal). */
|
|
8
|
-
export function killTunnel() {
|
|
9
|
-
if (tunnelProc && tunnelProc.exitCode === null) {
|
|
10
|
-
try {
|
|
11
|
-
tunnelProc.kill();
|
|
12
|
-
}
|
|
13
|
-
catch { }
|
|
14
|
-
tunnelProc = null;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export async function apiProbe(timeoutMs = 1500) {
|
|
18
|
-
try {
|
|
19
|
-
const res = await fetch(`${API_BASE}/api/v1/system/status`, {
|
|
20
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
21
|
-
});
|
|
22
|
-
return res.ok;
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
/** Ensure the local API is reachable: probe → start SSH tunnel if needed (remote only) → wait. */
|
|
29
|
-
export async function ensureApi() {
|
|
30
|
-
if (await apiProbe(1200))
|
|
31
|
-
return true;
|
|
32
|
-
if (!SSH_HOST)
|
|
33
|
-
return false; // Local deployment: fall back to local CLI when serve is down, no tunnel needed
|
|
34
|
-
let tunnelDead = false;
|
|
35
|
-
if (!tunnelProc || tunnelProc.exitCode !== null) {
|
|
36
|
-
tunnelProc = spawn("ssh", [
|
|
37
|
-
"-N",
|
|
38
|
-
"-L",
|
|
39
|
-
`${TUNNEL_PORT}:127.0.0.1:${TUNNEL_PORT}`,
|
|
40
|
-
"-o",
|
|
41
|
-
"BatchMode=yes",
|
|
42
|
-
"-o",
|
|
43
|
-
"ExitOnForwardFailure=yes",
|
|
44
|
-
"-o",
|
|
45
|
-
"ServerAliveInterval=30",
|
|
46
|
-
"-o",
|
|
47
|
-
"ServerAliveCountMax=3",
|
|
48
|
-
"-o",
|
|
49
|
-
"ConnectTimeout=10",
|
|
50
|
-
SSH_HOST,
|
|
51
|
-
], { stdio: "ignore", windowsHide: true });
|
|
52
|
-
// Fast-fail: a tunnel that exits immediately (bad alias, port in use,
|
|
53
|
-
// refused connection) is not going to recover — stop waiting for it.
|
|
54
|
-
tunnelProc.once("exit", () => {
|
|
55
|
-
tunnelDead = true;
|
|
56
|
-
tunnelProc = null;
|
|
57
|
-
});
|
|
58
|
-
tunnelProc.unref();
|
|
59
|
-
}
|
|
60
|
-
for (let i = 0; i < 20; i++) {
|
|
61
|
-
if (tunnelDead)
|
|
62
|
-
return false;
|
|
63
|
-
if (await apiProbe(1000))
|
|
64
|
-
return true;
|
|
65
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
/** JSON fetch against the DeepTutor API. Combines a fixed timeout with an optional caller-supplied abort signal. */
|
|
70
|
-
export async function apiJson(path, init, opts = {}) {
|
|
71
|
-
const controller = new AbortController();
|
|
72
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 60_000);
|
|
73
|
-
const onAbort = () => controller.abort();
|
|
74
|
-
// An already-aborted signal never fires its "abort" event again — check the
|
|
75
|
-
// flag first so a pre-aborted call fails immediately instead of running to
|
|
76
|
-
// completion against a still-open controller.
|
|
77
|
-
if (opts.signal?.aborted) {
|
|
78
|
-
controller.abort();
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const res = await fetch(`${API_BASE}${path}`, { ...init, signal: controller.signal });
|
|
85
|
-
if (!res.ok)
|
|
86
|
-
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
87
|
-
const text = await res.text();
|
|
88
|
-
return text ? JSON.parse(text) : null;
|
|
89
|
-
}
|
|
90
|
-
finally {
|
|
91
|
-
clearTimeout(timer);
|
|
92
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
93
|
-
}
|
|
94
|
-
}
|
package/scripts/sync-skills.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Copy the shared skill files from a pi-extensions checkout into this repo.
|
|
4
|
-
* The pi repo is the single source of truth; the files must be agent-neutral.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node scripts/sync-skills.mjs [path-to-pi-extensions]
|
|
8
|
-
* Default source: ../pi-extensions
|
|
9
|
-
*/
|
|
10
|
-
import { copyFileSync, existsSync, mkdirSync } from "node:fs";
|
|
11
|
-
import { dirname, join, resolve } from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
|
|
14
|
-
const here = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
-
const piRoot = resolve(process.argv[2] ?? join(here, "..", "pi-extensions"));
|
|
16
|
-
|
|
17
|
-
// [pi source, this-repo destination]
|
|
18
|
-
// The bundle uses the root scripts/ copy at runtime (src/html-render.ts);
|
|
19
|
-
// the skill copy ships inside the package under .agents/skills/. Both must
|
|
20
|
-
// stay identical.
|
|
21
|
-
const FILES = [
|
|
22
|
-
["skills/deeptutor/SKILL.md", ".agents/skills/deeptutor/SKILL.md"],
|
|
23
|
-
["skills/deeptutor/README.md", ".agents/skills/deeptutor/README.md"],
|
|
24
|
-
["skills/html-doc/SKILL.md", ".agents/skills/html-doc/SKILL.md"],
|
|
25
|
-
["skills/html-doc/assets/template.html", ".agents/skills/html-doc/assets/template.html"],
|
|
26
|
-
["skills/html-doc/scripts/md-to-html.js", "scripts/md-to-html.js"],
|
|
27
|
-
["skills/html-doc/scripts/md-to-html.js", ".agents/skills/html-doc/scripts/md-to-html.js"],
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
let failures = 0;
|
|
31
|
-
for (const [src, dst] of FILES) {
|
|
32
|
-
const from = join(piRoot, src);
|
|
33
|
-
const to = join(here, dst);
|
|
34
|
-
if (!existsSync(from)) {
|
|
35
|
-
console.error(`[sync-skills] missing source: ${from}`);
|
|
36
|
-
failures++;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
mkdirSync(dirname(to), { recursive: true });
|
|
40
|
-
copyFileSync(from, to);
|
|
41
|
-
console.log(`[sync-skills] ${src} -> ${dst}`);
|
|
42
|
-
}
|
|
43
|
-
if (failures > 0) {
|
|
44
|
-
console.error("[sync-skills] some sources missing — is the pi-extensions path correct?");
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
console.log("[sync-skills] done — review the diff, then commit.");
|
package/src/http-api.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DeepTutor bridge: HTTP/WS transport (API probing, SSH tunnel management, JSON calls).
|
|
3
|
-
*/
|
|
4
|
-
import { spawn } from "node:child_process";
|
|
5
|
-
import { API_BASE, SSH_HOST, TUNNEL_PORT } from "./config.ts";
|
|
6
|
-
|
|
7
|
-
let tunnelProc: ReturnType<typeof spawn> | null = null;
|
|
8
|
-
|
|
9
|
-
/** Kill the SSH tunnel process, if one is running (call on plugin disposal). */
|
|
10
|
-
export function killTunnel(): void {
|
|
11
|
-
if (tunnelProc && tunnelProc.exitCode === null) {
|
|
12
|
-
try {
|
|
13
|
-
tunnelProc.kill();
|
|
14
|
-
} catch {}
|
|
15
|
-
tunnelProc = null;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function apiProbe(timeoutMs = 1500): Promise<boolean> {
|
|
20
|
-
try {
|
|
21
|
-
const res = await fetch(`${API_BASE}/api/v1/system/status`, {
|
|
22
|
-
signal: AbortSignal.timeout(timeoutMs),
|
|
23
|
-
});
|
|
24
|
-
return res.ok;
|
|
25
|
-
} catch {
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/** Ensure the local API is reachable: probe → start SSH tunnel if needed (remote only) → wait. */
|
|
31
|
-
export async function ensureApi(): Promise<boolean> {
|
|
32
|
-
if (await apiProbe(1200)) return true;
|
|
33
|
-
if (!SSH_HOST) return false; // Local deployment: fall back to local CLI when serve is down, no tunnel needed
|
|
34
|
-
let tunnelDead = false;
|
|
35
|
-
if (!tunnelProc || tunnelProc.exitCode !== null) {
|
|
36
|
-
tunnelProc = spawn(
|
|
37
|
-
"ssh",
|
|
38
|
-
[
|
|
39
|
-
"-N",
|
|
40
|
-
"-L",
|
|
41
|
-
`${TUNNEL_PORT}:127.0.0.1:${TUNNEL_PORT}`,
|
|
42
|
-
"-o",
|
|
43
|
-
"BatchMode=yes",
|
|
44
|
-
"-o",
|
|
45
|
-
"ExitOnForwardFailure=yes",
|
|
46
|
-
"-o",
|
|
47
|
-
"ServerAliveInterval=30",
|
|
48
|
-
"-o",
|
|
49
|
-
"ServerAliveCountMax=3",
|
|
50
|
-
"-o",
|
|
51
|
-
"ConnectTimeout=10",
|
|
52
|
-
SSH_HOST,
|
|
53
|
-
],
|
|
54
|
-
{ stdio: "ignore", windowsHide: true },
|
|
55
|
-
);
|
|
56
|
-
// Fast-fail: a tunnel that exits immediately (bad alias, port in use,
|
|
57
|
-
// refused connection) is not going to recover — stop waiting for it.
|
|
58
|
-
tunnelProc.once("exit", () => {
|
|
59
|
-
tunnelDead = true;
|
|
60
|
-
tunnelProc = null;
|
|
61
|
-
});
|
|
62
|
-
tunnelProc.unref();
|
|
63
|
-
}
|
|
64
|
-
for (let i = 0; i < 20; i++) {
|
|
65
|
-
if (tunnelDead) return false;
|
|
66
|
-
if (await apiProbe(1000)) return true;
|
|
67
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
68
|
-
}
|
|
69
|
-
return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** JSON fetch against the DeepTutor API. Combines a fixed timeout with an optional caller-supplied abort signal. */
|
|
73
|
-
export async function apiJson(
|
|
74
|
-
path: string,
|
|
75
|
-
init?: RequestInit,
|
|
76
|
-
opts: { timeoutMs?: number; signal?: AbortSignal } = {},
|
|
77
|
-
): Promise<any> {
|
|
78
|
-
const controller = new AbortController();
|
|
79
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 60_000);
|
|
80
|
-
const onAbort = () => controller.abort();
|
|
81
|
-
// An already-aborted signal never fires its "abort" event again — check the
|
|
82
|
-
// flag first so a pre-aborted call fails immediately instead of running to
|
|
83
|
-
// completion against a still-open controller.
|
|
84
|
-
if (opts.signal?.aborted) {
|
|
85
|
-
controller.abort();
|
|
86
|
-
} else {
|
|
87
|
-
opts.signal?.addEventListener("abort", onAbort, { once: true });
|
|
88
|
-
}
|
|
89
|
-
try {
|
|
90
|
-
const res = await fetch(`${API_BASE}${path}`, { ...init, signal: controller.signal });
|
|
91
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
92
|
-
const text = await res.text();
|
|
93
|
-
return text ? JSON.parse(text) : null;
|
|
94
|
-
} finally {
|
|
95
|
-
clearTimeout(timer);
|
|
96
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
97
|
-
}
|
|
98
|
-
}
|