dsh-deeptutor 0.1.6 → 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 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
@@ -100,6 +100,8 @@ npm publish
100
100
 
101
101
  升级包版本时会就地覆盖 skill 文件;包未携带的文件不会被删除。
102
102
 
103
+ `skills/` 下的文件与 `TecFancy/pi-extensions` 中的同名技能逐字节一致(以 pi 仓库为唯一来源,保持 agent 中立)。上游修改后执行 `node scripts/sync-skills.mjs ../pi-extensions` 同步。
104
+
103
105
  ## 配置(环境变量,与 agent 无关)
104
106
 
105
107
  ```bash
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
- opts.signal?.addEventListener("abort", onAbort, { once: true });
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
  }
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
- /** Execute one turn via the CLI (local or SSH). */
23
- export declare function cliRunTurn(params: RunParams, timeoutMs: number, signal?: AbortSignal): Promise<TurnResult>;
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
- /** Execute one turn via the CLI (local or SSH). */
114
- export async function cliRunTurn(params, timeoutMs, signal) {
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
- for (const [k, v] of Object.entries(params.config ?? {}))
125
- argv.push("--config", `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`);
126
- const { stdout, stderr, code, killed } = await runCli(argv, { signal, timeoutMs });
127
- if (killed)
128
- throw new Error(`CLI execution timed out (${Math.round(timeoutMs / 1000)}s)`);
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.6",
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",
@@ -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 >= 22).
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 === "--out") opts.out = argv[++i];
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
- const templatePath = path.join(import.meta.dirname, "..", "assets", "template.html");
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 (dsh bundle + SKILL)
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
- > Bundle: the `dsh-deeptutor` npm package (tools) | Skill: `skills/deeptutor/SKILL.md` (shipped inside the same package, installed to `~/.dsh/skills/deeptutor/`)
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 dsh: combines your **personal knowledge bases** (RAG retrieval) with **authoritative web sources** to generate learning content, self-test questions, and study plans — then archives your notes to a DeepTutor notebook for later review.
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
- The `deeptutor` skill ships inside the `dsh-deeptutor` npm package. The bundled
22
- installer (`scripts/install-profile.mjs` in the package, or
23
- `pnpm dlx dsh-deeptutor --profile web`) installs the bundle **and** copies this
24
- skill to `~/.dsh/skills/deeptutor/` (dsh auto-discovers skills there):
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 extension injects `PYTHONUTF8=1` automatically; verify Python ≥ 3.11 otherwise |
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 bundle stay unchanged.
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 bundle.
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 themselves are registered by the dsh bundle.
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
- - Bundle features: edit the relevant file under `src/` (`index.ts` for tool wiring, `cli-exec.ts`/`http-api.ts`/`turn.ts` for transport logic, `config.ts` for env vars/helpers) → rebuild with `npm run build`
105
- - SKILL content: edit `skills/deeptutor/SKILL.md` (keep the `name` + `description` frontmatter)
106
- - Release: bump the version in `package.json`, push a `v*` tag, then `npm publish` (see the repo README)
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
- - **dsh path:** API reachable (local serve or tunnel) → HTTP/WS; unreachable →
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 | dsh (tools) | CLI equivalent (`deeptutor …`) |
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
- - pi: `deeptutor_kb action=search kb=<kb> query="<topic>"`
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
- - pi: `deeptutor_run capability=deep_solve prompt="<topic>" kbs=[<kb>] tools=[rag, web_search] language=zh`
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
- - pi: `deeptutor_run capability=deep_question prompt="<topic essentials>" session_id=<same> config={num_questions: 5} kbs=[<kb>] language=zh`
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
- On dsh, `deeptutor_run` does this automatically when given the `html` param:
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
- - dsh: `deeptutor_note notebook=<name> title="<t>" type=solve content=<markdown>`
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
- - On dsh, 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.
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.
@@ -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 ≥ 18)
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` (≥ 18) is required; no npm install needed
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.