@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.
@@ -0,0 +1,424 @@
1
+ import * as plugins from './plugins.js';
2
+ import { createReadOnlyProjectTools } from './tools.project.js';
3
+
4
+ export type TTsAgentAuthMode = 'auto' | 'chatgpt' | 'apiKey';
5
+
6
+ export interface ITsAgentConfig {
7
+ provider?: plugins.smartai.TProvider;
8
+ model?: string;
9
+ authMode?: TTsAgentAuthMode;
10
+ chatGptAuthSources?: plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[];
11
+ chatGptAuthWriteBack?: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
12
+ providerOptions?: plugins.smartai.TSmartAiProviderOptions;
13
+ cache?: plugins.smartagent.TAgentCacheSetting;
14
+ promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
15
+ apiKey?: string;
16
+ baseUrl?: string;
17
+ ollamaOptions?: plugins.smartai.IOllamaModelOptions;
18
+ }
19
+
20
+ export interface ITsAgentResolvedConfig extends Required<Pick<ITsAgentConfig,
21
+ 'provider' | 'model' | 'authMode' | 'chatGptAuthSources'
22
+ >> {
23
+ chatGptAuthWriteBack: Partial<Record<plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource, boolean>>;
24
+ providerOptions?: plugins.smartai.TSmartAiProviderOptions;
25
+ cache: plugins.smartagent.TAgentCacheSetting;
26
+ promptCaching?: boolean | plugins.smartai.ISmartAiCacheOptions;
27
+ apiKey?: string;
28
+ baseUrl?: string;
29
+ ollamaOptions?: plugins.smartai.IOllamaModelOptions;
30
+ }
31
+
32
+ export interface ITsAgentOptions {
33
+ argvArg?: any;
34
+ projectDir?: string;
35
+ projectConfigKeys?: string[];
36
+ legacyTokenStoreIdentities?: string[];
37
+ config?: ITsAgentConfig;
38
+ }
39
+
40
+ export interface IRunJsonTaskOptions<T> {
41
+ taskName: string;
42
+ projectDir: string;
43
+ prompt: string;
44
+ system?: string;
45
+ tools?: plugins.smartai.ToolSet;
46
+ maxSteps?: number;
47
+ useCompaction?: boolean;
48
+ validate?: (value: T) => string | void;
49
+ }
50
+
51
+ export const defaultTsAgentConfig: ITsAgentResolvedConfig = {
52
+ provider: 'openai',
53
+ model: 'gpt-5.5',
54
+ authMode: 'auto',
55
+ chatGptAuthSources: ['opencode', 'codex', 'smartai'],
56
+ chatGptAuthWriteBack: {
57
+ smartai: true,
58
+ opencode: false,
59
+ codex: false,
60
+ },
61
+ providerOptions: {
62
+ openai: {
63
+ reasoningEffort: 'xhigh',
64
+ },
65
+ },
66
+ cache: 'auto',
67
+ };
68
+
69
+ export const defaultChatGptAuthSources = defaultTsAgentConfig.chatGptAuthSources;
70
+
71
+ const isPlainObject = (value: unknown): value is Record<string, any> => {
72
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
73
+ };
74
+
75
+ const mergeProviderOptions = (
76
+ base?: plugins.smartai.TSmartAiProviderOptions,
77
+ override?: plugins.smartai.TSmartAiProviderOptions,
78
+ ): plugins.smartai.TSmartAiProviderOptions | undefined => {
79
+ if (!base && !override) return undefined;
80
+ const result: Record<string, any> = { ...(base ?? {}) };
81
+ for (const [provider, options] of Object.entries(override ?? {})) {
82
+ result[provider] = {
83
+ ...(isPlainObject(result[provider]) ? result[provider] : {}),
84
+ ...(isPlainObject(options) ? options : {}),
85
+ };
86
+ }
87
+ return result as plugins.smartai.TSmartAiProviderOptions;
88
+ };
89
+
90
+ const normalizeAuthMode = (value: unknown): TTsAgentAuthMode | undefined => {
91
+ return value === 'auto' || value === 'chatgpt' || value === 'apiKey' ? value : undefined;
92
+ };
93
+
94
+ const normalizeProvider = (value: unknown): plugins.smartai.TProvider | undefined => {
95
+ return ['anthropic', 'openai', 'google', 'groq', 'mistral', 'xai', 'perplexity', 'ollama'].includes(String(value))
96
+ ? value as plugins.smartai.TProvider
97
+ : undefined;
98
+ };
99
+
100
+ const normalizeChatGptSources = (value: unknown): plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource[] | undefined => {
101
+ const input = typeof value === 'string' ? value.split(',').map(source => source.trim()) : value;
102
+ if (!Array.isArray(input)) return undefined;
103
+ const sources = input.filter((source): source is plugins.smartaiOpenAiChatGptAuth.TOpenAiChatGptAuthSource => {
104
+ return source === 'opencode' || source === 'codex' || source === 'smartai';
105
+ });
106
+ return sources.length > 0 ? sources : undefined;
107
+ };
108
+
109
+ const normalizeOpenAiReasoningEffort = (value: unknown): plugins.smartai.TOpenAiReasoningEffort | undefined => {
110
+ return ['none', 'minimal', 'low', 'medium', 'high', 'xhigh'].includes(String(value))
111
+ ? value as plugins.smartai.TOpenAiReasoningEffort
112
+ : undefined;
113
+ };
114
+
115
+ const readProjectConfig = async (projectDirArg: string, keysArg: string[]): Promise<ITsAgentConfig> => {
116
+ const smartconfigPath = plugins.path.join(projectDirArg, '.smartconfig.json');
117
+ try {
118
+ const raw = await plugins.fs.readFile(smartconfigPath, 'utf8');
119
+ const smartconfigJson = JSON.parse(raw);
120
+ const result: Record<string, any> = {};
121
+ for (const key of keysArg) {
122
+ const keyConfig = isPlainObject(smartconfigJson[key]) ? smartconfigJson[key] : {};
123
+ Object.assign(result, keyConfig);
124
+ if (isPlainObject(keyConfig.ai)) {
125
+ Object.assign(result, keyConfig.ai);
126
+ }
127
+ }
128
+ return result as ITsAgentConfig;
129
+ } catch (error) {
130
+ if ((error as { code?: string }).code === 'ENOENT') {
131
+ return {};
132
+ }
133
+ throw new Error(`Could not parse .smartconfig.json: ${error instanceof Error ? error.message : String(error)}`);
134
+ }
135
+ };
136
+
137
+ const parseJsonResult = <T>(textArg: string): T => {
138
+ const jsonString = textArg
139
+ .replace(/```json\n?/gi, '')
140
+ .replace(/```\n?/gi, '')
141
+ .match(/\{[\s\S]*\}|\[[\s\S]*\]/)?.[0] ?? textArg;
142
+ return JSON.parse(jsonString) as T;
143
+ };
144
+
145
+ export class TsAgent {
146
+ private readonly argvArg: any;
147
+ private readonly projectDir: string;
148
+ private readonly projectConfigKeys: string[];
149
+ private readonly legacyTokenStoreIdentities: string[];
150
+ private readonly configOverride?: ITsAgentConfig;
151
+ private openaiToken = '';
152
+ private tokenStore?: plugins.smartconfig.KeyValueStore<{ OPENAI_TOKEN: string }>;
153
+ private legacyTokenStores: Array<plugins.smartconfig.KeyValueStore<{ OPENAI_TOKEN: string }>> = [];
154
+ private qenvInstance!: plugins.qenv.Qenv;
155
+ private interact!: plugins.smartinteract.SmartInteract;
156
+
157
+ public model!: plugins.smartai.LanguageModelV3;
158
+ public providerOptions?: plugins.smartai.TSmartAiProviderOptions;
159
+ public config: ITsAgentResolvedConfig = defaultTsAgentConfig;
160
+ public selectedAuthSource = 'none';
161
+
162
+ constructor(optionsArg: ITsAgentOptions = {}) {
163
+ this.argvArg = optionsArg.argvArg || {};
164
+ this.projectDir = optionsArg.projectDir || process.cwd();
165
+ this.projectConfigKeys = optionsArg.projectConfigKeys || ['@git.zone/tsagent'];
166
+ this.legacyTokenStoreIdentities = optionsArg.legacyTokenStoreIdentities || [];
167
+ this.configOverride = optionsArg.config;
168
+ }
169
+
170
+ private get isInteractive(): boolean {
171
+ return process.stdin.isTTY === true && process.stdout.isTTY === true && !this.argvArg?.ci && !this.argvArg?.json;
172
+ }
173
+
174
+ private async readTokenFromStore(storeArg: plugins.smartconfig.KeyValueStore<{ OPENAI_TOKEN: string }>): Promise<string | undefined> {
175
+ try {
176
+ const token = await storeArg.readKey('OPENAI_TOKEN');
177
+ return token || undefined;
178
+ } catch {
179
+ return undefined;
180
+ }
181
+ }
182
+
183
+ private async getStoredOpenAiToken(): Promise<string | undefined> {
184
+ if (this.tokenStore) {
185
+ const token = await this.readTokenFromStore(this.tokenStore);
186
+ if (token) return token;
187
+ }
188
+ for (const store of this.legacyTokenStores) {
189
+ const token = await this.readTokenFromStore(store);
190
+ if (token) return token;
191
+ }
192
+ return undefined;
193
+ }
194
+
195
+ private async resolveApiKey(providerArg: plugins.smartai.TProvider): Promise<string | undefined> {
196
+ const argvToken = this.argvArg?.OPENAI_TOKEN || this.argvArg?.OPENAI_API_KEY || this.argvArg?.apiKey;
197
+ if (argvToken) return argvToken;
198
+ if (this.config.apiKey) return this.config.apiKey;
199
+
200
+ const envNames = providerArg === 'openai'
201
+ ? ['OPENAI_TOKEN', 'OPENAI_API_KEY']
202
+ : [`${providerArg.toUpperCase()}_TOKEN`, `${providerArg.toUpperCase()}_API_KEY`];
203
+ for (const envName of envNames) {
204
+ const value = await this.qenvInstance.getEnvVarOnDemand(envName);
205
+ if (value) return value;
206
+ }
207
+
208
+ if (providerArg === 'openai') {
209
+ return await this.getStoredOpenAiToken();
210
+ }
211
+ return undefined;
212
+ }
213
+
214
+ private async promptForOpenAiToken(): Promise<string> {
215
+ if (!this.isInteractive) {
216
+ throw new Error('OpenAI API key is required. Set OPENAI_TOKEN/OPENAI_API_KEY or configure ChatGPT subscription auth.');
217
+ }
218
+ await plugins.smartdelay.delayFor(1000);
219
+ const answerObject = await this.interact.askQuestion({
220
+ type: 'input',
221
+ message: 'Please provide your OpenAI token. This will be persisted in your home directory.',
222
+ name: 'OPENAI_TOKEN',
223
+ default: '',
224
+ });
225
+ const token = answerObject.value;
226
+ await this.tokenStore?.writeKey('OPENAI_TOKEN', token);
227
+ return token;
228
+ }
229
+
230
+ private async runChatGptDeviceLogin(): Promise<plugins.smartai.IOpenAiChatGptTokenData> {
231
+ if (!this.isInteractive) {
232
+ throw new Error('ChatGPT subscription auth is required, but no usable auth file was found in non-interactive mode.');
233
+ }
234
+ return await loginChatGptAuth();
235
+ }
236
+
237
+ private async resolveConfig(): Promise<ITsAgentResolvedConfig> {
238
+ const projectConfig = await readProjectConfig(this.projectDir, this.projectConfigKeys);
239
+ const envProvider = normalizeProvider(await this.qenvInstance.getEnvVarOnDemand('TSAGENT_AI_PROVIDER'));
240
+ const envModel = await this.qenvInstance.getEnvVarOnDemand('TSAGENT_AI_MODEL');
241
+ const envAuthMode = normalizeAuthMode(await this.qenvInstance.getEnvVarOnDemand('TSAGENT_AUTH_MODE'));
242
+ const envChatGptSources = normalizeChatGptSources(await this.qenvInstance.getEnvVarOnDemand('TSAGENT_CHATGPT_AUTH_SOURCES'));
243
+ const envReasoningEffort = normalizeOpenAiReasoningEffort(await this.qenvInstance.getEnvVarOnDemand('TSAGENT_OPENAI_REASONING_EFFORT'));
244
+ const envProviderOptions = envReasoningEffort ? {
245
+ openai: { reasoningEffort: envReasoningEffort },
246
+ } as plugins.smartai.TSmartAiProviderOptions : undefined;
247
+ const argvConfig: ITsAgentConfig = {
248
+ provider: normalizeProvider(this.argvArg?.provider),
249
+ model: typeof this.argvArg?.model === 'string' ? this.argvArg.model : undefined,
250
+ authMode: normalizeAuthMode(this.argvArg?.authMode),
251
+ chatGptAuthSources: normalizeChatGptSources(this.argvArg?.chatGptAuthSources),
252
+ };
253
+ const mergedConfig: ITsAgentConfig = {
254
+ ...projectConfig,
255
+ ...this.configOverride,
256
+ ...argvConfig,
257
+ };
258
+ const provider = argvConfig.provider ?? envProvider ?? normalizeProvider(mergedConfig.provider) ?? defaultTsAgentConfig.provider;
259
+ const model = argvConfig.model ?? envModel ?? mergedConfig.model ?? defaultTsAgentConfig.model;
260
+ const authMode = argvConfig.authMode ?? envAuthMode ?? normalizeAuthMode(mergedConfig.authMode) ?? defaultTsAgentConfig.authMode;
261
+ const chatGptAuthSources = argvConfig.chatGptAuthSources
262
+ ?? envChatGptSources
263
+ ?? normalizeChatGptSources(mergedConfig.chatGptAuthSources)
264
+ ?? defaultTsAgentConfig.chatGptAuthSources;
265
+ const providerOptions = mergeProviderOptions(
266
+ mergeProviderOptions(defaultTsAgentConfig.providerOptions, mergedConfig.providerOptions),
267
+ envProviderOptions,
268
+ );
269
+ return {
270
+ ...defaultTsAgentConfig,
271
+ ...mergedConfig,
272
+ provider,
273
+ model,
274
+ authMode,
275
+ chatGptAuthSources,
276
+ chatGptAuthWriteBack: {
277
+ ...defaultTsAgentConfig.chatGptAuthWriteBack,
278
+ ...(mergedConfig.chatGptAuthWriteBack ?? {}),
279
+ },
280
+ providerOptions,
281
+ cache: mergedConfig.cache ?? defaultTsAgentConfig.cache,
282
+ };
283
+ }
284
+
285
+ public async start(): Promise<void> {
286
+ this.interact = new plugins.smartinteract.SmartInteract();
287
+ this.qenvInstance = new plugins.qenv.Qenv();
288
+ this.tokenStore = new plugins.smartconfig.KeyValueStore({
289
+ typeArg: 'userHomeDir',
290
+ identityArg: '@git.zone/tsagent',
291
+ mandatoryKeys: ['OPENAI_TOKEN'],
292
+ });
293
+ this.legacyTokenStores = this.legacyTokenStoreIdentities.map(identityArg => new plugins.smartconfig.KeyValueStore({
294
+ typeArg: 'userHomeDir',
295
+ identityArg,
296
+ mandatoryKeys: ['OPENAI_TOKEN'],
297
+ }));
298
+ this.config = await this.resolveConfig();
299
+
300
+ let openAiChatGptAuth: plugins.smartai.IOpenAiChatGptTokenData | undefined;
301
+ if (this.config.provider === 'openai' && this.config.authMode !== 'apiKey') {
302
+ const resolvedAuth = await plugins.smartaiOpenAiChatGptAuth.resolveOpenAiChatGptAuth({
303
+ sources: this.config.chatGptAuthSources,
304
+ refresh: 'ifNeeded',
305
+ writeBack: this.config.chatGptAuthWriteBack,
306
+ });
307
+ if (resolvedAuth) {
308
+ openAiChatGptAuth = resolvedAuth.tokenData;
309
+ this.selectedAuthSource = resolvedAuth.source;
310
+ } else if (this.config.authMode === 'chatgpt') {
311
+ openAiChatGptAuth = await this.runChatGptDeviceLogin();
312
+ this.selectedAuthSource = 'smartai';
313
+ }
314
+ }
315
+
316
+ let apiKey: string | undefined;
317
+ if (!openAiChatGptAuth) {
318
+ apiKey = await this.resolveApiKey(this.config.provider);
319
+ if (!apiKey && this.config.provider === 'openai' && this.config.authMode === 'auto' && this.isInteractive) {
320
+ openAiChatGptAuth = await this.runChatGptDeviceLogin();
321
+ this.selectedAuthSource = 'smartai';
322
+ }
323
+ if (!apiKey && !openAiChatGptAuth && this.config.provider === 'openai' && this.config.authMode !== 'chatgpt') {
324
+ apiKey = await this.promptForOpenAiToken();
325
+ }
326
+ if (apiKey) {
327
+ this.selectedAuthSource = 'apiKey';
328
+ if (this.config.provider === 'openai') {
329
+ this.openaiToken = apiKey;
330
+ }
331
+ }
332
+ }
333
+
334
+ const setup = plugins.smartai.getModelSetup({
335
+ provider: this.config.provider,
336
+ model: this.config.model,
337
+ apiKey,
338
+ openAiChatGptAuth,
339
+ providerOptions: this.config.providerOptions,
340
+ promptCaching: this.config.promptCaching,
341
+ baseUrl: this.config.baseUrl,
342
+ ollamaOptions: this.config.ollamaOptions,
343
+ });
344
+ this.model = setup.model;
345
+ this.providerOptions = setup.providerOptions;
346
+ }
347
+
348
+ public async stop(): Promise<void> {}
349
+
350
+ public getOpenaiToken(): string {
351
+ return this.openaiToken;
352
+ }
353
+
354
+ public async createReadOnlyProjectTools(projectDirArg = this.projectDir): Promise<plugins.smartai.ToolSet> {
355
+ return await createReadOnlyProjectTools(projectDirArg);
356
+ }
357
+
358
+ public async runAgent(options: Omit<plugins.smartagent.IAgentRunOptions, 'model'> & {
359
+ projectDir: string;
360
+ taskName: string;
361
+ useCompaction?: boolean;
362
+ }): Promise<plugins.smartagent.IAgentRunResult> {
363
+ const providerOptions = this.config.provider === 'openai' && this.selectedAuthSource !== 'apiKey' && options.system
364
+ ? mergeProviderOptions(this.providerOptions, {
365
+ openai: { instructions: options.system },
366
+ })
367
+ : this.providerOptions;
368
+ const result = await plugins.smartagent.runAgent({
369
+ ...options,
370
+ model: this.model,
371
+ providerOptions,
372
+ cache: this.config.cache,
373
+ sessionId: options.sessionId ?? `tsagent:${options.taskName}:${plugins.path.resolve(options.projectDir)}`,
374
+ onContextOverflow: options.useCompaction
375
+ ? async (messages) => plugins.smartagentCompaction.compactMessages(this.model, messages)
376
+ : options.onContextOverflow,
377
+ });
378
+ return result;
379
+ }
380
+
381
+ public async runJsonTask<T = unknown>(optionsArg: IRunJsonTaskOptions<T>): Promise<T> {
382
+ const tools = optionsArg.tools || await this.createReadOnlyProjectTools(optionsArg.projectDir);
383
+ const result = await this.runAgent({
384
+ taskName: optionsArg.taskName,
385
+ projectDir: optionsArg.projectDir,
386
+ prompt: optionsArg.prompt,
387
+ system: `${optionsArg.system || 'Return only valid JSON.'}\nYour final response must be parseable JSON with no markdown wrapper.`,
388
+ tools,
389
+ maxSteps: optionsArg.maxSteps ?? 15,
390
+ maxValidationRetries: 1,
391
+ useCompaction: optionsArg.useCompaction ?? true,
392
+ validateCompletion: (resultArg) => {
393
+ try {
394
+ const parsed = parseJsonResult<T>(resultArg.text);
395
+ return optionsArg.validate?.(parsed);
396
+ } catch (error) {
397
+ return `Return only valid JSON. Error: ${error instanceof Error ? error.message : String(error)}`;
398
+ }
399
+ },
400
+ });
401
+ const parsed = parseJsonResult<T>(result.text);
402
+ const validation = optionsArg.validate?.(parsed);
403
+ if (validation) {
404
+ throw new Error(validation);
405
+ }
406
+ return parsed;
407
+ }
408
+ }
409
+
410
+ export const inspectChatGptAuthSources = async (sourcesArg = defaultChatGptAuthSources) => {
411
+ return await plugins.smartaiOpenAiChatGptAuth.inspectOpenAiChatGptAuthSources({
412
+ sources: sourcesArg,
413
+ });
414
+ };
415
+
416
+ export const loginChatGptAuth = async (): Promise<plugins.smartai.IOpenAiChatGptTokenData> => {
417
+ const deviceCode = await plugins.smartai.requestOpenAiChatGptDeviceCode();
418
+ console.log(`Open ${deviceCode.verificationUrl} and enter code ${deviceCode.userCode}`);
419
+ const tokenData = await plugins.smartai.completeOpenAiChatGptDeviceCodeLogin(deviceCode);
420
+ const authFilePath = plugins.smartaiOpenAiChatGptAuth.getDefaultOpenAiChatGptAuthPath('smartai');
421
+ await plugins.smartaiOpenAiChatGptAuth.writeOpenAiChatGptAuthFile(authFilePath, tokenData, 'smartai');
422
+ console.log(`Stored OpenAI ChatGPT auth at ${authFilePath}`);
423
+ return tokenData;
424
+ };
package/ts/cli.ts ADDED
@@ -0,0 +1,133 @@
1
+ import * as plugins from './plugins.js';
2
+ import * as paths from './paths.js';
3
+ import { TsAgent, inspectChatGptAuthSources, loginChatGptAuth, type ITsAgentResolvedConfig } from './classes.tsagent.js';
4
+
5
+ const sensitiveConfigKeyRegex = /(^apiKey$|api[_-]?key|token|secret|password|credential|authorization)/i;
6
+
7
+ const redactSensitiveConfigValues = (valueArg: unknown, keyArg?: string): unknown => {
8
+ if (keyArg && sensitiveConfigKeyRegex.test(keyArg)) {
9
+ return valueArg ? '[redacted]' : valueArg;
10
+ }
11
+ if (Array.isArray(valueArg)) {
12
+ return valueArg.map(value => redactSensitiveConfigValues(value));
13
+ }
14
+ if (typeof valueArg === 'object' && valueArg !== null) {
15
+ return Object.fromEntries(Object.entries(valueArg).map(([key, value]) => [
16
+ key,
17
+ redactSensitiveConfigValues(value, key),
18
+ ]));
19
+ }
20
+ return valueArg;
21
+ };
22
+
23
+ export const redactConfigForOutput = (configArg: ITsAgentResolvedConfig): ITsAgentResolvedConfig => {
24
+ return redactSensitiveConfigValues(configArg) as ITsAgentResolvedConfig;
25
+ };
26
+
27
+ const getPrompt = async (argvArg: any): Promise<string> => {
28
+ const positional = (argvArg._ || []).slice(1).join(' ').trim();
29
+ if (positional) return positional;
30
+ if (typeof argvArg.prompt === 'string') return argvArg.prompt;
31
+ const interact = new plugins.smartinteract.SmartInteract();
32
+ const answer = await interact.askQuestion({
33
+ type: 'input',
34
+ name: 'prompt',
35
+ message: 'What should tsagent help with?',
36
+ default: '',
37
+ });
38
+ return answer.value;
39
+ };
40
+
41
+ const runChat = async (argvArg: any): Promise<void> => {
42
+ const prompt = await getPrompt(argvArg);
43
+ const agent = new TsAgent({ argvArg, projectDir: paths.cwd });
44
+ await agent.start();
45
+ try {
46
+ const tools = argvArg.repo ? await agent.createReadOnlyProjectTools(paths.cwd) : undefined;
47
+ const result = await agent.runAgent({
48
+ taskName: 'chat',
49
+ projectDir: paths.cwd,
50
+ system: argvArg.repo
51
+ ? 'You are a concise repo-aware engineering assistant. You can read files but must not reveal secrets.'
52
+ : 'You are a concise engineering assistant.',
53
+ prompt,
54
+ tools,
55
+ maxSteps: argvArg.repo ? 20 : 3,
56
+ useCompaction: true,
57
+ onToolCall: (toolName) => console.log(`[tool] ${toolName}`),
58
+ });
59
+ console.log(result.text);
60
+ } finally {
61
+ await agent.stop();
62
+ }
63
+ };
64
+
65
+ const runAuth = async (argvArg: any): Promise<void> => {
66
+ const subcommand = argvArg._?.[1] || 'status';
67
+ if (subcommand === 'login') {
68
+ await loginChatGptAuth();
69
+ return;
70
+ }
71
+ const inspections = await inspectChatGptAuthSources();
72
+ console.log('OpenAI ChatGPT auth sources:');
73
+ for (const inspection of inspections) {
74
+ const status = inspection.usable
75
+ ? 'usable'
76
+ : inspection.exists
77
+ ? inspection.expired
78
+ ? 'expired'
79
+ : 'not usable'
80
+ : 'missing';
81
+ const account = inspection.email ? ` (${inspection.email})` : '';
82
+ console.log(` ${inspection.source}: ${status}${account} - ${inspection.filePath}`);
83
+ }
84
+ };
85
+
86
+ const runConfig = async (argvArg: any): Promise<void> => {
87
+ const subcommand = argvArg._?.[1] || 'show';
88
+ if (subcommand !== 'show') {
89
+ throw new Error(`Unknown config subcommand: ${subcommand}`);
90
+ }
91
+ const agent = new TsAgent({ argvArg, projectDir: paths.cwd });
92
+ await agent.start();
93
+ try {
94
+ console.log(JSON.stringify({
95
+ config: redactConfigForOutput(agent.config),
96
+ selectedAuthSource: agent.selectedAuthSource,
97
+ }, null, 2));
98
+ } finally {
99
+ await agent.stop();
100
+ }
101
+ };
102
+
103
+ const printHelp = () => {
104
+ console.log(`
105
+ Usage: tsagent <command> [options]
106
+
107
+ Commands:
108
+ chat [--repo] [prompt] Chat directly with the configured model
109
+ auth [status|login] Inspect or create ChatGPT subscription auth
110
+ config show Show resolved tsagent config
111
+
112
+ Examples:
113
+ tsagent chat "explain this project"
114
+ tsagent chat --repo "which file exports the public API?"
115
+ tsagent auth status
116
+ `);
117
+ };
118
+
119
+ export const runCli = async () => {
120
+ const cli = new plugins.smartcli.Smartcli();
121
+ cli.standardCommand().subscribe(async (argvArg) => {
122
+ if (argvArg.help || argvArg.h) {
123
+ printHelp();
124
+ return;
125
+ }
126
+ await runChat(argvArg);
127
+ });
128
+ cli.addCommand('chat').subscribe(runChat);
129
+ cli.addCommand('auth').subscribe(runAuth);
130
+ cli.addCommand('config').subscribe(runConfig);
131
+ cli.addCommand('help').subscribe(async () => printHelp());
132
+ cli.startParse();
133
+ };
package/ts/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './classes.tsagent.js';
2
+ export * from './tools.project.js';
3
+ export * from './cli.js';
package/ts/paths.ts ADDED
@@ -0,0 +1,4 @@
1
+ import * as plugins from './plugins.js';
2
+
3
+ export const cwd = process.cwd();
4
+ export const packageBase = plugins.path.resolve(new URL('..', import.meta.url).pathname);
package/ts/plugins.ts ADDED
@@ -0,0 +1,30 @@
1
+ // node native scope
2
+ import * as fs from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+
5
+ export { fs, path };
6
+
7
+ // @push.rocks scope
8
+ import * as qenv from '@push.rocks/qenv';
9
+ import * as smartagent from '@push.rocks/smartagent';
10
+ import * as smartagentCompaction from '@push.rocks/smartagent/compaction';
11
+ import * as smartai from '@push.rocks/smartai';
12
+ import * as smartaiOpenAiChatGptAuth from '@push.rocks/smartai/openai-chatgpt-auth';
13
+ import * as smartcli from '@push.rocks/smartcli';
14
+ import * as smartconfig from '@push.rocks/smartconfig';
15
+ import * as smartdelay from '@push.rocks/smartdelay';
16
+ import * as smartinteract from '@push.rocks/smartinteract';
17
+ import * as smartpath from '@push.rocks/smartpath';
18
+
19
+ export {
20
+ qenv,
21
+ smartagent,
22
+ smartagentCompaction,
23
+ smartai,
24
+ smartaiOpenAiChatGptAuth,
25
+ smartcli,
26
+ smartconfig,
27
+ smartdelay,
28
+ smartinteract,
29
+ smartpath,
30
+ };