@git.zone/tsdoc 2.0.6 → 2.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.
@@ -3,70 +3,18 @@ import * as plugins from './plugins.js';
3
3
  import * as aiDocsClasses from './aidocs_classes/index.js';
4
4
  import { logger } from './logging.js';
5
5
 
6
- export type TAiDocAuthMode = 'auto' | 'chatgpt' | 'apiKey';
6
+ export type TAiDocAuthMode = plugins.tsagent.TTsAgentAuthMode;
7
7
 
8
- export interface IAiDocConfig {
9
- provider?: plugins.smartai.TProvider;
10
- model?: string;
11
- authMode?: TAiDocAuthMode;
12
- chatGptAuthSources?: plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[];
13
- chatGptAuthWriteBack?: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
14
- providerOptions?: plugins.smartai.TSmartAiProviderOptions;
15
- cache?: plugins.smartagent.TAgentCacheSetting;
16
- promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
17
- apiKey?: string;
18
- baseUrl?: string;
19
- ollamaOptions?: plugins.smartai.IOllamaModelOptions;
20
- }
8
+ export interface IAiDocConfig extends plugins.tsagent.ITsAgentConfig {}
21
9
 
22
- export interface IAiDocResolvedConfig extends Required<Pick<IAiDocConfig,
23
- 'provider' | 'model' | 'authMode' | 'chatGptAuthSources'
24
- >> {
25
- chatGptAuthWriteBack: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
26
- providerOptions?: plugins.smartai.TSmartAiProviderOptions;
27
- cache: plugins.smartagent.TAgentCacheSetting;
28
- promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
29
- apiKey?: string;
30
- baseUrl?: string;
31
- ollamaOptions?: plugins.smartai.IOllamaModelOptions;
32
- }
10
+ export interface IAiDocResolvedConfig extends plugins.tsagent.ITsAgentResolvedConfig {}
33
11
 
34
12
  export const defaultAiDocConfig: IAiDocResolvedConfig = {
35
- provider: 'openai',
36
- model: 'gpt-5.5',
37
- authMode: 'auto',
38
- chatGptAuthSources: ['opencode', 'codex', 'smartai'],
39
- chatGptAuthWriteBack: {
40
- smartai: true,
41
- opencode: false,
42
- codex: false,
43
- },
44
- providerOptions: {
45
- openai: {
46
- reasoningEffort: 'xhigh',
47
- },
48
- },
49
- cache: 'auto',
13
+ ...plugins.tsagent.defaultTsAgentConfig,
50
14
  };
51
15
 
52
- const isPlainObject = (value: unknown): value is Record<string, any> => {
53
- return typeof value === 'object' && value !== null && !Array.isArray(value);
54
- };
55
-
56
- const mergeProviderOptions = (
57
- base?: plugins.smartai.TSmartAiProviderOptions,
58
- override?: plugins.smartai.TSmartAiProviderOptions,
59
- ): plugins.smartai.TSmartAiProviderOptions | undefined => {
60
- if (!base && !override) return undefined;
61
- const result: Record<string, any> = { ...(base ?? {}) };
62
- for (const [provider, options] of Object.entries(override ?? {})) {
63
- result[provider] = {
64
- ...(isPlainObject(result[provider]) ? result[provider] : {}),
65
- ...(isPlainObject(options) ? options : {}),
66
- };
67
- }
68
- return result as plugins.smartai.TSmartAiProviderOptions;
69
- };
16
+ type TAiDocRunAgentOptions = Parameters<plugins.tsagent.TsAgent['runAgent']>[0];
17
+ type TAiDocRunAgentResult = Awaited<ReturnType<plugins.tsagent.TsAgent['runAgent']>>;
70
18
 
71
19
  const normalizeAuthMode = (value: unknown): TAiDocAuthMode | undefined => {
72
20
  return value === 'auto' || value === 'chatgpt' || value === 'apiKey' ? value : undefined;
@@ -93,46 +41,29 @@ const normalizeOpenAiReasoningEffort = (value: unknown): plugins.smartai.TOpenAi
93
41
  : undefined;
94
42
  };
95
43
 
96
- const getProjectTsdocConfig = async (projectDirArg: string): Promise<IAiDocConfig> => {
97
- const smartconfigPath = plugins.path.join(projectDirArg, '.smartconfig.json');
98
- if (!(await plugins.fsInstance.file(smartconfigPath).exists())) {
99
- return {};
100
- }
101
- try {
102
- const smartconfigJson = JSON.parse(String(await plugins.fsInstance.file(smartconfigPath).encoding('utf8').read()));
103
- const legacyConfig = isPlainObject(smartconfigJson.tsdoc) ? smartconfigJson.tsdoc : {};
104
- const namespacedConfig = isPlainObject(smartconfigJson['@git.zone/tsdoc']) ? smartconfigJson['@git.zone/tsdoc'] : {};
105
- const config = {
106
- ...legacyConfig,
107
- ...namespacedConfig,
108
- ...(isPlainObject(legacyConfig.ai) ? legacyConfig.ai : {}),
109
- ...(isPlainObject(namespacedConfig.ai) ? namespacedConfig.ai : {}),
110
- };
111
- return config as IAiDocConfig;
112
- } catch (error) {
113
- throw new Error(`Could not parse .smartconfig.json: ${error instanceof Error ? error.message : String(error)}`);
114
- }
115
- };
116
-
117
44
  export class AiDoc {
45
+ private tsAgent?: plugins.tsagent.TsAgent;
118
46
  private openaiToken = '';
119
47
 
120
48
  public smartconfigKV?: plugins.smartconfig.KeyValueStore<{ OPENAI_TOKEN: string }>;
121
- public qenvInstance!: plugins.qenv.Qenv;
122
- public aidocInteract!: plugins.smartinteract.SmartInteract;
123
- public model!: plugins.smartai.LanguageModelV3;
124
- public providerOptions?: plugins.smartai.TSmartAiProviderOptions;
49
+ public qenvInstance?: plugins.qenv.Qenv;
50
+ public aidocInteract?: plugins.smartinteract.SmartInteract;
51
+ public model!: plugins.tsagent.TsAgent['model'];
52
+ public providerOptions?: IAiDocResolvedConfig['providerOptions'];
125
53
  public config: IAiDocResolvedConfig = defaultAiDocConfig;
126
54
  public selectedAuthSource: string = 'none';
127
55
 
128
- argvArg: any;
56
+ public argvArg: any;
129
57
 
130
58
  constructor(argvArg?: any) {
131
- this.argvArg = argvArg;
59
+ this.argvArg = argvArg || {};
132
60
  }
133
61
 
134
- private get isInteractive(): boolean {
135
- return process.stdin.isTTY === true && process.stdout.isTTY === true && !this.argvArg?.ci && !this.argvArg?.json;
62
+ private get activeTsAgent(): plugins.tsagent.TsAgent {
63
+ if (!this.tsAgent) {
64
+ throw new Error('AiDoc.start() must be called before running AI tasks.');
65
+ }
66
+ return this.tsAgent;
136
67
  }
137
68
 
138
69
  private async migrateKeyValueStore(): Promise<void> {
@@ -152,201 +83,65 @@ export class AiDoc {
152
83
  }
153
84
  }
154
85
 
155
- private async getStoredOpenAiToken(): Promise<string | undefined> {
156
- if (!this.smartconfigKV) return undefined;
157
- try {
158
- const token = await this.smartconfigKV.readKey('OPENAI_TOKEN');
159
- return token || undefined;
160
- } catch {
161
- return undefined;
162
- }
163
- }
164
-
165
- private async resolveApiKey(provider: plugins.smartai.TProvider): Promise<string | undefined> {
166
- const argvToken = this.argvArg?.OPENAI_TOKEN || this.argvArg?.OPENAI_API_KEY || this.argvArg?.apiKey;
167
- if (argvToken) return argvToken;
168
- if (this.config.apiKey) return this.config.apiKey;
169
-
170
- const envNames = provider === 'openai'
171
- ? ['OPENAI_TOKEN', 'OPENAI_API_KEY']
172
- : [`${provider.toUpperCase()}_TOKEN`, `${provider.toUpperCase()}_API_KEY`];
173
- for (const envName of envNames) {
174
- const value = await this.qenvInstance.getEnvVarOnDemand(envName);
175
- if (value) return value;
176
- }
177
-
178
- if (provider === 'openai') {
179
- const storedToken = await this.getStoredOpenAiToken();
180
- if (storedToken) return storedToken;
181
- }
182
-
183
- return undefined;
184
- }
185
-
186
- private async promptForOpenAiToken(): Promise<string> {
187
- if (!this.isInteractive) {
188
- throw new Error('OpenAI API key is required. Set OPENAI_TOKEN/OPENAI_API_KEY or configure ChatGPT subscription auth.');
189
- }
190
- await plugins.smartdelay.delayFor(1000);
191
- const answerObject = await this.aidocInteract.askQuestion({
192
- type: 'input',
193
- message: 'Please provide your OpenAI token. This will be persisted in your home directory.',
194
- name: 'OPENAI_TOKEN',
195
- default: '',
196
- });
197
- const token = answerObject.value;
198
- await this.smartconfigKV?.writeKey('OPENAI_TOKEN', token);
199
- return token;
200
- }
201
-
202
- private async runChatGptDeviceLogin(): Promise<plugins.smartai.IOpenAiChatGptTokenData> {
203
- if (!this.isInteractive) {
204
- throw new Error('ChatGPT subscription auth is required, but no usable auth file was found in non-interactive mode.');
205
- }
206
-
207
- const deviceCode = await plugins.smartai.requestOpenAiChatGptDeviceCode();
208
- console.log(`Open ${deviceCode.verificationUrl} and enter code ${deviceCode.userCode}`);
209
- const tokenData = await plugins.smartai.completeOpenAiChatGptDeviceCodeLogin(deviceCode);
210
- const authFilePath = plugins.smartaiOpenAiChatGptAuth.getDefaultOpenAiChatGptAuthPath('smartai');
211
- await plugins.smartaiOpenAiChatGptAuth.writeOpenAiChatGptAuthFile(authFilePath, tokenData, 'smartai');
212
- return tokenData;
213
- }
214
-
215
- private async resolveConfig(): Promise<IAiDocResolvedConfig> {
216
- const projectConfig = await getProjectTsdocConfig(process.cwd());
86
+ private async resolveTsdocEnvConfig(): Promise<IAiDocConfig> {
87
+ this.qenvInstance = new plugins.qenv.Qenv();
217
88
  const envProvider = normalizeProvider(await this.qenvInstance.getEnvVarOnDemand('TSDOC_AI_PROVIDER'));
218
89
  const envModel = await this.qenvInstance.getEnvVarOnDemand('TSDOC_AI_MODEL');
219
90
  const envAuthMode = normalizeAuthMode(await this.qenvInstance.getEnvVarOnDemand('TSDOC_AUTH_MODE'));
220
91
  const envChatGptSources = normalizeChatGptSources(await this.qenvInstance.getEnvVarOnDemand('TSDOC_CHATGPT_AUTH_SOURCES'));
221
92
  const envReasoningEffort = normalizeOpenAiReasoningEffort(await this.qenvInstance.getEnvVarOnDemand('TSDOC_OPENAI_REASONING_EFFORT'));
222
- const envProviderOptions = envReasoningEffort ? {
223
- openai: {
224
- reasoningEffort: envReasoningEffort,
225
- },
226
- } as plugins.smartai.TSmartAiProviderOptions : undefined;
227
- const argvConfig: IAiDocConfig = {
228
- provider: normalizeProvider(this.argvArg?.provider),
229
- model: typeof this.argvArg?.model === 'string' ? this.argvArg.model : undefined,
230
- authMode: normalizeAuthMode(this.argvArg?.authMode),
231
- chatGptAuthSources: normalizeChatGptSources(this.argvArg?.chatGptAuthSources),
232
- };
233
- const provider = argvConfig.provider ?? envProvider ?? normalizeProvider(projectConfig.provider) ?? defaultAiDocConfig.provider;
234
- const model = argvConfig.model ?? envModel ?? projectConfig.model ?? defaultAiDocConfig.model;
235
- const authMode = argvConfig.authMode ?? envAuthMode ?? normalizeAuthMode(projectConfig.authMode) ?? defaultAiDocConfig.authMode;
236
- const chatGptAuthSources = argvConfig.chatGptAuthSources
237
- ?? envChatGptSources
238
- ?? normalizeChatGptSources(projectConfig.chatGptAuthSources)
239
- ?? defaultAiDocConfig.chatGptAuthSources;
240
- const providerOptions = mergeProviderOptions(
241
- mergeProviderOptions(defaultAiDocConfig.providerOptions, projectConfig.providerOptions),
242
- envProviderOptions,
243
- );
244
-
245
93
  return {
246
- ...defaultAiDocConfig,
247
- ...projectConfig,
248
- provider,
249
- model,
250
- authMode,
251
- chatGptAuthSources,
252
- chatGptAuthWriteBack: {
253
- ...defaultAiDocConfig.chatGptAuthWriteBack,
254
- ...(projectConfig.chatGptAuthWriteBack ?? {}),
255
- },
256
- providerOptions,
257
- cache: projectConfig.cache ?? defaultAiDocConfig.cache,
94
+ provider: envProvider,
95
+ model: envModel || undefined,
96
+ authMode: envAuthMode,
97
+ chatGptAuthSources: envChatGptSources,
98
+ providerOptions: envReasoningEffort ? {
99
+ openai: {
100
+ reasoningEffort: envReasoningEffort,
101
+ },
102
+ } : undefined,
258
103
  };
259
104
  }
260
105
 
261
106
  public async start() {
262
- this.aidocInteract = new plugins.smartinteract.SmartInteract();
263
- this.qenvInstance = new plugins.qenv.Qenv();
264
107
  await this.migrateKeyValueStore();
265
108
  this.smartconfigKV = new plugins.smartconfig.KeyValueStore({
266
109
  typeArg: 'userHomeDir',
267
110
  identityArg: '@git.zone/tsdoc',
268
111
  mandatoryKeys: ['OPENAI_TOKEN'],
269
112
  });
270
- this.config = await this.resolveConfig();
271
113
 
272
- let openAiChatGptAuth: plugins.smartai.IOpenAiChatGptTokenData | undefined;
273
- if (this.config.provider === 'openai' && this.config.authMode !== 'apiKey') {
274
- const resolvedAuth = await plugins.smartaiOpenAiChatGptAuth.resolveOpenAiChatGptAuth({
275
- sources: this.config.chatGptAuthSources,
276
- refresh: 'ifNeeded',
277
- writeBack: this.config.chatGptAuthWriteBack,
278
- });
279
- if (resolvedAuth) {
280
- openAiChatGptAuth = resolvedAuth.tokenData;
281
- this.selectedAuthSource = resolvedAuth.source;
282
- logger.log('info', `Using OpenAI ChatGPT auth from ${resolvedAuth.source}.`);
283
- } else if (this.config.authMode === 'chatgpt') {
284
- openAiChatGptAuth = await this.runChatGptDeviceLogin();
285
- this.selectedAuthSource = 'smartai';
286
- }
287
- }
288
-
289
- let apiKey: string | undefined;
290
- if (!openAiChatGptAuth) {
291
- apiKey = await this.resolveApiKey(this.config.provider);
292
- if (!apiKey && this.config.provider === 'openai' && this.config.authMode === 'auto' && this.isInteractive) {
293
- openAiChatGptAuth = await this.runChatGptDeviceLogin();
294
- this.selectedAuthSource = 'smartai';
295
- }
296
- if (!apiKey && !openAiChatGptAuth && this.config.provider === 'openai' && this.config.authMode !== 'chatgpt') {
297
- apiKey = await this.promptForOpenAiToken();
298
- }
299
- if (apiKey) {
300
- this.selectedAuthSource = 'apiKey';
301
- if (this.config.provider === 'openai') {
302
- this.openaiToken = apiKey;
303
- }
304
- }
305
- }
306
-
307
- const setup = plugins.smartai.getModelSetup({
308
- provider: this.config.provider,
309
- model: this.config.model,
310
- apiKey,
311
- openAiChatGptAuth,
312
- providerOptions: this.config.providerOptions,
313
- promptCaching: this.config.promptCaching,
314
- baseUrl: this.config.baseUrl,
315
- ollamaOptions: this.config.ollamaOptions,
114
+ this.tsAgent = new plugins.tsagent.TsAgent({
115
+ argvArg: this.argvArg,
116
+ projectDir: process.cwd(),
117
+ projectConfigKeys: ['@git.zone/tsagent', 'tsdoc', '@git.zone/tsdoc'],
118
+ legacyTokenStoreIdentities: ['@git.zone/tsdoc'],
119
+ config: await this.resolveTsdocEnvConfig(),
316
120
  });
317
- this.model = setup.model;
318
- this.providerOptions = setup.providerOptions;
121
+ await this.tsAgent.start();
122
+
123
+ this.model = this.tsAgent.model;
124
+ this.providerOptions = this.tsAgent.providerOptions;
125
+ this.config = this.tsAgent.config as IAiDocResolvedConfig;
126
+ this.selectedAuthSource = this.tsAgent.selectedAuthSource;
127
+ this.openaiToken = this.tsAgent.getOpenaiToken();
128
+ if (this.selectedAuthSource !== 'none') {
129
+ logger.log('info', `Using AI auth from ${this.selectedAuthSource}.`);
130
+ }
319
131
  }
320
132
 
321
133
  public async stop() {
322
- // No lifecycle management needed with getModel() API
134
+ await this.tsAgent?.stop();
323
135
  }
324
136
 
325
137
  public getOpenaiToken(): string {
326
138
  return this.openaiToken;
327
139
  }
328
140
 
329
- public async runAgent(options: Omit<plugins.smartagent.IAgentRunOptions, 'model'> & {
330
- projectDir: string;
331
- taskName: string;
332
- useCompaction?: boolean;
333
- }): Promise<plugins.smartagent.IAgentRunResult> {
334
- const providerOptions = this.config.provider === 'openai' && this.selectedAuthSource !== 'apiKey' && options.system
335
- ? mergeProviderOptions(this.providerOptions, {
336
- openai: {
337
- instructions: options.system,
338
- },
339
- })
340
- : this.providerOptions;
341
- const result = await plugins.smartagent.runAgent({
141
+ public async runAgent(options: TAiDocRunAgentOptions): Promise<TAiDocRunAgentResult> {
142
+ const result = await this.activeTsAgent.runAgent({
342
143
  ...options,
343
- model: this.model,
344
- providerOptions,
345
- cache: this.config.cache,
346
144
  sessionId: options.sessionId ?? `tsdoc:${options.taskName}:${plugins.path.resolve(options.projectDir)}`,
347
- onContextOverflow: options.useCompaction
348
- ? async (messages) => plugins.smartagentCompaction.compactMessages(this.model, messages)
349
- : options.onContextOverflow,
350
145
  });
351
146
  logger.log(
352
147
  'info',
package/ts/cli.ts CHANGED
@@ -6,12 +6,6 @@ import { TypeDoc } from './classes.typedoc.js';
6
6
  import { AiDoc } from './classes.aidoc.js';
7
7
  import { NoChangesError } from './aidocs_classes/commit.js';
8
8
 
9
- const defaultChatGptAuthSources: plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[] = [
10
- 'opencode',
11
- 'codex',
12
- 'smartai',
13
- ];
14
-
15
9
  const createAiDoc = async (argvArg: any): Promise<AiDoc> => {
16
10
  const aidocInstance = new AiDoc(argvArg);
17
11
  await aidocInstance.start();
@@ -27,18 +21,11 @@ const getSmartcliArgv = (): string[] | undefined => {
27
21
  const handleAuthCommand = async (argvArg: any): Promise<void> => {
28
22
  const subcommand = argvArg._?.[1] ?? 'status';
29
23
  if (subcommand === 'login') {
30
- const deviceCode = await plugins.smartai.requestOpenAiChatGptDeviceCode();
31
- console.log(`Open ${deviceCode.verificationUrl} and enter code ${deviceCode.userCode}`);
32
- const tokenData = await plugins.smartai.completeOpenAiChatGptDeviceCodeLogin(deviceCode);
33
- const authFilePath = plugins.smartaiOpenAiChatGptAuth.getDefaultOpenAiChatGptAuthPath('smartai');
34
- await plugins.smartaiOpenAiChatGptAuth.writeOpenAiChatGptAuthFile(authFilePath, tokenData, 'smartai');
35
- logger.log('success', `Stored OpenAI ChatGPT auth at ${authFilePath}`);
24
+ await plugins.tsagent.loginChatGptAuth();
36
25
  return;
37
26
  }
38
27
 
39
- const inspections = await plugins.smartaiOpenAiChatGptAuth.inspectOpenAiChatGptAuthSources({
40
- sources: defaultChatGptAuthSources,
41
- });
28
+ const inspections = await plugins.tsagent.inspectChatGptAuthSources();
42
29
  console.log('OpenAI ChatGPT auth sources:');
43
30
  for (const inspection of inspections) {
44
31
  const status = inspection.usable
@@ -1,125 +1,5 @@
1
1
  import * as plugins from './plugins.js';
2
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 normalizeRelativePath = (rootDir: string, inputPath: string): string => {
28
- const resolvedRoot = plugins.path.resolve(rootDir);
29
- const resolvedPath = plugins.path.resolve(
30
- plugins.path.isAbsolute(inputPath) ? inputPath : plugins.path.join(resolvedRoot, inputPath),
31
- );
32
- if (resolvedPath !== resolvedRoot && !resolvedPath.startsWith(`${resolvedRoot}${plugins.path.sep}`)) {
33
- throw new Error(`Access denied: "${inputPath}" is outside allowed root "${rootDir}"`);
34
- }
35
-
36
- const relativePath = plugins.path.relative(resolvedRoot, resolvedPath) || '.';
37
- assertAllowedRelativePath(relativePath);
38
- return resolvedPath;
39
- };
40
-
41
- const assertAllowedRelativePath = (relativePath: string): void => {
42
- const normalized = relativePath.split(plugins.path.sep).join('/');
43
- const parts = normalized.split('/').filter(Boolean);
44
- const basename = parts.at(-1) ?? normalized;
45
-
46
- if (basename === '.env' || basename.startsWith('.env.')) {
47
- throw new Error(`Access denied: ${relativePath} may contain environment secrets.`);
48
- }
49
- if (basename.endsWith('.pem') || basename.endsWith('.key') || basename.endsWith('.p12')) {
50
- throw new Error(`Access denied: ${relativePath} may contain private key material.`);
51
- }
52
- if (defaultDeniedBasenames.has(basename)) {
53
- throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
54
- }
55
- for (const segment of parts) {
56
- if (defaultDeniedSegments.has(segment) || segment.startsWith('dist_')) {
57
- throw new Error(`Access denied: ${relativePath} is excluded from AI file access.`);
58
- }
59
- }
60
- };
61
-
62
- const listDirectory = async (dirPath: string, recursive = false, prefix = ''): Promise<string[]> => {
63
- const entries = await plugins.fs.readdir(dirPath, { withFileTypes: true });
64
- const result: string[] = [];
65
- for (const entry of entries) {
66
- const relativeEntryPath = prefix ? `${prefix}/${entry.name}` : entry.name;
67
- try {
68
- assertAllowedRelativePath(relativeEntryPath);
69
- } catch {
70
- continue;
71
- }
72
- result.push(`${relativeEntryPath}${entry.isDirectory() ? '/' : ''}`);
73
- if (recursive && entry.isDirectory()) {
74
- result.push(...await listDirectory(plugins.path.join(dirPath, entry.name), true, relativeEntryPath));
75
- }
76
- }
77
- return result;
78
- };
79
-
80
- export const createReadOnlyFileSystemTools = (rootDirArg: string): plugins.smartai.ToolSet => {
81
- const rootDir = plugins.path.resolve(rootDirArg);
82
-
83
- return {
84
- read_file: plugins.smartagent.tool({
85
- description: 'Read file contents within the project. Secret and generated paths are blocked.',
86
- inputSchema: plugins.smartagent.z.object({
87
- path: plugins.smartagent.z.string().describe('Absolute or project-relative path to the file'),
88
- startLine: plugins.smartagent.z.number().optional().describe('First line, 1-indexed and inclusive'),
89
- endLine: plugins.smartagent.z.number().optional().describe('Last line, 1-indexed and inclusive'),
90
- }),
91
- execute: async ({ path: filePath, startLine, endLine }: {
92
- path: string;
93
- startLine?: number;
94
- endLine?: number;
95
- }) => {
96
- const resolvedPath = normalizeRelativePath(rootDir, filePath);
97
- const stat = await plugins.fs.stat(resolvedPath);
98
- if (!stat.isFile()) {
99
- throw new Error(`Cannot read non-file path: ${filePath}`);
100
- }
101
- const content = await plugins.fs.readFile(resolvedPath, 'utf8');
102
- const selectedContent = startLine !== undefined || endLine !== undefined
103
- ? content.split('\n').slice((startLine ?? 1) - 1, endLine).join('\n')
104
- : content;
105
- return plugins.smartagent.truncateOutput(selectedContent).content;
106
- },
107
- }),
108
- list_directory: plugins.smartagent.tool({
109
- description: 'List project files and directories. Secret and generated paths are omitted.',
110
- inputSchema: plugins.smartagent.z.object({
111
- path: plugins.smartagent.z.string().describe('Absolute or project-relative directory path to list'),
112
- recursive: plugins.smartagent.z.boolean().optional().describe('List recursively'),
113
- }),
114
- execute: async ({ path: dirPath, recursive }: { path: string; recursive?: boolean }) => {
115
- const resolvedPath = normalizeRelativePath(rootDir, dirPath);
116
- const stat = await plugins.fs.stat(resolvedPath);
117
- if (!stat.isDirectory()) {
118
- throw new Error(`Cannot list non-directory path: ${dirPath}`);
119
- }
120
- const entries = await listDirectory(resolvedPath, recursive === true);
121
- return plugins.smartagent.truncateOutput(entries.join('\n')).content;
122
- },
123
- }),
124
- };
3
+ export const createReadOnlyFileSystemTools = async (rootDirArg: string) => {
4
+ return await plugins.tsagent.createReadOnlyProjectTools(rootDirArg);
125
5
  };
package/ts/plugins.ts CHANGED
@@ -53,9 +53,10 @@ export const fsInstance = new smartfs.SmartFs(smartFsNodeProvider);
53
53
  export const smartfileFactory = smartfile.SmartFileFactory.nodeFs();
54
54
 
55
55
  // @git.zone scope
56
+ import * as tsagent from '@git.zone/tsagent';
56
57
  import * as tspublish from '@git.zone/tspublish';
57
58
 
58
- export { tspublish };
59
+ export { tsagent, tspublish };
59
60
 
60
61
  // third party scope
61
62
  import * as typedoc from 'typedoc';