dsh-deeptutor 0.1.5 → 0.1.7
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 +4 -0
- package/README.zh.md +2 -0
- package/lib/config.js +1 -0
- package/lib/http-api.js +18 -1
- package/lib/index.js +33 -14
- package/lib/turn.d.ts +10 -2
- package/lib/turn.js +27 -7
- package/package.json +6 -2
- package/scripts/md-to-html.js +35 -7
- package/scripts/sync-skills.mjs +46 -0
- package/skills/deeptutor/README.md +37 -14
- package/skills/deeptutor/SKILL.md +12 -11
- package/skills/html-doc/SKILL.md +2 -2
- package/skills/html-doc/assets/template.html +420 -420
- package/skills/html-doc/scripts/md-to-html.js +35 -7
- package/src/config.ts +1 -0
- package/src/http-api.ts +16 -1
- package/src/index.ts +32 -13
- package/src/turn.ts +33 -10
package/README.md
CHANGED
|
@@ -143,6 +143,10 @@ Two skills ship inside this package (`skills/deeptutor` and
|
|
|
143
143
|
Installing a newer package version overwrites skill files in place; files not
|
|
144
144
|
shipped by the package are never deleted.
|
|
145
145
|
|
|
146
|
+
The files under `skills/` are byte-identical copies of the same skills in
|
|
147
|
+
`TecFancy/pi-extensions` (single source of truth, agent-neutral). Sync them
|
|
148
|
+
after upstream edits with `node scripts/sync-skills.mjs ../pi-extensions`.
|
|
149
|
+
|
|
146
150
|
## Configuration (env vars, agent-agnostic)
|
|
147
151
|
|
|
148
152
|
```bash
|
package/README.zh.md
CHANGED
package/lib/config.js
CHANGED
|
@@ -44,6 +44,7 @@ export function configGuide() {
|
|
|
44
44
|
' export DEEPTUTOR_REMOTE_HOME="remote-DEEPTUTOR_HOME-workspace"\n' +
|
|
45
45
|
" Verify serve is running on the server (ssh to it: curl http://127.0.0.1:8001/api/v1/system/status)\n" +
|
|
46
46
|
" Verify passwordless SSH login works (ssh alias + BatchMode)\n" +
|
|
47
|
+
'PowerShell users: use $env:VAR="value" instead of export VAR=value.\n' +
|
|
47
48
|
"Restart the agent after configuring.\n");
|
|
48
49
|
}
|
|
49
50
|
export const CAPABILITIES = [
|
package/lib/http-api.js
CHANGED
|
@@ -31,6 +31,7 @@ export async function ensureApi() {
|
|
|
31
31
|
return true;
|
|
32
32
|
if (!SSH_HOST)
|
|
33
33
|
return false; // Local deployment: fall back to local CLI when serve is down, no tunnel needed
|
|
34
|
+
let tunnelDead = false;
|
|
34
35
|
if (!tunnelProc || tunnelProc.exitCode !== null) {
|
|
35
36
|
tunnelProc = spawn("ssh", [
|
|
36
37
|
"-N",
|
|
@@ -48,9 +49,17 @@ export async function ensureApi() {
|
|
|
48
49
|
"ConnectTimeout=10",
|
|
49
50
|
SSH_HOST,
|
|
50
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
|
+
});
|
|
51
58
|
tunnelProc.unref();
|
|
52
59
|
}
|
|
53
60
|
for (let i = 0; i < 20; i++) {
|
|
61
|
+
if (tunnelDead)
|
|
62
|
+
return false;
|
|
54
63
|
if (await apiProbe(1000))
|
|
55
64
|
return true;
|
|
56
65
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -62,7 +71,15 @@ export async function apiJson(path, init, opts = {}) {
|
|
|
62
71
|
const controller = new AbortController();
|
|
63
72
|
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 60_000);
|
|
64
73
|
const onAbort = () => controller.abort();
|
|
65
|
-
|
|
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
|
+
}
|
|
66
83
|
try {
|
|
67
84
|
const res = await fetch(`${API_BASE}${path}`, { ...init, signal: controller.signal });
|
|
68
85
|
if (!res.ok)
|
package/lib/index.js
CHANGED
|
@@ -127,6 +127,10 @@ export function apply(ctx) {
|
|
|
127
127
|
});
|
|
128
128
|
}
|
|
129
129
|
catch (err) {
|
|
130
|
+
if (exec.signal.aborted) {
|
|
131
|
+
// user cancelled mid-CLI-run: report it as a cancellation, not a failure
|
|
132
|
+
throw new Error("[deeptutor_run cancelled]");
|
|
133
|
+
}
|
|
130
134
|
throw new Error(configGuide() +
|
|
131
135
|
`[deeptutor_run failed (HTTP and CLI both unavailable)] ${err?.message ?? String(err)}`);
|
|
132
136
|
}
|
|
@@ -158,7 +162,7 @@ export function apply(ctx) {
|
|
|
158
162
|
},
|
|
159
163
|
top_k: {
|
|
160
164
|
type: "number",
|
|
161
|
-
description: "Number of hits to return, default 5",
|
|
165
|
+
description: "Number of hits to return, default 5 (applied locally to the CLI search result; the deeptutor CLI has no --top-k flag)",
|
|
162
166
|
},
|
|
163
167
|
max_chars: {
|
|
164
168
|
type: "number",
|
|
@@ -215,7 +219,8 @@ export function apply(ctx) {
|
|
|
215
219
|
console.log(`[deeptutor] kb ${args.action} HTTP failed, falling back to CLI: ${err?.message}`);
|
|
216
220
|
}
|
|
217
221
|
}
|
|
218
|
-
// CLI fallback (local or SSH)
|
|
222
|
+
// CLI fallback (local or SSH). NOTE: `kb search` has no --top-k flag
|
|
223
|
+
// (verified on deeptutor CLI 1.5.x); top_k is applied locally below.
|
|
219
224
|
let argv;
|
|
220
225
|
if (args.action === "list") {
|
|
221
226
|
argv = ["kb", "list", "--format", "json"];
|
|
@@ -234,8 +239,6 @@ export function apply(ctx) {
|
|
|
234
239
|
"--mode",
|
|
235
240
|
args.mode ?? "hybrid",
|
|
236
241
|
];
|
|
237
|
-
if (args.top_k)
|
|
238
|
-
argv.push("--top-k", String(args.top_k));
|
|
239
242
|
}
|
|
240
243
|
const { stdout, stderr, code, killed } = await runCli(argv, {
|
|
241
244
|
signal: exec.signal,
|
|
@@ -250,16 +253,32 @@ export function apply(ctx) {
|
|
|
250
253
|
let body = stdout.trim() || "(empty result)";
|
|
251
254
|
if (args.action === "search") {
|
|
252
255
|
try {
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
256
|
+
const parsed = JSON.parse(stdout);
|
|
257
|
+
const fmtHit = (s, i) => {
|
|
258
|
+
const text = s.text ?? s.content ?? s.snippet ?? JSON.stringify(s);
|
|
259
|
+
const src = s.source ?? s.doc_name ?? s.file ?? "";
|
|
260
|
+
const title = s.title ? ` title: ${s.title}` : "";
|
|
261
|
+
const score = typeof s.score === "number" ? ` score: ${s.score}` : "";
|
|
262
|
+
return `[${i + 1}]${src ? ` source: ${src}` : ""}${title}${score}\n${typeof text === "string" ? text : JSON.stringify(text)}`;
|
|
263
|
+
};
|
|
264
|
+
// Current deeptutor CLI (1.5.x) returns an object
|
|
265
|
+
// { query, answer, content, sources, provider }; older builds
|
|
266
|
+
// returned a bare array of hits. Accept both.
|
|
267
|
+
if (Array.isArray(parsed)) {
|
|
268
|
+
const hits = args.top_k ? parsed.slice(0, args.top_k) : parsed;
|
|
269
|
+
body = `${hits.length} hits:\n\n` + hits.map(fmtHit).join("\n\n");
|
|
270
|
+
}
|
|
271
|
+
else if (parsed && typeof parsed === "object") {
|
|
272
|
+
const parts = [];
|
|
273
|
+
if (parsed.query)
|
|
274
|
+
parts.push(`Query: ${parsed.query}`);
|
|
275
|
+
if (typeof parsed.answer === "string" && parsed.answer.trim())
|
|
276
|
+
parts.push(`Answer: ${parsed.answer.trim()}`);
|
|
277
|
+
const sources = Array.isArray(parsed.sources) ? parsed.sources : [];
|
|
278
|
+
const hits = args.top_k ? sources.slice(0, args.top_k) : sources;
|
|
279
|
+
parts.push(`${hits.length} hits:`);
|
|
280
|
+
parts.push(hits.map(fmtHit).join("\n\n"));
|
|
281
|
+
body = parts.join("\n\n");
|
|
263
282
|
}
|
|
264
283
|
}
|
|
265
284
|
catch {
|
package/lib/turn.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runCli } from "./cli-exec.ts";
|
|
1
2
|
export interface TurnResult {
|
|
2
3
|
answer: string;
|
|
3
4
|
errors: string[];
|
|
@@ -19,6 +20,13 @@ export interface RunParams {
|
|
|
19
20
|
export declare function collectEvent(state: TurnResult, e: any): void;
|
|
20
21
|
/** Execute one turn over WebSocket (streaming). */
|
|
21
22
|
export declare function wsRunTurn(params: RunParams, timeoutMs: number, signal?: AbortSignal): Promise<TurnResult>;
|
|
22
|
-
/**
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Build the `deeptutor run` argv for a capability turn (pure, unit-tested).
|
|
25
|
+
* Scalar config values go as repeatable --config k=v; object/array values are
|
|
26
|
+
* collected into --config-json (the CLI's documented form for complex config)
|
|
27
|
+
* instead of being stringified into a k=<json> value that no parser expects.
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildRunArgs(params: RunParams): string[];
|
|
30
|
+
/** Execute one turn via the CLI (local or SSH). `run` is injectable for tests. */
|
|
31
|
+
export declare function cliRunTurn(params: RunParams, timeoutMs: number, signal?: AbortSignal, run?: typeof runCli): Promise<TurnResult>;
|
|
24
32
|
export declare const fmtTurnResult: (capability: string, r: TurnResult, maxChars: number) => string;
|
package/lib/turn.js
CHANGED
|
@@ -110,8 +110,13 @@ export function wsRunTurn(params, timeoutMs, signal) {
|
|
|
110
110
|
};
|
|
111
111
|
});
|
|
112
112
|
}
|
|
113
|
-
/**
|
|
114
|
-
|
|
113
|
+
/**
|
|
114
|
+
* Build the `deeptutor run` argv for a capability turn (pure, unit-tested).
|
|
115
|
+
* Scalar config values go as repeatable --config k=v; object/array values are
|
|
116
|
+
* collected into --config-json (the CLI's documented form for complex config)
|
|
117
|
+
* instead of being stringified into a k=<json> value that no parser expects.
|
|
118
|
+
*/
|
|
119
|
+
export function buildRunArgs(params) {
|
|
115
120
|
const argv = ["run", params.capability, params.prompt, "--format", "json"];
|
|
116
121
|
for (const kb of params.kbs ?? [])
|
|
117
122
|
argv.push("--kb", kb);
|
|
@@ -121,11 +126,26 @@ export async function cliRunTurn(params, timeoutMs, signal) {
|
|
|
121
126
|
argv.push("--session", params.session_id);
|
|
122
127
|
if (params.language)
|
|
123
128
|
argv.push("--language", params.language);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
const complexConfig = {};
|
|
130
|
+
for (const [k, v] of Object.entries(params.config ?? {})) {
|
|
131
|
+
if (typeof v === "object" && v !== null)
|
|
132
|
+
complexConfig[k] = v;
|
|
133
|
+
else
|
|
134
|
+
argv.push("--config", `${k}=${v}`);
|
|
135
|
+
}
|
|
136
|
+
if (Object.keys(complexConfig).length > 0) {
|
|
137
|
+
argv.push("--config-json", JSON.stringify(complexConfig));
|
|
138
|
+
}
|
|
139
|
+
return argv;
|
|
140
|
+
}
|
|
141
|
+
/** Execute one turn via the CLI (local or SSH). `run` is injectable for tests. */
|
|
142
|
+
export async function cliRunTurn(params, timeoutMs, signal, run = runCli) {
|
|
143
|
+
const { stdout, stderr, code, killed } = await run(buildRunArgs(params), { signal, timeoutMs });
|
|
144
|
+
if (killed) {
|
|
145
|
+
// A kill can come from the user cancelling (signal) or from the timeout;
|
|
146
|
+
// report the real reason instead of always blaming the timeout.
|
|
147
|
+
throw new Error(signal?.aborted ? "Aborted" : `CLI execution timed out (${Math.round(timeoutMs / 1000)}s)`);
|
|
148
|
+
}
|
|
129
149
|
if (code !== 0)
|
|
130
150
|
throw new Error(`CLI exit=${code}: ${stderr || "(no stderr)"}`);
|
|
131
151
|
const state = { answer: "", errors: [], toolCalls: [] };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deeptutor",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "DeepTutor bridge bundle for DeepSeek Harness: learning capabilities, knowledge bases, and note archiving (HTTP/WS first, CLI fallback; auto-adapts local/remote deployment).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"agent"
|
|
13
13
|
],
|
|
14
14
|
"license": "MIT",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20.11"
|
|
17
|
+
},
|
|
15
18
|
"type": "module",
|
|
16
19
|
"main": "./lib/index.js",
|
|
17
20
|
"types": "./lib/index.d.ts",
|
|
@@ -47,7 +50,8 @@
|
|
|
47
50
|
"build": "tsc -p tsconfig.build.json",
|
|
48
51
|
"typecheck": "tsc --noEmit",
|
|
49
52
|
"prepack": "npm run build && npm run typecheck",
|
|
50
|
-
"pack:check": "npm pack --dry-run"
|
|
53
|
+
"pack:check": "npm pack --dry-run",
|
|
54
|
+
"test": "node --test --experimental-strip-types"
|
|
51
55
|
},
|
|
52
56
|
"peerDependencies": {
|
|
53
57
|
"@deepseek-ai/cordis": "^4.0.1",
|
package/scripts/md-to-html.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Features: headings, fenced code blocks, inline code/bold/italic/links, lists,
|
|
11
11
|
* task lists, blockquotes, pipe tables, hr, quiz answer collapsing (<details>),
|
|
12
|
-
* raw HTML passthrough, HTML escaping. Zero npm dependencies (Node >=
|
|
12
|
+
* raw HTML passthrough, HTML escaping. Zero npm dependencies (Node >= 20.11 — uses import.meta.dirname).
|
|
13
13
|
*/
|
|
14
14
|
"use strict";
|
|
15
15
|
|
|
@@ -29,22 +29,41 @@ function parseArgs(argv) {
|
|
|
29
29
|
collapse: true,
|
|
30
30
|
};
|
|
31
31
|
const positional = [];
|
|
32
|
+
const valueFlags = new Set(["--out", "--title", "--theme", "--lang"]);
|
|
32
33
|
for (let i = 0; i < argv.length; i++) {
|
|
33
34
|
const a = argv[i];
|
|
34
|
-
if (a === "--
|
|
35
|
-
else if (a === "--title") opts.title = argv[++i];
|
|
36
|
-
else if (a === "--theme") opts.theme = argv[++i] === "dark" ? "dark" : "light";
|
|
37
|
-
else if (a === "--toc") opts.toc = true;
|
|
38
|
-
else if (a === "--lang") opts.lang = argv[++i];
|
|
35
|
+
if (a === "--toc") opts.toc = true;
|
|
39
36
|
else if (a === "--no-collapse") opts.collapse = false;
|
|
40
37
|
else if (a === "--help" || a === "-h") {
|
|
41
38
|
printHelp();
|
|
42
39
|
process.exit(0);
|
|
40
|
+
} else if (valueFlags.has(a)) {
|
|
41
|
+
const value = argv[i + 1];
|
|
42
|
+
if (value === undefined || value.startsWith("--")) {
|
|
43
|
+
fail(`Missing value for ${a} (e.g. --out out.html)`);
|
|
44
|
+
}
|
|
45
|
+
i++;
|
|
46
|
+
if (a === "--out") opts.out = value;
|
|
47
|
+
else if (a === "--title") opts.title = value;
|
|
48
|
+
else if (a === "--theme") {
|
|
49
|
+
if (value !== "dark" && value !== "light") {
|
|
50
|
+
fail(`Invalid --theme value "${value}": expected dark or light`);
|
|
51
|
+
}
|
|
52
|
+
opts.theme = value;
|
|
53
|
+
} else if (a === "--lang") opts.lang = value;
|
|
54
|
+
} else if (a.startsWith("--")) {
|
|
55
|
+
fail(`Unknown option: ${a} (run with --help for usage)`);
|
|
43
56
|
} else positional.push(a);
|
|
44
57
|
}
|
|
45
58
|
return { opts, positional };
|
|
46
59
|
}
|
|
47
60
|
|
|
61
|
+
/** Print an error to stderr and exit non-zero (CLI contract: never silently swallow bad flags). */
|
|
62
|
+
function fail(message) {
|
|
63
|
+
console.error(`[html-doc] ${message}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
function printHelp() {
|
|
49
68
|
console.log(`md-to-html.js — Markdown → self-contained HTML
|
|
50
69
|
|
|
@@ -330,7 +349,16 @@ function main() {
|
|
|
330
349
|
: "";
|
|
331
350
|
|
|
332
351
|
const generated = new Date().toISOString().slice(0, 10);
|
|
333
|
-
|
|
352
|
+
// The same converter ships in two locations (skills/html-doc/scripts/ and a
|
|
353
|
+
// package-root scripts/ copy); resolve the template wherever we are.
|
|
354
|
+
const templateCandidates = [
|
|
355
|
+
path.join(import.meta.dirname, "..", "assets", "template.html"),
|
|
356
|
+
path.join(import.meta.dirname, "..", "skills", "html-doc", "assets", "template.html"),
|
|
357
|
+
];
|
|
358
|
+
const templatePath = templateCandidates.find((p) => fs.existsSync(p));
|
|
359
|
+
if (!templatePath) {
|
|
360
|
+
fail(`template.html not found (looked in: ${templateCandidates.join(", ")})`);
|
|
361
|
+
}
|
|
334
362
|
let template = fs.readFileSync(templatePath, "utf8");
|
|
335
363
|
template = template
|
|
336
364
|
.replace(/\{\{TITLE\}\}/g, esc(title))
|
|
@@ -0,0 +1,46 @@
|
|
|
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. Both must stay identical.
|
|
20
|
+
const FILES = [
|
|
21
|
+
["skills/deeptutor/SKILL.md", "skills/deeptutor/SKILL.md"],
|
|
22
|
+
["skills/deeptutor/README.md", "skills/deeptutor/README.md"],
|
|
23
|
+
["skills/html-doc/SKILL.md", "skills/html-doc/SKILL.md"],
|
|
24
|
+
["skills/html-doc/assets/template.html", "skills/html-doc/assets/template.html"],
|
|
25
|
+
["skills/html-doc/scripts/md-to-html.js", "scripts/md-to-html.js"],
|
|
26
|
+
["skills/html-doc/scripts/md-to-html.js", "skills/html-doc/scripts/md-to-html.js"],
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
let failures = 0;
|
|
30
|
+
for (const [src, dst] of FILES) {
|
|
31
|
+
const from = join(piRoot, src);
|
|
32
|
+
const to = join(here, dst);
|
|
33
|
+
if (!existsSync(from)) {
|
|
34
|
+
console.error(`[sync-skills] missing source: ${from}`);
|
|
35
|
+
failures++;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
mkdirSync(dirname(to), { recursive: true });
|
|
39
|
+
copyFileSync(from, to);
|
|
40
|
+
console.log(`[sync-skills] ${src} -> ${dst}`);
|
|
41
|
+
}
|
|
42
|
+
if (failures > 0) {
|
|
43
|
+
console.error("[sync-skills] some sources missing — is the pi-extensions path correct?");
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log("[sync-skills] done — review the diff, then commit.");
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
# DeepTutor Learning Assistant (
|
|
1
|
+
# DeepTutor Learning Assistant (extension / bundle + SKILL)
|
|
2
2
|
|
|
3
3
|
> This README is the human-facing documentation; `SKILL.md` is the agent-facing instruction file (loaded on demand).
|
|
4
|
-
>
|
|
4
|
+
> The same code ships in two ecosystems — edit in `pi-extensions` first, then sync to `dsh-deeptutor` (see Maintenance).
|
|
5
5
|
|
|
6
|
-
Bridges **DeepTutor** (open-source personalized tutoring platform) into
|
|
6
|
+
Bridges **DeepTutor** (open-source personalized tutoring platform) into your
|
|
7
|
+
agent: combines your **personal knowledge bases** (RAG retrieval) with
|
|
8
|
+
**authoritative web sources** to generate learning content, self-test
|
|
9
|
+
questions, and study plans — then archives your notes to a DeepTutor notebook
|
|
10
|
+
for later review.
|
|
11
|
+
|
|
12
|
+
- **pi** — extension `extensions/deeptutor/` (entry: `index.ts`) + skill `skills/deeptutor/SKILL.md` (repo: `TecFancy/pi-extensions`)
|
|
13
|
+
- **dsh** — npm bundle `dsh-deeptutor` (the same TypeScript core) + the same skill, installed to `~/.dsh/skills/deeptutor/` (repo: `TecFancy/dsh-deeptutor`)
|
|
7
14
|
|
|
8
15
|
## Features
|
|
9
16
|
|
|
@@ -18,10 +25,17 @@ Bridges **DeepTutor** (open-source personalized tutoring platform) into dsh: com
|
|
|
18
25
|
|
|
19
26
|
## Installation
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
### pi
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Install the pi package (extension + SKILL)
|
|
32
|
+
pi install git:github.com/TecFancy/pi-extensions
|
|
33
|
+
|
|
34
|
+
# Local development: /reload after editing (local path installs are not copied)
|
|
35
|
+
pi install D:/source/personal/pi-extensions
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### dsh
|
|
25
39
|
|
|
26
40
|
```bash
|
|
27
41
|
# One-shot: bundle + skill, from the published package
|
|
@@ -81,26 +95,35 @@ The agent follows the SKILL workflow automatically:
|
|
|
81
95
|
| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
82
96
|
| Connection timeout / host resolution failure | ① Check `DEEPTUTOR_SSH_HOST` is set and SSH passwordless login works ② Is `deeptutor serve` running? (`curl http://127.0.0.1:8001/api/v1/system/status`) |
|
|
83
97
|
| Tool returns "No DEEPTUTOR_* env vars detected" | One-time hint on first call; configure per the Configuration section and restart the agent |
|
|
84
|
-
| Local CLI garbled output / crashes | The
|
|
98
|
+
| Local CLI garbled output / crashes | The tools inject `PYTHONUTF8=1` automatically; verify Python ≥ 3.11 otherwise |
|
|
85
99
|
| DNS resolution fails (Windows) | Common on corporate networks: if `nslookup` works but curl/python can't resolve, fix `C:\Windows\System32\drivers\etc\hosts` (when appending entries, always start with a newline) |
|
|
86
100
|
| CLI-only package missing modules | Official `packaging/deeptutor-cli` has incomplete deps; `pip install <module>` per the error (known: loguru, fastapi) |
|
|
87
101
|
|
|
88
102
|
## FAQ
|
|
89
103
|
|
|
90
104
|
**Q: Can local and remote be used at the same time?**
|
|
91
|
-
A: Yes. Switching only requires changing env vars (set/remove `DEEPTUTOR_SSH_HOST`); the SKILL and
|
|
105
|
+
A: Yes. Switching only requires changing env vars (set/remove `DEEPTUTOR_SSH_HOST`); the SKILL and tools stay unchanged.
|
|
92
106
|
|
|
93
107
|
**Q: What if I change servers or ports?**
|
|
94
|
-
A: Only env vars change (`DEEPTUTOR_SSH_HOST` / `DEEPTUTOR_API_BASE` / remote paths) — never the SKILL or
|
|
108
|
+
A: Only env vars change (`DEEPTUTOR_SSH_HOST` / `DEEPTUTOR_API_BASE` / remote paths) — never the SKILL or tools.
|
|
95
109
|
|
|
96
110
|
**Q: Is the knowledge base list hardcoded?**
|
|
97
111
|
A: No. Tools query `deeptutor_kb list` dynamically; new bases are picked up automatically.
|
|
98
112
|
|
|
99
113
|
**Q: Can other agents (Claude Code / Codex) use it?**
|
|
100
|
-
A: Yes. The SKILL follows the Agent Skills standard and can be linked into `~/.agents/skills/`; the tools
|
|
114
|
+
A: Yes. The SKILL follows the Agent Skills standard and can be linked into `~/.agents/skills/`; the `deeptutor_*` tools are registered by the pi extension or the dsh bundle.
|
|
101
115
|
|
|
102
116
|
## Maintenance
|
|
103
117
|
|
|
104
|
-
|
|
105
|
-
-
|
|
106
|
-
-
|
|
118
|
+
Shared skill files (`skills/deeptutor/*`, `skills/html-doc/*`) live **in this
|
|
119
|
+
repo (pi-extensions)** and are kept agent-neutral. `dsh-deeptutor` ships
|
|
120
|
+
byte-identical copies; after editing, sync with:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
cd ../dsh-deeptutor && node scripts/sync-skills.mjs ../pi-extensions
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
- pi code: edit the relevant file under `extensions/deeptutor/` (`index.ts` for tool wiring, `cli-exec.ts`/`http-api.ts`/`turn.ts` for transport logic, `config.ts` for env vars/helpers) → `/reload` to verify
|
|
127
|
+
- dsh code: same files under `dsh-deeptutor/src/` → rebuild with `npm run build`
|
|
128
|
+
- Release pi: push a tag to GitHub, then `pi install git:github.com/TecFancy/pi-extensions@<tag>`
|
|
129
|
+
- Release dsh: bump the version in `package.json`, push a `v*` tag → CI publishes to npm
|
|
@@ -12,7 +12,7 @@ questions, and study plans, then archives notes to a DeepTutor notebook.
|
|
|
12
12
|
This skill is **agent-agnostic**: every action below has two equivalent
|
|
13
13
|
drivers —
|
|
14
14
|
|
|
15
|
-
- **dsh (with the dsh-deeptutor bundle installed):** the `deeptutor_*` tools.
|
|
15
|
+
- **pi / dsh (with the pi deeptutor extension or the `dsh-deeptutor` bundle installed):** the `deeptutor_*` tools.
|
|
16
16
|
They auto-adapt local/remote deployment (HTTP/WS first with an automatic
|
|
17
17
|
SSH tunnel, CLI fallback) and stream progress.
|
|
18
18
|
- **Any agent / plain CLI:** the `deeptutor` executable directly (local
|
|
@@ -30,7 +30,7 @@ what matters.
|
|
|
30
30
|
|
|
31
31
|
## Configuration (env vars, agent-agnostic)
|
|
32
32
|
|
|
33
|
-
Nothing is hardcoded in this skill; all server parameters come from environment variables (put them in `~/.bashrc` or `~/.zshrc`; any agent — dsh / Claude Code / Codex / opencode — inherits them at startup). **Deployment mode adapts automatically**:
|
|
33
|
+
Nothing is hardcoded in this skill; all server parameters come from environment variables (put them in `~/.bashrc` or `~/.zshrc`; any agent — pi / dsh / Claude Code / Codex / opencode — inherits them at startup). **Deployment mode adapts automatically**:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
# Remote deployment (DeepTutor on a server, reached through an SSH tunnel)
|
|
@@ -44,8 +44,8 @@ export DEEPTUTOR_REMOTE_HOME="/home/ubuntu/my-deeptutor"
|
|
|
44
44
|
# export DEEPTUTOR_LOCAL_BIN="deeptutor" # local CLI path (default: deeptutor on PATH)
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
- **
|
|
48
|
-
local CLI or SSH CLI fallback automatically. The bundle starts/recycles
|
|
47
|
+
- **Tools path:** API reachable (local serve or tunnel) → HTTP/WS; unreachable →
|
|
48
|
+
local CLI or SSH CLI fallback automatically. The extension/bundle starts/recycles
|
|
49
49
|
the SSH tunnel; local mode needs no tunnel.
|
|
50
50
|
- **Plain CLI path:** run `deeptutor <subcommand>` locally, or
|
|
51
51
|
`ssh <DEEPTUTOR_SSH_HOST> <DEEPTUTOR_REMOTE_BIN> <subcommand>` for remote
|
|
@@ -54,7 +54,7 @@ export DEEPTUTOR_REMOTE_HOME="/home/ubuntu/my-deeptutor"
|
|
|
54
54
|
|
|
55
55
|
## Actions Cheat Sheet
|
|
56
56
|
|
|
57
|
-
| Action |
|
|
57
|
+
| Action | Tools (`deeptutor_*`) | CLI equivalent (`deeptutor …`) |
|
|
58
58
|
| ---------------------------- | -------------------------------- | ------------------------------------------------------------ |
|
|
59
59
|
| List knowledge bases | `deeptutor_kb action=list` | `kb list --format json` |
|
|
60
60
|
| Search a knowledge base | `deeptutor_kb action=search` | `kb search <kb> "<query>" --format json --mode hybrid` |
|
|
@@ -96,13 +96,13 @@ in `--help`, report that to the user instead of guessing.
|
|
|
96
96
|
|
|
97
97
|
- `web_search` for **authoritative sources**: official docs (Microsoft Learn / learn.microsoft.com), official blogs, trusted tutorials. Keep the source links.
|
|
98
98
|
- Search the user's personal knowledge base (pick the right one: dotnet-related → their dotnet base, database → sqlserver) to get their own curated notes:
|
|
99
|
-
-
|
|
99
|
+
- Tools: `deeptutor_kb action=search kb=<kb> query="<topic>"`
|
|
100
100
|
- CLI: `deeptutor kb search <kb> "<topic>" --format json`
|
|
101
101
|
- Always list bases first (`deeptutor_kb action=list` / `deeptutor kb list --format json`) — never assume names.
|
|
102
102
|
|
|
103
103
|
### 2. Deep Solve
|
|
104
104
|
|
|
105
|
-
-
|
|
105
|
+
- Tools: `deeptutor_run capability=deep_solve prompt="<topic>" kbs=[<kb>] tools=[rag, web_search] language=zh`
|
|
106
106
|
- CLI: `deeptutor run deep_solve "<topic>" --kb <kb> --tool rag --tool web_search --language zh --format json`
|
|
107
107
|
|
|
108
108
|
**Always record the returned `session_id`** — reuse it for every follow-up turn so DeepTutor keeps the context (chat history, knowledge bases, tools).
|
|
@@ -113,7 +113,7 @@ Continue with the same `session_id`: details, code examples, common pitfalls, di
|
|
|
113
113
|
|
|
114
114
|
### 4. Self-test
|
|
115
115
|
|
|
116
|
-
-
|
|
116
|
+
- Tools: `deeptutor_run capability=deep_question prompt="<topic essentials>" session_id=<same> config={num_questions: 5} kbs=[<kb>] language=zh`
|
|
117
117
|
- CLI: `deeptutor run deep_question "<topic essentials>" --session <same> --config num_questions=5 --kb <kb> --language zh --format json`
|
|
118
118
|
|
|
119
119
|
### 5. Output a study plan
|
|
@@ -137,7 +137,7 @@ Node converter):
|
|
|
137
137
|
node skills/html-doc/scripts/md-to-html.js <answer.md> --out <page.html> --title "<主题>" --toc
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
When given the `html` param, `deeptutor_run` does this automatically:
|
|
141
141
|
|
|
142
142
|
```
|
|
143
143
|
deeptutor_run capability=deep_solve prompt=<topic> kbs=[...] tools=[rag, web_search]
|
|
@@ -152,7 +152,7 @@ Then tell the user the HTML file paths so they can open them in a browser.
|
|
|
152
152
|
|
|
153
153
|
Store the final notes/plan in a server notebook (e.g. `dotnet-learning`) so they can be retrieved for review later:
|
|
154
154
|
|
|
155
|
-
-
|
|
155
|
+
- Tools: `deeptutor_note notebook=<name> title="<t>" type=solve content=<markdown>`
|
|
156
156
|
- CLI:
|
|
157
157
|
```bash
|
|
158
158
|
deeptutor notebook list # find the notebook id
|
|
@@ -172,7 +172,8 @@ explicit request — never proactively.
|
|
|
172
172
|
|
|
173
173
|
## Notes
|
|
174
174
|
|
|
175
|
-
-
|
|
175
|
+
- Tool calls go through HTTP/WS first (tunnel), falling back to SSH CLI automatically when the tunnel is down; `deeptutor_run` streams over WebSocket so progress is visible.
|
|
176
|
+
- CLI-only flags (not exposed as `deeptutor_run` tool parameters): `--config-json` (complex config; the tools pass config through the API's `config` parameter instead), `--notebook-ref`, `--history-ref`.
|
|
176
177
|
- `deep_research` can take minutes: give a generous timeout (900+) or split long tasks.
|
|
177
178
|
- Results may be truncated (default 30000 chars) — ask to continue for the full content.
|
|
178
179
|
- RAG hits can be noisy; treat official sources as authoritative and point out conflicts to the user.
|
package/skills/html-doc/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ Convert Markdown into a beautiful, self-contained local HTML file (single file,
|
|
|
18
18
|
html-doc/
|
|
19
19
|
├── SKILL.md # this file
|
|
20
20
|
├── scripts/
|
|
21
|
-
│ └── md-to-html.js # zero-dependency Markdown → HTML converter (Node ≥
|
|
21
|
+
│ └── md-to-html.js # zero-dependency Markdown → HTML converter (Node ≥ 20.11)
|
|
22
22
|
└── assets/
|
|
23
23
|
└── template.html # default HTML template (CSS + JS, customizable)
|
|
24
24
|
```
|
|
@@ -86,6 +86,6 @@ Edit `assets/template.html` — it uses `{{TITLE}}`, `{{BODY}}`, `{{THEME}}`, `{
|
|
|
86
86
|
|
|
87
87
|
## Notes
|
|
88
88
|
|
|
89
|
-
- Zero dependencies — only `node` (≥
|
|
89
|
+
- Zero dependencies — only `node` (≥ 20.11, uses `import.meta.dirname`) is required; no npm install needed
|
|
90
90
|
- Output is fully self-contained: inline CSS + JS, no external fonts/CDN (offline-friendly)
|
|
91
91
|
- Escaping is handled: content is HTML-escaped before insertion. Raw-HTML passthrough is intentionally narrow (`<details>`/`<summary>` only) since `deeptutor_run` feeds model/web_search-sourced answers into this converter automatically — a wider tag whitelist would let untrusted `<script>`/`on*=` content ride along into the generated local HTML.
|