@tbox.cn/tboxcode-dsh-tools 0.0.0-stage → 0.1.0-rc.1

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.
@@ -0,0 +1,25 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/pre-execute/index.d.ts
4
+
5
+ declare const name = "tboxcode-pre-execute";
6
+ declare const inject: string[];
7
+ interface Config {
8
+ /** permission 映射(config `permission` 翻译域)。 */
9
+ permission?: Record<string, 'allow' | 'ask' | 'deny'>;
10
+ /** plan 档 MCP 写类模式表(后缀或完整名;缺省内置写类动词表)。 */
11
+ mcpDenyPatterns?: string[];
12
+ }
13
+ /** 内置 MCP 写类模式表(保守后缀匹配——误伤面最小化,Revisions 维护义务)。 */
14
+ declare const DEFAULT_MCP_DENY_PATTERNS: readonly string[];
15
+ /** permission 优先级链(源 permission-policy resolveToolPermission 移植,
16
+ * dsh 侧无 sideEffecting/defaultPermission 面——无匹配返回 undefined 透传):
17
+ * exact → 最长 `prefix_*` 通配 → 组精确/通配 → `*` 全局。 */
18
+ declare function resolveToolPermission(toolName: string, permission: Record<string, 'allow' | 'ask' | 'deny'> | undefined, permissionGroups?: readonly string[]): 'allow' | 'ask' | 'deny' | undefined;
19
+ /** MCP 写类命中:`_` 分段精确匹配(模式词剥前导 `_`;如 `github_create_issue`
20
+ * 的 [github, create, issue] 含 'create')或完整名匹配。分段制覆盖写类动词
21
+ * 位于任意段位的 MCP sanitized 名形态(`<server>_<verb>` 与 `<server>_<verb>_<object>`)。 */
22
+ declare function matchesMcpDenyPattern(toolName: string, patterns: readonly string[]): boolean;
23
+ declare function apply(ctx: Context, config?: Config): () => void;
24
+ //#endregion
25
+ export { matchesMcpDenyPattern as a, inject as i, DEFAULT_MCP_DENY_PATTERNS as n, name as o, apply as r, resolveToolPermission as s, Config as t };
@@ -0,0 +1,13 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/restrict/index.d.ts
4
+
5
+ declare const name = "tboxcode-restrict";
6
+ declare const inject: string[];
7
+ interface Config {
8
+ /** `'slash-only'`:skill 工具对模型不可见(仅 user 手势可调)。 */
9
+ skills?: 'slash-only';
10
+ }
11
+ declare function apply(ctx: Context, config?: Config): () => void;
12
+ //#endregion
13
+ export { name as i, apply as n, inject as r, Config as t };
@@ -0,0 +1,21 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/scratch/index.d.ts
4
+ declare const name = "tboxcode-scratch";
5
+ declare const inject: string[];
6
+ /** 结构面(dsh 类型子集——零 peer 膨胀)。 */
7
+ interface SandboxExecutionPolicy {
8
+ mode: string;
9
+ workspaceRoot: string;
10
+ sessionId?: string;
11
+ }
12
+ /** 会话私有 scratch 目录(平台 tmp——生命周期归 OS,不自建 retention)。 */
13
+ declare function scratchDirFor(sessionId: string): string;
14
+ /** 相对路径对 session cwd 解析(绝对原样);cwd 缺席且相对 → 拒绝(明错)。 */
15
+ declare function resolveWithSessionCwd(modelPath: string | undefined, cwd: string | undefined): string | undefined;
16
+ /** per-call policy 构造(工作区 = scratch 目录——工作区不可达,/tmp 系由
17
+ * workspace-write 档 writableRoots 原生覆盖)。 */
18
+ declare function scratchPolicy(sessionId: string): SandboxExecutionPolicy;
19
+ declare function apply(ctx: Context): () => void;
20
+ //#endregion
21
+ export { scratchDirFor as a, resolveWithSessionCwd as i, inject as n, scratchPolicy as o, name as r, apply as t };
@@ -0,0 +1,14 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/webfetch/index.d.ts
4
+
5
+ declare const name = "tboxcode-webfetch";
6
+ declare const inject: string[];
7
+ /** 插件行 config(翻译域值;内部防御校验)。 */
8
+ interface Config {
9
+ maxLines?: number;
10
+ maxBytes?: number;
11
+ }
12
+ declare function apply(ctx: Context, config?: Config): () => void;
13
+ //#endregion
14
+ export { name as i, apply as n, inject as r, Config as t };
@@ -0,0 +1,23 @@
1
+ import { Context } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/skill-provider/index.d.ts
4
+ declare const name = "tboxcode-skill-provider";
5
+ declare const inject: string[];
6
+ /** 插件行 config(`skills.paths` 翻译域 + configDir 注入 + watcher 节奏)。 */
7
+ interface Config {
8
+ /** 额外扫描根(config `skills.paths` 翻译域,C8)。 */
9
+ paths?: string[];
10
+ /** tboxcode configDir(缺省 `~/.config/tboxcode-dsh`——翻译域不传时兜底)。 */
11
+ configDir?: string;
12
+ /** 轮询间隔 ms(缺省 2000;测试注入短间隔)。 */
13
+ pollIntervalMs?: number;
14
+ /** 变更稳定窗口 ms(缺省 500;测试注入短窗口)。 */
15
+ settleWindowMs?: number;
16
+ }
17
+ /** 统一 rank(note 定案:< 原生项目根 100)。 */
18
+ declare const TBOXCODE_SKILL_RANK = 50;
19
+ /** cwd 向上每级两目录(近→远;源 collectCwdUpSkillRoots 移植)。 */
20
+ declare function collectCwdUpSkillRoots(cwd: string): string[];
21
+ declare function apply(ctx: Context, config?: Config): () => void;
22
+ //#endregion
23
+ export { inject as a, collectCwdUpSkillRoots as i, TBOXCODE_SKILL_RANK as n, name as o, apply as r, Config as t };
package/lib/index.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { i as name$4, n as apply$4 } from "./index-DjflX-te.js";
2
+ import { i as collectCwdUpSkillRoots, n as TBOXCODE_SKILL_RANK, o as name$3, r as apply$3 } from "./index-NdtUHa7X.js";
3
+ import { i as name$1, n as apply$1 } from "./index-CSv9cWMb.js";
4
+ import { a as matchesMcpDenyPattern, n as DEFAULT_MCP_DENY_PATTERNS, o as name, r as apply, s as resolveToolPermission } from "./index-9ZM90CvT.js";
5
+ import { a as scratchDirFor, i as resolveWithSessionCwd, o as scratchPolicy, r as name$2, t as apply$2 } from "./index-DHJdScQt.js";
6
+ import { ToolRunContext } from "@deepseek-ai/dsh-tools";
7
+
8
+ //#region src/webfetch/webfetch-core.d.ts
9
+
10
+ /** 截断缺省(parity:openacpagent truncate.ts MAX_LINES/MAX_BYTES)。 */
11
+ declare const TRUNCATE_MAX_LINES = 2000;
12
+ declare const TRUNCATE_MAX_BYTES: number;
13
+ declare function extractTextFromHTML(html: string): Promise<string>;
14
+ interface Hop {
15
+ readonly url: string;
16
+ readonly kind: 'js' | 'meta';
17
+ }
18
+ declare function detectRedirect(html: string, baseUrl: string): Hop | null;
19
+ /** 流式读取响应体,字节上限保护(防极端 OOM;SSRF 职责归运行沙箱)。 */
20
+ declare function readLimitedResponse(resp: Response, maxBytes: number): Promise<Buffer>;
21
+ interface TruncateOptions {
22
+ max_lines?: number;
23
+ max_bytes?: number;
24
+ }
25
+ /** 截断落盘 sink(工具装配点注入;缺席 = 纯截断降级)。 */
26
+ interface TruncateSink {
27
+ (text: string, exec: ToolRunContext): Promise<string | undefined>;
28
+ }
29
+ /**
30
+ * 统一输出截断:行/字节双限 head 保留;截断时全量经 sink 落盘(缺席或失败
31
+ * → 纯截断降级,零异常外泄)。截断注记格式对齐投影基准:
32
+ * `[output truncated: N lines omitted]`。
33
+ */
34
+ declare function truncateOutput(text: string, exec: ToolRunContext, options?: TruncateOptions, sink?: TruncateSink): Promise<{
35
+ content: string;
36
+ truncated: boolean;
37
+ }>;
38
+ interface WebfetchInput {
39
+ url: string;
40
+ format?: 'text' | 'markdown' | 'html';
41
+ timeout?: number;
42
+ }
43
+ interface WebfetchDeps {
44
+ /** 截断参数(插件 config,缺省 2000 行 / 50KB)。 */
45
+ max_lines?: number;
46
+ max_bytes?: number;
47
+ /** 截断落盘 sink(spillStore 装配;缺席 = 纯截断降级)。 */
48
+ spillSink?: TruncateSink;
49
+ }
50
+ declare function executeWebfetch(input: WebfetchInput, exec: ToolRunContext, deps?: WebfetchDeps): Promise<string>;
51
+ //#endregion
52
+ //#region src/skill-provider/frontmatter.d.ts
53
+ interface ParsedSkillFile {
54
+ name: string;
55
+ description: string;
56
+ allowedTools?: string[];
57
+ hidden: boolean;
58
+ metadata?: Record<string, unknown>;
59
+ content: string;
60
+ contentHash: string;
61
+ }
62
+ /**
63
+ * 解析 SKILL.md 原文。无 frontmatter / 无 name / YAML 解析失败 → undefined。
64
+ * 未知顶层字段并入 metadata(spike 结论"未知字段不破坏解析、技能不丢"的
65
+ * dsh metadata 通道落位);contentHash = sha256(原文) 前 16 hex。
66
+ */
67
+ declare function parseSkillFile(raw: string, yamlParse: (s: string) => unknown): ParsedSkillFile | undefined;
68
+ /** 目录描述截断上限(`catalogDescriptionMaxLength 1024` 翻译域的语义源头)。 */
69
+ declare const MAX_SKILL_DESC = 1024;
70
+ /** 折行 → 截断至 MAX_SKILL_DESC 码点(确定性——截断点外变更不改 hash)。 */
71
+ declare function formatSkillDesc(desc: string | undefined): string;
72
+ //#endregion
73
+ export { DEFAULT_MCP_DENY_PATTERNS, MAX_SKILL_DESC, TBOXCODE_SKILL_RANK, TRUNCATE_MAX_BYTES, TRUNCATE_MAX_LINES, type TruncateOptions, type TruncateSink, type WebfetchDeps, type WebfetchInput, collectCwdUpSkillRoots, detectRedirect, executeWebfetch, extractTextFromHTML, formatSkillDesc, matchesMcpDenyPattern, parseSkillFile, apply as preExecuteApply, name as preExecuteName, readLimitedResponse, resolveToolPermission, resolveWithSessionCwd, apply$1 as restrictApply, name$1 as restrictName, apply$2 as scratchApply, scratchDirFor, name$2 as scratchName, scratchPolicy, apply$3 as skillProviderApply, name$3 as skillProviderName, truncateOutput, apply$4 as webfetchApply, name$4 as webfetchName };
package/lib/index.js ADDED
@@ -0,0 +1 @@
1
+ import{a as e,c as t,i as n,l as r,o as i,r as a,s as o,t as s,u as c}from"./webfetch-CXvjRbDU.js";import{a as l,c as u,n as d,o as f,r as p,s as m,t as h}from"./skill-provider-DenqO0Yu.js";import{r as g,t as _}from"./restrict-CldLToKG.js";import{a as v,i as y,n as b,o as x,t as S}from"./pre-execute-CSOIc_CR.js";import{a as C,i as w,o as T,r as E,t as D}from"./scratch-9LkFiTBl.js";export{S as DEFAULT_MCP_DENY_PATTERNS,f as MAX_SKILL_DESC,h as TBOXCODE_SKILL_RANK,n as TRUNCATE_MAX_BYTES,e as TRUNCATE_MAX_LINES,p as collectCwdUpSkillRoots,i as detectRedirect,o as executeWebfetch,t as extractTextFromHTML,m as formatSkillDesc,y as matchesMcpDenyPattern,u as parseSkillFile,b as preExecuteApply,v as preExecuteName,r as readLimitedResponse,x as resolveToolPermission,w as resolveWithSessionCwd,_ as restrictApply,g as restrictName,D as scratchApply,C as scratchDirFor,E as scratchName,T as scratchPolicy,d as skillProviderApply,l as skillProviderName,c as truncateOutput,s as webfetchApply,a as webfetchName};
@@ -0,0 +1,2 @@
1
+ import { a as matchesMcpDenyPattern, i as inject, n as DEFAULT_MCP_DENY_PATTERNS, o as name, r as apply, s as resolveToolPermission, t as Config } from "../index-9ZM90CvT.js";
2
+ export { Config, DEFAULT_MCP_DENY_PATTERNS, apply, inject, matchesMcpDenyPattern, name, resolveToolPermission };
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"../pre-execute-CSOIc_CR.js";export{a as DEFAULT_MCP_DENY_PATTERNS,n as apply,i as inject,t as matchesMcpDenyPattern,e as name,r as resolveToolPermission};
@@ -0,0 +1 @@
1
+ import{z as e}from"zod";const t=`tboxcode-pre-execute`,n=[`tools`],r=e.enum([`allow`,`ask`,`deny`]),i=e.object({permission:e.record(e.string(),r).optional(),mcpDenyPatterns:e.array(e.string()).optional()}),a=[`_write`,`_create`,`_update`,`_delete`,`_remove`,`_patch`,`_modify`,`_rename`,`_move`,`_copy`,`_upload`,`_send`,`_deploy`,`_publish`,`_commit`,`_push`,`_post`,`_put`,`_insert`,`_replace`];function o(e,t,n){if(t===void 0)return;let r=t[e];if(r!==void 0)return r;let i=-1,a;for(let n of Object.keys(t))if(n.endsWith(`_*`)){let r=n.slice(0,-2);e.startsWith(`${r}_`)&&r.length>i&&(i=r.length,a=t[n])}if(a!==void 0)return a;if(n!==void 0&&n.length>0){for(let e of n){let n=t[e];if(n!==void 0)return n}for(let e of Object.keys(t))if(e.endsWith(`:*`)){let r=e.slice(0,-2);for(let i of n)if(i.startsWith(`${r}:`))return t[e]}}return t[`*`]}function s(e,t){if(!e.includes(`_`))return!1;let n=new Set(e.split(`_`));for(let r of t){if(r===e)return!0;let t=r.startsWith(`_`)?r.slice(1):r;if(t.length>0&&n.has(t))return!0}return!1}function c(e,t={}){let n=i.parse(t),r=n.permission,c=n.mcpDenyPatterns??a,l=e.get(`sessionProjections`);return e.on(`tools/pre-execute`,async(e,t)=>{if(o(e.name,r)===`deny`)return{kind:`deny`,reason:`Tool '${e.name}' is denied by the permission configuration. Do not retry this tool; use an alternative approach or ask the user.`};if(l!==void 0&&e.agent!==void 0)try{if(l.stateOf(e.agent.session,`sandboxMode`)===`read-only`&&s(e.name,c))return{kind:`deny`,reason:`Plan mode is active (read-only): the MCP tool '${e.name}' may modify external systems, so it is blocked. Record the intended action in the plan instead, or ask the user to switch to build mode to execute it.`}}catch{}return t()}),()=>{}}export{t as a,s as i,c as n,o,n as r,a as t};
@@ -0,0 +1,2 @@
1
+ import { i as name, n as apply, r as inject, t as Config } from "../index-CSv9cWMb.js";
2
+ export { Config, apply, inject, name };
@@ -0,0 +1 @@
1
+ import{n as e,r as t,t as n}from"../restrict-CldLToKG.js";export{n as apply,e as inject,t as name};
@@ -0,0 +1 @@
1
+ import{z as e}from"zod";const t=`tboxcode-restrict`,n=[`tools`],r=e.object({skills:e.literal(`slash-only`).optional()});function i(e){let t=new Map,n=e=>{if(!t.has(e))try{let n=e.ctx.tools.restrict({deny:[`skill`]});t.set(e,n)}catch{}};for(let t of e.agents.list())n(t);return e.on(`agent/created`,({agent:e})=>{n(e)}),e.on(`agent/disposed`,({agent:e})=>{t.delete(e)}),()=>{t.clear()}}function a(e,t={}){return r.parse(t).skills===`slash-only`?i(e):()=>{}}export{n,t as r,a as t};
@@ -0,0 +1,2 @@
1
+ import { a as scratchDirFor, i as resolveWithSessionCwd, n as inject, o as scratchPolicy, r as name, t as apply } from "../index-DHJdScQt.js";
2
+ export { apply, inject, name, resolveWithSessionCwd, scratchDirFor, scratchPolicy };
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,o as r,r as i,t as a}from"../scratch-9LkFiTBl.js";export{a as apply,n as inject,i as name,t as resolveWithSessionCwd,e as scratchDirFor,r as scratchPolicy};
@@ -0,0 +1,2 @@
1
+ import{defineTool as e}from"@deepseek-ai/dsh-tools";import{mkdir as t}from"node:fs/promises";import{tmpdir as n}from"node:os";import{isAbsolute as r,join as i,sep as a}from"node:path";import{randomUUID as o}from"node:crypto";const s=`tboxcode-scratch`,c=[`tools`,`shell`,`fs`];function l(e){return i(n(),`tboxcode-scratch`,e)}function u(e){return e.agent?.session?.header?.cwd}function d(e,t){if(e===void 0)return t;if(r(e))return e;if(t!==void 0)return`${t}${a}${e}`}function f(e){return{mode:`workspace-write`,workspaceRoot:l(e),sessionId:e}}async function p(e){await t(e,{recursive:!0})}function m(e){let t=[];return t.push(`exitCode: ${e.exitCode??`null`}${e.timedOut?` (timed out)`:``}${e.aborted?` (aborted)`:``}`),e.stdout.trim().length>0&&t.push(`stdout:\n${e.stdout}`),e.stderr.trim().length>0&&t.push(`stderr:\n${e.stderr}`),t.join(`
2
+ `)}function h(t){let n=t.shell,r=t.fs,i=e=>{let t=e.agent?.session?.header?.id??`adhoc-${o()}`;return{sessionId:t,dir:l(t),cwd:u(e),policy:f(t)}},a=[t.tools.register(e({name:`scratch_bash`,description:`Run a bash command confined to the session-private scratch directory (a temporary workspace outside the project). Use it for throwaway verification work — quick scripts, dependency checks, file inspection — that must not touch the workspace. Working directory defaults to the session workspace; relative paths resolve against it.`,parameters:{command:{type:`string`,required:!0,description:`The bash command to execute inside the scratch sandbox.`},description:{type:`string`,required:!0,description:`Clear, concise description of what this command does, 5-10 words.`},timeoutMs:{type:`number`,description:`Timeout in milliseconds; the executor applies its configured default and cap.`}},output:{schema:{type:`string`},render:(e,t)=>[{type:`text`,text:String(t)}]},async execute(e,t){let{dir:r,cwd:a,policy:o}=i(t);await p(r);let s=d(void 0,a);if(s===void 0)throw Error(`scratch_bash requires a session workspace (agentless calls cannot resolve a working directory)`);let c={command:e.command,workdir:s,...e.timeoutMs===void 0?{}:{timeoutMs:e.timeoutMs},signal:t.signal},l=n.resolve(c);l.sandboxPolicy=o;let u=(await n.execute(l)).result;if(u===void 0)throw Error(`scratch_bash: executor produced no result`);return m(u)}})),t.tools.register(e({name:`scratch_write`,description:`Create or overwrite a file inside the session-private scratch directory (a temporary workspace outside the project). Use it for scratch files, fixture data, and one-off scripts. Paths resolve against the session workspace; the file must land inside the scratch sandbox.`,parameters:{filePath:{type:`string`,required:!0,description:`Target file path (must be inside the scratch directory).`},content:{type:`string`,required:!0,description:`Full file content to write.`}},output:{schema:{type:`string`},render:(e,t)=>[{type:`text`,text:String(t)}]},async execute(e,t){let{dir:n,cwd:a,policy:o}=i(t);await p(n);let s=d(e.filePath,a);if(s===void 0)throw Error(`scratch_write requires a session workspace or an absolute path`);let c=await r.resolve(s,{cwd:a,signal:t.signal}),l=await r.writeText(c,e.content,void 0,t.signal,o);return`written: ${e.filePath} (version ${String(l.version)})`}})),t.tools.register(e({name:`scratch_edit`,description:`Edit a file inside the session-private scratch directory by exact literal replacement. Use it to iterate on scratch files without rewriting them. Paths resolve against the session workspace; the file must land inside the scratch sandbox.`,parameters:{filePath:{type:`string`,required:!0,description:`Target file path (must be inside the scratch directory).`},oldString:{type:`string`,required:!0,description:`Literal text to replace; must match exactly.`},newString:{type:`string`,required:!0,description:`Replacement text (empty string deletes the match).`},replaceAll:{type:`boolean`,description:`Replace every match instead of requiring exactly one (default false).`}},output:{schema:{type:`string`},render:(e,t)=>[{type:`text`,text:String(t)}]},async execute(e,t){let{dir:n,cwd:a,policy:o}=i(t);await p(n);let s=d(e.filePath,a);if(s===void 0)throw Error(`scratch_edit requires a session workspace or an absolute path`);let c=await r.resolve(s,{cwd:a,signal:t.signal}),l=await r.editText(c,{oldString:e.oldString,newString:e.newString,replaceAll:e.replaceAll??!1},void 0,t.signal,o);return`edited: ${e.filePath} (version ${String(l.version)})`}}))];return()=>{for(let e of a)e()}}export{l as a,d as i,c as n,f as o,s as r,h as t};
@@ -0,0 +1,2 @@
1
+ import { a as inject, i as collectCwdUpSkillRoots, n as TBOXCODE_SKILL_RANK, o as name, r as apply, t as Config } from "../index-NdtUHa7X.js";
2
+ export { Config, TBOXCODE_SKILL_RANK, apply, collectCwdUpSkillRoots, inject, name };
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,r,t as i}from"../skill-provider-DenqO0Yu.js";export{i as TBOXCODE_SKILL_RANK,n as apply,r as collectCwdUpSkillRoots,t as inject,e as name};
@@ -0,0 +1,7 @@
1
+ import{z as e}from"zod";import{readFile as t,readdir as n}from"node:fs/promises";import{homedir as r}from"node:os";import{dirname as i,join as a,resolve as o}from"node:path";import{parse as s}from"yaml";import{createHash as c}from"node:crypto";function l(e,t){let n;try{n=t(e)}catch{return}if(!n||typeof n!=`object`)return;let r=n;return{...r,name:String(r.name??``),description:r.description==null?void 0:String(r.description),allowedTools:u(r[`allowed-tools`]??r.allowedTools),hidden:r.hidden===!0,metadata:typeof r.metadata==`object`&&r.metadata!==null&&!Array.isArray(r.metadata)?r.metadata:void 0}}function u(e){if(Array.isArray(e))return e.map(String);if(typeof e==`string`)return e.split(`,`).map(e=>e.trim()).filter(Boolean)}const d=new Set([`name`,`description`,`allowed-tools`,`allowedTools`,`hidden`,`metadata`]);function f(e,t){let n=p(e);if(n.frontmatter===null)return;let r=l(n.frontmatter,t);if(r===void 0||!r.name)return;let i={};for(let[e,t]of Object.entries(r))!d.has(e)&&t!==void 0&&(i[e]=t);let a=r.metadata===void 0?Object.keys(i).length>0?i:void 0:{...i,...r.metadata};return{name:r.name,description:r.description??``,...r.allowedTools===void 0?{}:{allowedTools:r.allowedTools},hidden:r.hidden===!0,...a===void 0?{}:{metadata:a},content:n.body,contentHash:c(`sha256`).update(e).digest(`hex`).slice(0,16)}}function p(e){let t=(e.includes(`\r
2
+ `)?e.replace(/\r\n/g,`
3
+ `):e).split(`
4
+ `);if(t[0]?.trim()!==`---`)return{frontmatter:null,body:e};let n=-1;for(let e=1;e<t.length;e++)if(t[e]?.trim()===`---`){n=e;break}return n<0?{frontmatter:null,body:e}:{frontmatter:t.slice(1,n).join(`
5
+ `),body:t.slice(n+1).join(`
6
+ `).trim()}}const m=1024;function h(e){if(!e?.trim())return``;let t=e.trim().replace(/\s*\n\s*/g,` `);if(t.length<=m)return t;let n=Array.from(t);return n.length>m?`${n.slice(0,m).join(``)}…`:t}const g=`tboxcode-skill-provider`,_=[`skills`],v=e.object({paths:e.array(e.string()).optional(),configDir:e.string().optional(),pollIntervalMs:e.number().int().positive().optional(),settleWindowMs:e.number().int().nonnegative().optional()}),y=50;async function b(e,r){let a;try{a=await n(e,{withFileTypes:!0})}catch{return}for(let n of a){let a=o(e,n.name);if(n.isDirectory()){if(n.name.startsWith(`.`))continue;await b(a,r)}else if(n.isFile()&&n.name===`SKILL.md`)try{let e=f(await t(a,`utf-8`),e=>s(e));if(e===void 0)continue;r.has(e.name)||r.set(e.name,{parsed:e,location:a,dir:i(a)})}catch{}}}function x(e){let t=[],n=o(e);for(;;){t.push(a(n,`.claude`,`skills`)),t.push(a(n,`.agents`,`skills`));let e=i(n);if(e===n)break;n=e}return t}function S(e,n={}){let i=v.parse(n),o=e.skills,c=()=>{let e=r(),t=[a(e,`.claude`,`skills`),a(e,`.agents`,`skills`),i.configDir??a(e,`.config`,`tboxcode-dsh`)];return t.push(...i.paths??[]),t},l=async e=>{let t=new Map,n=[...c(),...e===void 0?[]:x(e)];for(let e of n)await b(e,t);return t},u=async e=>[...(await l(e)).values()].map(({parsed:e,location:t})=>`${t}:${e.contentHash}`).sort().join(`
7
+ `),d={invalidate:()=>{}},p,m=!1,g,_,y=o.registerProvider(e=>(d.invalidate=e.invalidate,p=setInterval(()=>{m||(async()=>{try{let t=await u(_);if(g===void 0){g=t;return}t!==g&&(g=t,setTimeout(()=>{m||e.invalidate()},i.settleWindowMs??500))}catch{}})()},i.pollIntervalMs??2e3),typeof p==`object`&&`unref`in p&&p.unref(),{name:`tboxcode`,rank:50,async list(e){_=e.cwd,g===void 0&&(g=await u(e.cwd));let t=await l(e.cwd),n=[];for(let{parsed:e,location:r,dir:i}of t.values())n.push({path:r,name:e.name,description:h(e.description),invocation:{modelInvocable:!e.hidden,userInvocable:!0},source:`custom`,provider:`tboxcode`,resourceBase:{kind:`directory`,path:i},rank:50,locator:r,...e.metadata===void 0?{}:{metadata:e.metadata}});return n},async get(e){let n=String(e.locator);try{let e=f(await t(n,`utf-8`),e=>s(e));return e===void 0?void 0:{name:e.name,description:e.description,invocation:{modelInvocable:!e.hidden,userInvocable:!0},source:`custom`,provider:`tboxcode`,content:e.content,...e.metadata===void 0?{}:{metadata:e.metadata}}}catch{return}}}));return()=>{m=!0,p!==void 0&&clearInterval(p),y()}}export{g as a,f as c,_ as i,S as n,m as o,x as r,h as s,y as t};
@@ -0,0 +1,2 @@
1
+ import { i as name, n as apply, r as inject, t as Config } from "../index-DjflX-te.js";
2
+ export { Config, apply, inject, name };
@@ -0,0 +1 @@
1
+ import{n as e,r as t,t as n}from"../webfetch-CXvjRbDU.js";export{n as apply,e as inject,t as name};
@@ -0,0 +1,4 @@
1
+ import{defineTool as e}from"@deepseek-ai/dsh-tools";import{z as t}from"zod";const n=2e3,r=50*1024;function i(e){return e===`text`?`text/plain;q=1.0, text/markdown;q=0.9, text/html;q=0.8, */*;q=0.1`:e===`html`?`text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1`:`text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1`}function a(e,t){return{"User-Agent":t,Accept:i(e),"Accept-Language":`en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7`,"Upgrade-Insecure-Requests":`1`}}async function o(e,t,n,r){let i=await fetch(e,{signal:r,redirect:`follow`,headers:a(t,n)});return i.status===403&&i.headers.get(`cf-mitigated`)===`challenge`?fetch(e,{signal:r,redirect:`follow`,headers:a(t,`tboxcode/1.0`)}):i}const s=/charset\s*=\s*["']?([\w-]+)/i,c={gb2312:`gbk`,gbk:`gbk`,gb18030:`gbk`,big5:`big5`,"cn-big5":`big5`,"big-5":`big5`,"utf-16":`utf-16le`,utf16le:`utf-16le`,utf16be:`utf-16be`,ascii:`utf-8`,"us-ascii":`utf-8`,"iso-8859-1":`utf-8`,latin1:`utf-8`};function l(e){let t=s.exec(e);if(!t||!t[1])return`utf-8`;let n=t[1].trim().toLowerCase();return c[n]??(n||`utf-8`)}function u(e,t){try{return new TextDecoder(t,{fatal:!1}).decode(e)}catch{return new TextDecoder(`utf-8`,{fatal:!1}).decode(e)}}const d=e=>e.split(`;`,1)[0]?.trim().toLowerCase()??``,f=e=>e.startsWith(`image/`)&&e!==`image/svg+xml`&&e!==`image/vnd.fastbidsheet`,p=e=>!e||e.startsWith(`text/`)||e===`application/json`||e.endsWith(`+json`)||e===`application/xml`||e.endsWith(`+xml`)||e===`application/javascript`||e===`application/x-javascript`,m=[`script`,`style`,`noscript`,`iframe`,`object`,`embed`];async function h(e){let{Parser:t}=await import(`htmlparser2`),n=``,r=0,i=new t({onopentag(e){(r>0||m.includes(e))&&r++},ontext(e){r===0&&(n+=e)},onclosetag(){r>0&&r--}});return i.write(e),i.end(),n.trim()}async function g(e){try{let t=(await import(`turndown`)).default,n=new t({headingStyle:`atx`,hr:`---`,bulletListMarker:`-`,codeBlockStyle:`fenced`,emDelimiter:`*`});return n.remove([`script`,`style`,`meta`,`link`]),n.turndown(e)}catch{return h(e)}}function _(e,t){try{return new URL(e,t||void 0).toString()}catch{return null}}function v(e,t){return!/location\.replace\s*\(\s*location\.href\.replace\s*\(\s*["']https:\/\/["']\s*,\s*["']http:\/\/["']\s*\)\s*\)/i.test(e)||!t.startsWith(`https://`)?null:t.replace(/^https:\/\//,`http://`)}function y(e,t){let n=e.slice(0,65536),r=n.match(/<script\b[^>]*>[\s\S]*?<\/script>/gi)??[],i=[/location\.replace\s*\(\s*['"]([^'"]+)['"]\s*\)/i,/location\.href\s*=\s*['"]([^'"]+)['"]/i,/window\.location(?:\.href)?\s*=\s*['"]([^'"]+)['"]/i,/window\.location\.replace\s*\(\s*['"]([^'"]+)['"]\s*\)/i];for(let e of r){let n=e.replace(/<\/?script\b[^>]*>/gi,``),r=v(n,t);if(r)return{url:r,kind:`js`};for(let e of i){let r=e.exec(n)?.[1];if(!r||/^(location|window)\b/i.test(r))continue;let i=_(r,t);if(i&&i!==t)return{url:i,kind:`js`}}}let a=/<meta\s+http-equiv\s*=\s*["']?refresh["']?\s+content\s*=\s*["']([^"']+)["']/i.exec(n);if(a?.[1]){let e=/url\s*=\s*(['"]?)([^'"\s]+)\1/i.exec(a[1]);if(e?.[2]){let n=_(e[2],t);if(n&&n!==t)return{url:n,kind:`meta`}}}return null}function b(e){let{status:t,statusText:n,url:r,cfMitigated:i,retryAfter:a}=e,o=`Fetch failed: HTTP ${t} ${n} (url: ${r}).`;return i?{summary:o,hint:`The site blocks automated access (Cloudflare challenge). Try a different source or ask the user to fetch it manually.`}:t===401||t===403?{summary:o,hint:`The server requires authentication. Ask the user for credentials or try a different source.`}:t===404||t===410?{summary:o,hint:`The URL does not exist. Do not retry the same URL; try a different source.`}:t===429?{summary:o,hint:a?`Retry-After: ${a}. Wait and retry, or try a different source.`:`Retry later or try a different source.`}:t>=400&&t<500?{summary:o,hint:`The request was rejected by the server. Try a different URL or ask the user to verify.`}:t>=500?{summary:o,hint:`The server is temporarily unavailable. Retry once, or try a different source.`}:{summary:o,hint:`Try a different URL or ask the user to verify.`}}async function x(e,t,n){let r,i=[],a=0;try{if(r=e.body?.getReader(),!r)return null;for(;a<800;){let{value:e,done:t}=await r.read();if(t)break;e&&(i.push(e),a+=e.byteLength)}}catch{}finally{try{await r?.cancel()}catch{}}if(i.length===0)return null;let o=new Uint8Array(Math.min(a,800)),s=0;for(let e of i){if(s>=800)break;let t=e.subarray(0,800-s);o.set(t,s),s+=t.byteLength}return u(o,l(n)).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g,` `).replace(/\s+/g,` `).trim().slice(0,t)||null}async function S(e,t){let n=e.headers.get(`content-length`);if(n){let e=Number.parseInt(n,10);if(Number.isFinite(e)&&e>t)throw Error(`Response too large: ${e} bytes (max ${t})`)}let r=e.body?.getReader();if(!r)throw Error(`fetch failed: response has no body`);let i=[],a=0;try{for(;;){let{done:e,value:n}=await r.read();if(e)break;if(a+=n.byteLength,a>t)throw Error(`Response exceeded ${t} byte limit`);i.push(n)}}finally{r.releaseLock()}return Buffer.concat(i)}async function C(e,t,i={},a){let o=i.max_lines??n,s=i.max_bytes??r,c=e.split(`
2
+ `),l=Buffer.byteLength(e,`utf-8`);if(c.length<=o&&l<=s)return{content:e,truncated:!1};let u=[],d=0,f=!1;for(let e=0;e<c.length&&e<o;e++){let t=Buffer.byteLength(c[e]??``,`utf-8`)+(e>0?1:0);if(d+t>s){f=!0;break}u.push(c[e]??``),d+=t}let p;if(a!==void 0)try{p=await a(e,t)}catch{p=void 0}let m=f?l-d:c.length-u.length,h=f?`bytes`:`lines`;return{content:`${u.join(`
3
+ `)}\n\n${`[output truncated: ${m} ${h} omitted]`}\n\n${p===void 0?`The tool call succeeded but the output was truncated.`:`The tool call succeeded but the output was truncated. Full output saved to: ${p}\nUse Grep to search the full content or Read with offset/limit to view specific sections.`}`,truncated:!0}}async function w(e,t,n={}){let r=Math.min((e.timeout??30)*1e3,12e4);try{let i=e.format??`markdown`,a=AbortSignal.any([t.signal,AbortSignal.timeout(r)]),s=e.url;for(let r=0;r<3;r++){let r=await o(s,i,`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36`,a);if(!r.ok){let e=(r.headers.get(`content-type`)??``).toLowerCase(),t=r.headers.get(`cf-mitigated`)===`challenge`,n=r.headers.get(`retry-after`),i=n?/^\d+$/.test(n)?`${n}s`:n:null,a=null,o=d(e);if(o===`application/json`||o.endsWith(`+json`))a=await x(r,200,e);else try{await r.body?.cancel()}catch{}let{summary:s,hint:c}=b({status:r.status,statusText:r.statusText,url:r.url,contentType:e,cfMitigated:t,retryAfter:i,bodySnippet:a}),l=[s,c];throw a&&l.push(`Server response: ${a}`),Error(l.join(`
4
+ `))}let c=await S(r,52428800),m=(r.headers.get(`content-type`)??``).toLowerCase(),_=d(m);if(f(_))throw Error(`Cannot fetch image content type: ${_} (url: ${e.url}).\nUse a different URL, or describe the image to the user.`);if(!p(_))throw Error(`Cannot fetch non-textual content type: ${_} (url: ${e.url}).\nUse a different URL, or ask the user to verify the resource.`);let v=u(c,l(m)),w=y(v,r.url||s);if(w&&w.url!==s){s=w.url;continue}let T;T=i===`html`?v:m.includes(`text/html`)?i===`markdown`?await g(v):await h(v):v;let{content:E}=await C(T,t,{max_lines:n.max_lines,max_bytes:n.max_bytes},n.spillSink);return E}throw Error(`Fetch failed: exceeded 3 redirect hops (last url: ${s}).\nThe page uses a redirect loop; try a different source.`)}catch(n){throw t.signal.aborted?n:n instanceof DOMException&&n.name===`TimeoutError`?Error(`Fetch failed: request timed out after ${r}ms (url: ${e.url}).\nRetry with a longer timeout, or try a different source.`):n instanceof TypeError?Error(`Fetch failed: cannot reach ${e.url} (reason: ${n.message}).\nCheck the URL or try again later.`):n}}const T=`tboxcode-webfetch`,E=[`tools`],D=t.object({maxLines:t.number().int().positive().optional(),maxBytes:t.number().int().positive().optional()});function O(t,n={}){let r=D.parse(n),i=t.get(`spillStore`),a=i===void 0?void 0:async(e,t)=>{let n=t.agent?.session.header.id;if(n===void 0)return;let r=await i.saveText({owner:{sessionId:n},source:{kind:`tool`,toolName:`webfetch`,callId:t.callId,label:`result`},suggestedName:`web_fetch.txt`,content:e});return String(r.locator)},o={max_lines:r.maxLines,max_bytes:r.maxBytes,spillSink:a},s=e({name:`webfetch`,description:`Fetch content from a URL and return it as text, markdown, or HTML. Markdown is the default. HTML→markdown strips script/style/meta/link. HTML→text strips all non-content tags via htmlparser2. Follows HTTP 3xx and JS/<meta refresh> redirects (up to 3 hops). Retries Cloudflare bot challenges with an honest User-Agent. Rejects images and non-textual content types.`,parameters:{url:{type:`string`,required:!0,description:`The URL to fetch (https only)`},format:{type:`string`,enum:[`text`,`markdown`,`html`],description:`Output format; defaults to markdown`},timeout:{type:`number`,description:`Timeout in seconds; defaults to 30, max 120`}},output:{schema:{type:`string`},render:(e,t)=>[{type:`text`,text:t}]},async execute(e,t){return w(e,t,o)}});return t.tools.register(s)}export{n as a,h as c,r as i,S as l,E as n,y as o,T as r,w as s,O as t,C as u};
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@tbox.cn/tboxcode-dsh-tools",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-rc.1",
4
+ "type": "module",
5
+ "description": "tboxcode 工具插件族:webfetch(截断链三分支)/ skill provider(rank 50 cwd-up)/ restrict / pre-execute 拦截器 / scratch 三件",
6
+ "main": "lib/index.js",
7
+ "types": "lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "default": "./lib/index.js"
12
+ },
13
+ "./webfetch": {
14
+ "types": "./lib/webfetch/index.d.ts",
15
+ "default": "./lib/webfetch/index.js"
16
+ },
17
+ "./skill-provider": {
18
+ "types": "./lib/skill-provider/index.d.ts",
19
+ "default": "./lib/skill-provider/index.js"
20
+ },
21
+ "./restrict": {
22
+ "types": "./lib/restrict/index.d.ts",
23
+ "default": "./lib/restrict/index.js"
24
+ },
25
+ "./pre-execute": {
26
+ "types": "./lib/pre-execute/index.d.ts",
27
+ "default": "./lib/pre-execute/index.js"
28
+ },
29
+ "./scratch": {
30
+ "types": "./lib/scratch/index.d.ts",
31
+ "default": "./lib/scratch/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "lib"
36
+ ],
37
+ "peerDependencies": {
38
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2"
39
+ },
40
+ "dependencies": {
41
+ "htmlparser2": "^10.0.0",
42
+ "turndown": "7.2.4",
43
+ "yaml": "^2.9.1",
44
+ "zod": "4.6.7"
45
+ },
46
+ "devDependencies": {
47
+ "@deepseek-ai/cordis": "4.0.5",
48
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
49
+ "@types/turndown": "^5.0.5"
50
+ },
51
+ "dsh": {
52
+ "bundle": {
53
+ "patch": []
54
+ }
55
+ },
56
+ "scripts": {
57
+ "build": "tsdown",
58
+ "typecheck": "tsc --noEmit -p tsconfig.json"
59
+ }
6
60
  }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.