@git.zone/tsagent 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +46 -0
- package/cli.js +4 -0
- package/dist_ts/00_commitinfo_data.d.ts +8 -0
- package/dist_ts/00_commitinfo_data.js +9 -0
- package/dist_ts/classes.tsagent.d.ts +79 -0
- package/dist_ts/classes.tsagent.js +346 -0
- package/dist_ts/cli.d.ts +3 -0
- package/dist_ts/cli.js +129 -0
- package/dist_ts/index.d.ts +3 -0
- package/dist_ts/index.js +4 -0
- package/dist_ts/paths.d.ts +2 -0
- package/dist_ts/paths.js +4 -0
- package/dist_ts/plugins.d.ts +14 -0
- package/dist_ts/plugins.js +17 -0
- package/dist_ts/tools.project.d.ts +2 -0
- package/dist_ts/tools.project.js +126 -0
- package/license.md +21 -0
- package/package.json +68 -0
- package/readme.md +126 -0
- package/ts/00_commitinfo_data.ts +8 -0
- package/ts/classes.tsagent.ts +424 -0
- package/ts/cli.ts +133 -0
- package/ts/index.ts +3 -0
- package/ts/paths.ts +4 -0
- package/ts/plugins.ts +30 -0
- package/ts/tools.project.ts +143 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import * as plugins from './plugins.js';
|
|
2
|
+
|
|
3
|
+
const defaultDeniedSegments = new Set([
|
|
4
|
+
'.cache',
|
|
5
|
+
'.git',
|
|
6
|
+
'.next',
|
|
7
|
+
'.nogit',
|
|
8
|
+
'.rpt2_cache',
|
|
9
|
+
'build',
|
|
10
|
+
'coverage',
|
|
11
|
+
'dist',
|
|
12
|
+
'node_modules',
|
|
13
|
+
'out',
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const defaultDeniedBasenames = new Set([
|
|
17
|
+
'.npmrc',
|
|
18
|
+
'bun.lockb',
|
|
19
|
+
'credentials.json',
|
|
20
|
+
'deno.lock',
|
|
21
|
+
'npm-shrinkwrap.json',
|
|
22
|
+
'package-lock.json',
|
|
23
|
+
'pnpm-lock.yaml',
|
|
24
|
+
'yarn.lock',
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const assertAllowedRelativePath = (relativePath: string): void => {
|
|
28
|
+
const normalized = relativePath.split(plugins.path.sep).join('/');
|
|
29
|
+
const parts = normalized.split('/').filter(Boolean);
|
|
30
|
+
const basename = parts.at(-1) ?? normalized;
|
|
31
|
+
|
|
32
|
+
if (basename === '.env' || basename.startsWith('.env.')) {
|
|
33
|
+
throw new Error(`Access denied: ${relativePath} may contain environment secrets.`);
|
|
34
|
+
}
|
|
35
|
+
if (basename.endsWith('.pem') || basename.endsWith('.key') || basename.endsWith('.p12')) {
|
|
36
|
+
throw new Error(`Access denied: ${relativePath} may contain private key material.`);
|
|
37
|
+
}
|
|
38
|
+
if (defaultDeniedBasenames.has(basename)) {
|
|
39
|
+
throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
|
|
40
|
+
}
|
|
41
|
+
for (const segment of parts) {
|
|
42
|
+
if (defaultDeniedSegments.has(segment) || segment.startsWith('dist_')) {
|
|
43
|
+
throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const realpathIfExists = async (pathArg: string): Promise<string> => {
|
|
49
|
+
try {
|
|
50
|
+
return await plugins.fs.realpath(pathArg);
|
|
51
|
+
} catch {
|
|
52
|
+
return plugins.path.resolve(pathArg);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const resolveAllowedPath = async (rootDirArg: string, inputPathArg: string): Promise<{
|
|
57
|
+
resolvedPath: string;
|
|
58
|
+
relativePath: string;
|
|
59
|
+
}> => {
|
|
60
|
+
const rootReal = await plugins.fs.realpath(plugins.path.resolve(rootDirArg));
|
|
61
|
+
const resolvedCandidate = plugins.path.resolve(
|
|
62
|
+
plugins.path.isAbsolute(inputPathArg)
|
|
63
|
+
? inputPathArg
|
|
64
|
+
: plugins.path.join(rootReal, inputPathArg),
|
|
65
|
+
);
|
|
66
|
+
const resolvedReal = await realpathIfExists(resolvedCandidate);
|
|
67
|
+
|
|
68
|
+
if (resolvedReal !== rootReal && !resolvedReal.startsWith(`${rootReal}${plugins.path.sep}`)) {
|
|
69
|
+
throw new Error(`Access denied: "${inputPathArg}" is outside allowed root "${rootDirArg}"`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const relativePath = plugins.path.relative(rootReal, resolvedReal) || '.';
|
|
73
|
+
assertAllowedRelativePath(relativePath);
|
|
74
|
+
return { resolvedPath: resolvedReal, relativePath };
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const listDirectory = async (rootDirArg: string, dirPathArg: string, recursive = false): Promise<string[]> => {
|
|
78
|
+
const { resolvedPath } = await resolveAllowedPath(rootDirArg, dirPathArg);
|
|
79
|
+
const entries = await plugins.fs.readdir(resolvedPath, { withFileTypes: true });
|
|
80
|
+
const result: string[] = [];
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const childPath = plugins.path.join(resolvedPath, entry.name);
|
|
83
|
+
const childRelativeInput = plugins.path.relative(rootDirArg, childPath) || entry.name;
|
|
84
|
+
try {
|
|
85
|
+
const allowed = await resolveAllowedPath(rootDirArg, childRelativeInput);
|
|
86
|
+
const stat = await plugins.fs.stat(allowed.resolvedPath);
|
|
87
|
+
result.push(`${allowed.relativePath}${stat.isDirectory() ? '/' : ''}`);
|
|
88
|
+
if (recursive && stat.isDirectory()) {
|
|
89
|
+
result.push(...await listDirectory(rootDirArg, allowed.relativePath, true));
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result.sort();
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const createReadOnlyProjectTools = async (rootDirArg: string): Promise<plugins.smartai.ToolSet> => {
|
|
99
|
+
const rootDir = await plugins.fs.realpath(plugins.path.resolve(rootDirArg));
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
read_file: plugins.smartagent.tool({
|
|
103
|
+
description: 'Read file contents within the project. Secret and generated paths are blocked.',
|
|
104
|
+
inputSchema: plugins.smartagent.z.object({
|
|
105
|
+
path: plugins.smartagent.z.string().describe('Absolute or project-relative path to the file'),
|
|
106
|
+
startLine: plugins.smartagent.z.number().optional().describe('First line, 1-indexed and inclusive'),
|
|
107
|
+
endLine: plugins.smartagent.z.number().optional().describe('Last line, 1-indexed and inclusive'),
|
|
108
|
+
}),
|
|
109
|
+
execute: async ({ path: filePath, startLine, endLine }: {
|
|
110
|
+
path: string;
|
|
111
|
+
startLine?: number;
|
|
112
|
+
endLine?: number;
|
|
113
|
+
}) => {
|
|
114
|
+
const { resolvedPath } = await resolveAllowedPath(rootDir, filePath);
|
|
115
|
+
const stat = await plugins.fs.stat(resolvedPath);
|
|
116
|
+
if (!stat.isFile()) {
|
|
117
|
+
throw new Error(`Cannot read non-file path: ${filePath}`);
|
|
118
|
+
}
|
|
119
|
+
const content = await plugins.fs.readFile(resolvedPath, 'utf8');
|
|
120
|
+
const selectedContent = startLine !== undefined || endLine !== undefined
|
|
121
|
+
? content.split('\n').slice((startLine ?? 1) - 1, endLine).join('\n')
|
|
122
|
+
: content;
|
|
123
|
+
return plugins.smartagent.truncateOutput(selectedContent).content;
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
list_directory: plugins.smartagent.tool({
|
|
127
|
+
description: 'List project files and directories. Secret and generated paths are omitted.',
|
|
128
|
+
inputSchema: plugins.smartagent.z.object({
|
|
129
|
+
path: plugins.smartagent.z.string().describe('Absolute or project-relative directory path to list'),
|
|
130
|
+
recursive: plugins.smartagent.z.boolean().optional().describe('List recursively'),
|
|
131
|
+
}),
|
|
132
|
+
execute: async ({ path: dirPath, recursive }: { path: string; recursive?: boolean }) => {
|
|
133
|
+
const { resolvedPath } = await resolveAllowedPath(rootDir, dirPath);
|
|
134
|
+
const stat = await plugins.fs.stat(resolvedPath);
|
|
135
|
+
if (!stat.isDirectory()) {
|
|
136
|
+
throw new Error(`Cannot list non-directory path: ${dirPath}`);
|
|
137
|
+
}
|
|
138
|
+
const entries = await listDirectory(rootDir, dirPath, recursive === true);
|
|
139
|
+
return plugins.smartagent.truncateOutput(entries.join('\n')).content;
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
};
|
|
143
|
+
};
|