ollama tests

This commit is contained in:
lovebird 2026-03-19 17:40:19 +01:00
parent 9b96450054
commit 561ec84eef
63 changed files with 7394 additions and 0 deletions

View File

@ -0,0 +1,2 @@
export declare const LOGGER_NAME = "llm-tools-cli";
export declare const EXCLUDE_GLOB: string[];

View File

@ -0,0 +1,11 @@
export const LOGGER_NAME = 'llm-tools-cli';
export const EXCLUDE_GLOB = [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/coverage/**",
"*.log",
".kbot",
".git"
];
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uc3RhbnRzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2FpLXRvb2xzL2NvbnN0YW50cy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxNQUFNLENBQUMsTUFBTSxXQUFXLEdBQUcsZUFBZSxDQUFBO0FBRTFDLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRztJQUN4QixvQkFBb0I7SUFDcEIsWUFBWTtJQUNaLGFBQWE7SUFDYixnQkFBZ0I7SUFDaEIsT0FBTztJQUNQLE9BQU87SUFDUCxNQUFNO0NBQ1QsQ0FBQSJ9

View File

@ -0,0 +1,38 @@
import { z } from 'zod';
import { ISettingsParam, Logger } from "tslog";
import * as winston from 'winston';
import { IKBotTask } from './types.js';
export declare let logger: Logger<unknown>;
export declare const TLogLevelNameSchema: z.ZodEnum<{
silly: "silly";
trace: "trace";
debug: "debug";
info: "info";
warn: "warn";
error: "error";
fatal: "fatal";
}>;
export type LogLevel = z.infer<typeof TLogLevelNameSchema>;
export declare enum LogLevelEx {
silly = 0,
trace = 1,
debug = 2,
info = 3,
warn = 4,
error = 5,
fatal = 6
}
export declare enum ELogTargets {
Console = 1,
FileText = 2,
FileJson = 4,
Seq = 8
}
export declare function createLogger(name: string, options?: ISettingsParam<any>): Logger<unknown>;
export declare const defaultLogger: Logger<unknown>;
export declare const winstonLogger: (name: string, file: string, targets?: ELogTargets) => winston.Logger;
export declare const createFileLogger: (logger: Logger<unknown>, level: number, file: string) => Logger<unknown>;
export declare const toolLoggerTS: (name: any, options: IKBotTask) => Logger<unknown>;
export declare const toolLogger: (name: any, options?: IKBotTask) => winston.Logger;
export * from './types.js';
export * from './types_kbot.js';

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
import { IKBotTask } from '../../types.js';
export declare const decode_base64: (base64: string) => string;
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

View File

@ -0,0 +1,85 @@
import { toolLogger } from '../../index.js';
import { input, select } from '@inquirer/prompts';
export const tools = (target, options) => {
const logger = toolLogger('interact', options);
return [
{
type: 'function',
function: {
name: 'ask_question',
description: 'Ask user a simple question and get response',
parameters: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'Question to ask the user'
},
default: {
type: 'string',
description: 'Default answer',
optional: true
}
},
required: ['question']
},
function: async (params) => {
try {
const answer = await input({
message: params.question,
default: params.default
});
return { response: answer };
}
catch (error) {
logger.error('Error asking question:', error.message);
return null;
}
},
parse: JSON.parse
}
},
{
type: 'function',
function: {
name: 'choose_option',
description: 'Ask user to choose from multiple options',
parameters: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Message to show the user'
},
choices: {
type: 'array',
items: { type: 'string' },
description: 'List of choices'
},
multiple: {
type: 'boolean',
description: 'Allow multiple selections',
optional: true
}
},
required: ['message', 'choices']
},
function: async (params) => {
try {
const answer = await select({
message: params.message,
choices: params.choices
});
return { response: answer };
}
catch (error) {
logger.error('Error in choice selection:', error.message);
return null;
}
},
parse: JSON.parse
}
}
];
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJhY3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYWktdG9vbHMvbGliL3Rvb2xzL2ludGVyYWN0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUUzQyxPQUFPLEVBQUUsS0FBSyxFQUFFLE1BQU0sRUFBRSxNQUFNLG1CQUFtQixDQUFBO0FBQ2pELE1BQU0sQ0FBQyxNQUFNLEtBQUssR0FBRyxDQUFDLE1BQWMsRUFBRSxPQUFrQixFQUFjLEVBQUU7SUFDcEUsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLFVBQVUsRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUM5QyxPQUFPO1FBQ0g7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLGNBQWM7Z0JBQ3BCLFdBQVcsRUFBRSw2Q0FBNkM7Z0JBQzFELFVBQVUsRUFBRTtvQkFDUixJQUFJLEVBQUUsUUFBUTtvQkFDZCxVQUFVLEVBQUU7d0JBQ1IsUUFBUSxFQUFFOzRCQUNOLElBQUksRUFBRSxRQUFROzRCQUNkLFdBQVcsRUFBRSwwQkFBMEI7eUJBQzFDO3dCQUNELE9BQU8sRUFBRTs0QkFDTCxJQUFJLEVBQUUsUUFBUTs0QkFDZCxXQUFXLEVBQUUsZ0JBQWdCOzRCQUM3QixRQUFRLEVBQUUsSUFBSTt5QkFDakI7cUJBQ0o7b0JBQ0QsUUFBUSxFQUFFLENBQUMsVUFBVSxDQUFDO2lCQUN6QjtnQkFDRCxRQUFRLEVBQUUsS0FBSyxFQUFFLE1BQVcsRUFBRSxFQUFFO29CQUM1QixJQUFJLENBQUM7d0JBQ0QsTUFBTSxNQUFNLEdBQUcsTUFBTSxLQUFLLENBQ3RCOzRCQUNJLE9BQU8sRUFBRSxNQUFNLENBQUMsUUFBUTs0QkFDeEIsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPO3lCQUMxQixDQUNKLENBQUE7d0JBQ0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLEVBQUUsQ0FBQTtvQkFDL0IsQ0FBQztvQkFBQyxPQUFPLEtBQVUsRUFBRSxDQUFDO3dCQUNsQixNQUFNLENBQUMsS0FBSyxDQUFDLHdCQUF3QixFQUFFLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQzt3QkFDdEQsT0FBTyxJQUFJLENBQUM7b0JBQ2hCLENBQUM7Z0JBQ0wsQ0FBQztnQkFDRCxLQUFLLEVBQUUsSUFBSSxDQUFDLEtBQUs7YUFDcEI7U0FDeUI7UUFDOUI7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLGVBQWU7Z0JBQ3JCLFdBQVcsRUFBRSwwQ0FBMEM7Z0JBQ3ZELFVBQVUsRUFBRTtvQkFDUixJQUFJLEVBQUUsUUFBUTtvQkFDZCxVQUFVLEVBQUU7d0JBQ1IsT0FBTyxFQUFFOzRCQUNMLElBQUksRUFBRSxRQUFROzRCQUNkLFdBQVcsRUFBRSwwQkFBMEI7eUJBQzFDO3dCQUNELE9BQU8sRUFBRTs0QkFDTCxJQUFJLEVBQUUsT0FBTzs0QkFDYixLQUFLLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFOzRCQUN6QixXQUFXLEVBQUUsaUJBQWlCO3lCQUNqQzt3QkFDRCxRQUFRLEVBQUU7NEJBQ04sSUFBSSxFQUFFLFNBQVM7NEJBQ2YsV0FBVyxFQUFFLDJCQUEyQjs0QkFDeEMsUUFBUSxFQUFFLElBQUk7eUJBQ2pCO3FCQUNKO29CQUNELFFBQVEsRUFBRSxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQ25DO2dCQUNELFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBVyxFQUFFLEVBQUU7b0JBQzVCLElBQUksQ0FBQzt3QkFDRCxNQUFNLE1BQU0sR0FBRyxNQUFNLE1BQU0sQ0FDdkI7NEJBQ0ksT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPOzRCQUN2QixPQUFPLEVBQUUsTUFBTSxDQUFDLE9BQU87eUJBQzFCLENBQ0osQ0FBQzt3QkFDRixPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sRUFBRSxDQUFBO29CQUMvQixDQUFDO29CQUFDLE9BQU8sS0FBVSxFQUFFLENBQUM7d0JBQ2xCLE1BQU0sQ0FBQyxLQUFLLENBQUMsNEJBQTRCLEVBQUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO3dCQUMxRCxPQUFPLElBQUksQ0FBQztvQkFDaEIsQ0FBQztnQkFDTCxDQUFDO2dCQUNELEtBQUssRUFBRSxJQUFJLENBQUMsS0FBSzthQUNwQjtTQUN5QjtLQUNqQyxDQUFBO0FBQ0wsQ0FBQyxDQUFBIn0=

View File

@ -0,0 +1,4 @@
import Keyv from 'keyv';
export declare const store: (storePath: string, ns?: string, opts?: any) => Keyv<any>;
export declare const get: (key: string, storePath: string, ns?: string, opts?: any) => Promise<any>;
export declare const set: (key: string, value: any, storePath: string, ns?: string, opts?: any) => Promise<boolean>;

View File

@ -0,0 +1,20 @@
import * as path from 'path';
import Keyv from 'keyv';
import { KeyvFile } from 'keyv-file';
import { resolve } from '@polymech/commons';
export const store = (storePath, ns = 'ns-unknown', opts = {}) => {
const keyvFile = new KeyvFile({
filename: path.resolve(resolve(storePath)),
writeDelay: 100, // ms
});
return new Keyv({ store: keyvFile, namespace: ns, ...opts });
};
export const get = async (key, storePath, ns = 'ns-unknown', opts = {}) => {
const keyv = store(storePath, ns, opts);
return await keyv.get(key);
};
export const set = async (key, value, storePath, ns = 'ns-unknown', opts = {}) => {
const keyv = store(storePath, ns, opts);
return await keyv.set(key, value);
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia2V5di5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9haS10b29scy9saWIvdG9vbHMva2V5di50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssSUFBSSxNQUFNLE1BQU0sQ0FBQTtBQUM1QixPQUFPLElBQUksTUFBTSxNQUFNLENBQUE7QUFDdkIsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLFdBQVcsQ0FBQTtBQUVwQyxPQUFPLEVBQUUsT0FBTyxFQUFFLE1BQU0sbUJBQW1CLENBQUE7QUFFM0MsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHLENBQUMsU0FBaUIsRUFBRSxLQUFhLFlBQVksRUFBRSxPQUFZLEVBQUUsRUFBRSxFQUFFO0lBQ2xGLE1BQU0sUUFBUSxHQUFHLElBQUksUUFBUSxDQUFDO1FBQzFCLFFBQVEsRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUMxQyxVQUFVLEVBQUUsR0FBRyxFQUFFLEtBQUs7S0FDekIsQ0FBQyxDQUFBO0lBQ0YsT0FBTyxJQUFJLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRSxHQUFHLElBQUksRUFBRSxDQUFDLENBQUE7QUFDaEUsQ0FBQyxDQUFBO0FBQ0QsTUFBTSxDQUFDLE1BQU0sR0FBRyxHQUFHLEtBQUssRUFBRSxHQUFXLEVBQUUsU0FBaUIsRUFBRSxLQUFhLFlBQVksRUFBRSxPQUFZLEVBQUUsRUFBRSxFQUFFO0lBQ25HLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxTQUFTLEVBQUUsRUFBRSxFQUFFLElBQUksQ0FBQyxDQUFBO0lBQ3ZDLE9BQU8sTUFBTSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFBO0FBQzlCLENBQUMsQ0FBQTtBQUNELE1BQU0sQ0FBQyxNQUFNLEdBQUcsR0FBRyxLQUFLLEVBQUUsR0FBVyxFQUFFLEtBQVUsRUFBRSxTQUFpQixFQUFFLEtBQWEsWUFBWSxFQUFFLE9BQVksRUFBRSxFQUFFLEVBQUU7SUFDL0csTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLFNBQVMsRUFBRSxFQUFFLEVBQUUsSUFBSSxDQUFDLENBQUE7SUFDdkMsT0FBTyxNQUFNLElBQUksQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFBO0FBQ3JDLENBQUMsQ0FBQSJ9

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,16 @@
export declare enum STATUS {
OK = 0,
ERROR = 1,
PENDING = 2
}
export declare class Process {
binary: string;
cwd: string;
args: string;
buffer: string[];
constructor(options?: any);
exec(command: string, args?: string[]): Promise<any>;
}
export declare class Helper {
static run(cwd: any, cmd: string, args: string[], buffer?: string[], debug_stream?: boolean): Promise<any>;
}

View File

@ -0,0 +1,126 @@
import { logger } from '../../index.js';
import { spawn } from 'child_process';
export var STATUS;
(function (STATUS) {
STATUS[STATUS["OK"] = 0] = "OK";
STATUS[STATUS["ERROR"] = 1] = "ERROR";
STATUS[STATUS["PENDING"] = 2] = "PENDING";
})(STATUS || (STATUS = {}));
const fatalHandler = (message, fn) => {
if (message.startsWith('fatal:')) {
fn('\t\ ' + message);
return true;
}
return false;
};
const defaultFilter = (message) => {
return message.length > 0 &&
message !== '\n' &&
message !== '\r' &&
message !== '\r\n' &&
!message.startsWith('Debugger attached') &&
!message.includes('NODE_TLS_REJECT_UNAUTHORIZED') &&
!message.includes('Waiting for the debugger to disconnect');
};
const subscribe = (signal, collector = () => { }) => {
if (!signal || !signal.on) {
return;
}
signal.on('message', (message) => logger.debug('message', message));
signal.on('error', (error) => logger.error('std-error', error));
signal.on('data', (data) => {
/*
const msg = data.toString().replace(ansiRegex(), "")
if (!defaultFilter(msg)) {
return
}
collector(msg)*/
process.stdout.write(data);
});
};
const merge = (buffer, data) => buffer.concat(data);
const hook = (child, resolve, reject, cmd, buffer = []) => {
const collector = (data) => { buffer.push(data); };
//subscribe(child.stderr, collector)
//process.stdin.pipe(child.stdin)
debugger;
child.on('exit', (code, signal) => {
debugger;
if (code) {
resolve({
code: STATUS.ERROR,
command: cmd,
error: code,
messages: buffer
});
}
else {
resolve({
code: STATUS.OK,
command: cmd,
messages: buffer
});
}
});
return child;
};
export class Process {
binary = '';
cwd = '';
args = '';
buffer = [];
constructor(options = {}) {
this.binary = options.binary || this.binary;
this.cwd = options.cwd || process.cwd();
this.buffer = options.buffer || [];
}
async exec(command, args = []) {
args = [command].concat(args);
try {
let cmd = `${this.binary} ${args.join(' ')}`;
/*
const p = new Promise<any>((resolve, reject) => {
const p = exec(cmd, {
cwd: this.cwd
})
return hook(p, resolve, reject, this.binary + ' ' + args.join(' '), this.buffer)
})
return p
*/
try {
//stdio: ['pipe', 'pipe', 'pipe'],
debugger;
const p = new Promise((resolve, reject) => {
const cp = spawn(cmd, args, {
cwd: this.cwd,
shell: true,
stdio: 'inherit',
env: {
...process.env
},
});
return hook(cp, resolve, reject, cmd, this.buffer);
});
return p;
}
catch (e) {
logger.error('Error executing command', e);
}
}
catch (e) {
logger.error('Error executing command', e);
}
}
}
export class Helper {
static async run(cwd, cmd, args, buffer = [], debug_stream = false) {
debug_stream && logger.info(`Run ${cmd} in ${cwd}`, args);
const gitProcess = new Process({
cwd,
binary: cmd,
buffer
});
return gitProcess.exec('', args);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9haS10b29scy9saWIvdG9vbHMvcHJvY2Vzcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFFdkMsT0FBTyxFQUFnQixLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFFbkQsTUFBTSxDQUFOLElBQVksTUFJWDtBQUpELFdBQVksTUFBTTtJQUNkLCtCQUFFLENBQUE7SUFDRixxQ0FBSyxDQUFBO0lBQ0wseUNBQU8sQ0FBQTtBQUNYLENBQUMsRUFKVyxNQUFNLEtBQU4sTUFBTSxRQUlqQjtBQUVELE1BQU0sWUFBWSxHQUFHLENBQUMsT0FBZSxFQUFFLEVBQXlCLEVBQVcsRUFBRTtJQUN6RSxJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLEVBQUUsQ0FBQztRQUMvQixFQUFFLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxDQUFBO1FBQ3BCLE9BQU8sSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFDRCxPQUFPLEtBQUssQ0FBQTtBQUNoQixDQUFDLENBQUE7QUFFRCxNQUFNLGFBQWEsR0FBRyxDQUFDLE9BQWUsRUFBVyxFQUFFO0lBQy9DLE9BQU8sT0FBTyxDQUFDLE1BQU0sR0FBRyxDQUFDO1FBQ3JCLE9BQU8sS0FBSyxJQUFJO1FBQ2hCLE9BQU8sS0FBSyxJQUFJO1FBQ2hCLE9BQU8sS0FBSyxNQUFNO1FBQ2xCLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxtQkFBbUIsQ0FBQztRQUN4QyxDQUFDLE9BQU8sQ0FBQyxRQUFRLENBQUMsOEJBQThCLENBQUM7UUFDakQsQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLHdDQUF3QyxDQUFDLENBQUE7QUFDbkUsQ0FBQyxDQUFBO0FBRUQsTUFBTSxTQUFTLEdBQUcsQ0FBQyxNQUF1QixFQUFFLFlBQWlDLEdBQUcsRUFBRSxHQUFHLENB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

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

View File

@ -0,0 +1,90 @@
import { isArray } from '@polymech/core/primitives';
import { CONFIG_DEFAULT } from '@polymech/commons';
import { toolLogger } from '../../index.js';
export const tools = (target, options) => {
const logger = toolLogger('search', options);
return [
{
type: 'function',
function: {
name: 'google',
description: 'Searches Google for the given query',
parameters: {
type: 'object',
properties: {
query: { type: 'string' }
},
required: ['query']
},
function: async (params) => {
const { query } = params;
const config = CONFIG_DEFAULT();
let apiKey = config?.google?.api_key;
let cse = config?.google?.cse;
if (!config || !apiKey || !cse) {
logger.debug("Config not found in $HOME/.osr/config.json. " +
"Optionally, export OSR_CONFIG with the path to the configuration file " +
"");
return undefined;
}
const res = await fetch(`https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${cse}&q=${encodeURIComponent(query)}`);
const data = await res.json();
let results = data.items?.map((item) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
...item
})) ?? [];
return JSON.stringify(results);
},
parse: JSON.parse
}
},
{
type: 'function',
function: {
name: 'serpapi',
description: 'Searches Serpapi (finds locations (engine:google_local), places on the map (engine:google_maps) ) for the given query',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
engine: { type: 'string', default: 'google' },
},
required: ['query']
},
function: async (params) => {
const { query, engine } = params;
const config = CONFIG_DEFAULT();
let apiKey = config?.serpapi?.key || config?.serpapi?.api_key;
if (!config || !apiKey) {
logger.debug("Config not found in $HOME/.osr/config.json. " +
"Optionally, export OSR_CONFIG with the path to the configuration file " +
"");
return undefined;
}
const url = `https://serpapi.com/search?api_key=${apiKey}&engine=${engine || 'google'}&q=${encodeURIComponent(query)}&google_domain=google.com`;
const res = await fetch(url);
logger.debug(`Searching ${url}`);
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json();
let items = data.organic_results || data.local_results || data.place_results || data.places || data.maps_results || [];
if (items && !isArray(items)) {
items = [items];
}
let results = items.map((item) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
...item
})) ?? [];
return JSON.stringify(results);
},
parse: JSON.parse
}
}
];
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VhcmNoLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2FpLXRvb2xzL2xpYi90b29scy9zZWFyY2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUEsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLDJCQUEyQixDQUFBO0FBQ25ELE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQTtBQUNsRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sZ0JBQWdCLENBQUE7QUFHM0MsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHLENBQUMsTUFBYyxFQUFFLE9BQWtCLEVBQWMsRUFBRTtJQUNwRSxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsUUFBUSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0lBQzVDLE9BQU87UUFDSDtZQUNJLElBQUksRUFBRSxVQUFVO1lBQ2hCLFFBQVEsRUFBRTtnQkFDTixJQUFJLEVBQUUsUUFBUTtnQkFDZCxXQUFXLEVBQUUscUNBQXFDO2dCQUNsRCxVQUFVLEVBQUU7b0JBQ1IsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsVUFBVSxFQUFFO3dCQUNSLEtBQUssRUFBRSxFQUFFLElBQUksRUFBRSxRQUFRLEVBQUU7cUJBQzVCO29CQUNELFFBQVEsRUFBRSxDQUFDLE9BQU8sQ0FBQztpQkFDdEI7Z0JBQ0QsUUFBUSxFQUFFLEtBQUssRUFBRSxNQUFXLEVBQUUsRUFBRTtvQkFDNUIsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLE1BQU0sQ0FBQTtvQkFDeEIsTUFBTSxNQUFNLEdBQUcsY0FBYyxFQUFTLENBQUE7b0JBQ3RDLElBQUksTUFBTSxHQUFHLE1BQU0sRUFBRSxNQUFNLEVBQUUsT0FBTyxDQUFBO29CQUNwQyxJQUFJLEdBQUcsR0FBRyxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsQ0FBQTtvQkFDN0IsSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLE1BQU0sSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO3dC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

View File

@ -0,0 +1,2 @@
import { IKBotTask } from '../../types.js';
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

View File

@ -0,0 +1,116 @@
import * as path from 'path';
import { spawn } from 'child_process';
import { toolLogger } from '../../index.js';
import { Helper } from './process.js';
export const tools = (target, options) => {
const logger = toolLogger('terminal', options);
return [
{
type: 'function',
function: {
name: 'execute_command',
description: 'Execute a terminal command and capture output',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Command to execute'
},
args: {
type: 'array',
items: { type: 'string' },
description: 'Command arguments',
optional: true
},
cwd: {
type: 'string',
description: 'Working directory for command execution',
optional: true
},
background: {
type: 'boolean',
description: 'Run command in background (non-blocking)',
optional: true,
default: false
},
window: {
type: 'boolean',
description: 'Open command in new terminal window',
optional: true,
default: false
},
detached: {
type: 'boolean',
description: 'Run process detached from parent',
optional: true,
default: false
}
},
required: ['command']
},
function: async (params) => {
try {
debugger;
const cwd = params.cwd ? path.join(target, params.cwd) : target;
const args = params.args || [];
logger.debug(`Tool::Terminal : ExecuteCommand Running '${params.command}' in ${cwd}`, params);
if (params.detached) {
const isWindows = process.platform === 'win32';
if (isWindows) {
spawn('cmd', ['/c', 'start', 'cmd', '/k', params.command, ...args], {
cwd: cwd,
detached: true,
stdio: 'ignore'
});
}
else {
// For macOS/Linux
spawn('x-terminal-emulator', ['-e', `${params.command} ${args.join(' ')}`], {
cwd: cwd,
detached: true,
stdio: 'ignore'
});
}
return {
success: true,
output: 'Command launched in new window',
error: null
};
}
if (params.background || params.detached) {
const child = spawn(params.command, args, {
cwd: cwd,
detached: params.detached === true,
stdio: 'ignore'
});
if (params.detached) {
child.unref();
}
return {
success: true,
output: `Process started with PID: ${child.pid}`,
error: null
};
}
const cmd = `${params.command} ${args.join(' ')}`.trim();
logger.debug(`Tool::ExecuteCommand Running '${cmd}' in ${cwd}`);
const collector = [];
const ret = await Helper.run(cwd, cmd, [], collector, true);
return ret;
}
catch (error) {
logger.error('Error executing command', error);
return {
success: false,
output: null,
error: error.message
};
}
},
parse: JSON.parse
}
}
];
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVybWluYWwuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYWktdG9vbHMvbGliL3Rvb2xzL3Rlcm1pbmFsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxJQUFJLE1BQU0sTUFBTSxDQUFBO0FBRTVCLE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUE7QUFDckMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGdCQUFnQixDQUFBO0FBRTNDLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxjQUFjLENBQUE7QUFFckMsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHLENBQUMsTUFBYyxFQUFFLE9BQWtCLEVBQWMsRUFBRTtJQUNwRSxNQUFNLE1BQU0sR0FBRyxVQUFVLENBQUMsVUFBVSxFQUFFLE9BQU8sQ0FBQyxDQUFBO0lBQzlDLE9BQU87UUFDSDtZQUNJLElBQUksRUFBRSxVQUFVO1lBQ2hCLFFBQVEsRUFBRTtnQkFDTixJQUFJLEVBQUUsaUJBQWlCO2dCQUN2QixXQUFXLEVBQUUsK0NBQStDO2dCQUM1RCxVQUFVLEVBQUU7b0JBQ1IsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsVUFBVSxFQUFFO3dCQUNSLE9BQU8sRUFBRTs0QkFDTCxJQUFJLEVBQUUsUUFBUTs0QkFDZCxXQUFXLEVBQUUsb0JBQW9CO3lCQUNwQzt3QkFDRCxJQUFJLEVBQUU7NEJBQ0YsSUFBSSxFQUFFLE9BQU87NEJBQ2IsS0FBSyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRTs0QkFDekIsV0FBVyxFQUFFLG1CQUFtQjs0QkFDaEMsUUFBUSxFQUFFLElBQUk7eUJBQ2pCO3dCQUNELEdBQUcsRUFBRTs0QkFDRCxJQUFJLEVBQUUsUUFBUTs0QkFDZCxXQUFXLEVBQUUseUNBQXlDOzRCQUN0RCxRQUFRLEVBQUUsSUFBSTt5QkFDakI7d0JBQ0QsVUFBVSxFQUFFOzRCQUNSLElBQUksRUFBRSxTQUFTOzRCQUNmLFdBQVcs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

View File

@ -0,0 +1,10 @@
export declare const tools: {
fs: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
npm: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
git: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
terminal: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
interact: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
user: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
search: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
memory: (target: string, options: import("../../types.js").IKBotTask) => Array<any>;
};

View File

@ -0,0 +1,23 @@
import { tools as fsTools } from './fs.js';
import { tools as npmTools } from './npm.js';
import { tools as gitTools } from './git.js';
import { tools as terminalTools } from './terminal.js';
import { tools as interactTools } from './interact.js';
import { tools as userTools } from './user.js';
import { tools as search } from './search.js';
//import { tools as webTools } from './web.js'
import { tools as memoryTools } from './memory.js';
// import { tools as emailTools } from './email'
export const tools = {
fs: fsTools,
npm: npmTools,
git: gitTools,
terminal: terminalTools,
interact: interactTools,
user: userTools,
search: search,
// web: webTools,
memory: memoryTools
// email: emailTools
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9vbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvYWktdG9vbHMvbGliL3Rvb2xzL3Rvb2xzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxLQUFLLElBQUksT0FBTyxFQUFFLE1BQU0sU0FBUyxDQUFBO0FBQzFDLE9BQU8sRUFBRSxLQUFLLElBQUksUUFBUSxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBQzVDLE9BQU8sRUFBRSxLQUFLLElBQUksUUFBUSxFQUFFLE1BQU0sVUFBVSxDQUFBO0FBQzVDLE9BQU8sRUFBRSxLQUFLLElBQUksYUFBYSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQ3RELE9BQU8sRUFBRSxLQUFLLElBQUksYUFBYSxFQUFFLE1BQU0sZUFBZSxDQUFBO0FBQ3RELE9BQU8sRUFBRSxLQUFLLElBQUksU0FBUyxFQUFFLE1BQU0sV0FBVyxDQUFBO0FBQzlDLE9BQU8sRUFBRSxLQUFLLElBQUksTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQzdDLDhDQUE4QztBQUM5QyxPQUFPLEVBQUUsS0FBSyxJQUFJLFdBQVcsRUFBRSxNQUFNLGFBQWEsQ0FBQTtBQUNsRCxnREFBZ0Q7QUFFaEQsTUFBTSxDQUFDLE1BQU0sS0FBSyxHQUFHO0lBQ2pCLEVBQUUsRUFBRSxPQUFPO0lBQ1gsR0FBRyxFQUFFLFFBQVE7SUFDYixHQUFHLEVBQUUsUUFBUTtJQUNiLFFBQVEsRUFBRSxhQUFhO0lBQ3ZCLFFBQVEsRUFBRSxhQUFhO0lBQ3ZCLElBQUksRUFBRSxTQUFTO0lBQ2YsTUFBTSxFQUFFLE1BQU07SUFDZCxpQkFBaUI7SUFDakIsTUFBTSxFQUFFLFdBQVc7SUFDbkIsb0JBQW9CO0NBQ3ZCLENBQUEifQ==

View File

@ -0,0 +1,4 @@
import { IKBotTask } from '../../types.js';
export declare const mime: (file?: string) => any;
export declare const fileToBase64: (filePath: string) => string | null;
export declare const tools: (target: string, options: IKBotTask) => Array<any>;

View File

@ -0,0 +1,78 @@
import { parse, join } from 'path';
import * as fs from 'fs';
import { lookup } from 'mime-types';
import { toolLogger } from '../../index.js';
export const mime = (file = '') => parse(file).ext ? lookup(file) : null;
//const screenshot = require('screenshot-desktop')
export const fileToBase64 = (filePath) => {
try {
const fileBuffer = fs.readFileSync(filePath);
const mimeType = lookup(filePath);
if (!mimeType) {
throw new Error('Unable to determine MIME type.');
}
const base64Data = fileBuffer.toString('base64');
return `data:${mimeType};base64,${base64Data}`;
}
catch (error) {
console.error('fileToBase64 : Error reading file:', error);
return null;
}
};
export const tools = (target, options) => {
const logger = toolLogger('user', options);
return [
{
type: 'function',
function: {
name: 'capture_screen',
description: 'Capture a screenshot and store it as file (jpg). Returns the path to the file',
parameters: {
type: 'object',
properties: {
file: { type: 'string' }
},
required: ['file']
},
function: async (params) => {
try {
const outputPath = join(target, params.file);
const takeScreenshot = async () => {
/*
return new Promise((resolve, reject) => {
screenshot({ format: 'jpg' }).then((img) => {
write(outputPath, img)
resolve({ success: true, path: outputPath})
}).catch(reject)
})
*/
};
const { path } = await takeScreenshot();
return {
"role": "user",
"content": [
/*
{
type: "image_url",
image_url: {
url: fileToBase64( path),
}
}
*/
]
};
}
catch (error) {
logger.error('Error capturing screenshot:', error);
return {
success: false,
error: error.message
};
}
},
parse: JSON.parse
}
}
];
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9haS10b29scy9saWIvdG9vbHMvdXNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxNQUFNLE1BQU0sQ0FBQTtBQUdsQyxPQUFPLEtBQUssRUFBRSxNQUFNLElBQUksQ0FBQTtBQUN4QixPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFBO0FBRW5DLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQTtBQUUzQyxNQUFNLENBQUMsTUFBTSxJQUFJLEdBQUcsQ0FBQyxPQUFlLEVBQUUsRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUE7QUFFaEYsa0RBQWtEO0FBRWxELE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBRyxDQUFDLFFBQWdCLEVBQWlCLEVBQUU7SUFDNUQsSUFBSSxDQUFDO1FBQ0QsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFDLFlBQVksQ0FBQyxRQUFRLENBQUMsQ0FBQTtRQUM1QyxNQUFNLFFBQVEsR0FBRyxNQUFNLENBQUMsUUFBUSxDQUFDLENBQUE7UUFDakMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ1osTUFBTSxJQUFJLEtBQUssQ0FBQyxnQ0FBZ0MsQ0FBQyxDQUFBO1FBQ3JELENBQUM7UUFDRCxNQUFNLFVBQVUsR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1FBQ2hELE9BQU8sUUFBUSxRQUFRLFdBQVcsVUFBVSxFQUFFLENBQUE7SUFDbEQsQ0FBQztJQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7UUFDYixPQUFPLENBQUMsS0FBSyxDQUFDLG9DQUFvQyxFQUFFLEtBQUssQ0FBQyxDQUFBO1FBQzFELE9BQU8sSUFBSSxDQUFBO0lBQ2YsQ0FBQztBQUNMLENBQUMsQ0FBQTtBQUNELE1BQU0sQ0FBQyxNQUFNLEtBQUssR0FBRyxDQUFDLE1BQWMsRUFBRSxPQUFrQixFQUFjLEVBQUU7SUFDcEUsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQTtJQUMxQyxPQUFPO1FBQ0g7WUFDSSxJQUFJLEVBQUUsVUFBVTtZQUNoQixRQUFRLEVBQUU7Z0JBQ04sSUFBSSxFQUFFLGdCQUFnQjtnQkFDdEIsV0FBVyxFQUFFLCtFQUErRTtnQkFDNUYsVUFBVSxFQUFFO29CQUNSLElBQUksRUFBRSxRQUFRO29CQUNkLFVBQVUsRUFBRTt3QkFDUixJQUFJLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFO3FCQUMzQjtvQkFDRCxRQUFRLEVBQUUsQ0FBQyxNQUFNLENBQUM7aUJBQ3JCO2dCQUNELFFBQVEsRUFBRSxLQUFLLEVBQUUsTUFBVyxFQUFFLEVBQUU7b0JBQzVCLElBQUksQ0FBQzt3QkFDRCxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQTt3QkFDNUMsTUFBTSxjQUFjLEdBQUcsS0FBSyxJQUFtQixFQUFFOzRCQUM3Qzs7Ozs7Ozs4QkFPRTt3QkFDTixDQUFDLENBQUE7d0JBQ0QsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLE1BQU0sY0FBYyxFQUFFLENBQUE7d0JBQ3ZDLE9BQU87NEJBQ0gsTUFBTSxFQUFFLE1BQU07NEJBQ2QsU0FBUyxFQUNQOzRCQUNFOzs7Ozs7OzhCQU9FOzZCQUNIO3lCQUNKLENBQUE7b0JBQ1AsQ0FBQztvQkFBQyxPQUFPLEtBQVUsRUFBRSxDQUFDO3dCQUNsQixNQUFNLENBQUMsS0FBSyxDQUFDLDZCQUE2QixFQUFFLEtBQUssQ0FBQyxDQUFDO3dCQUNuRCxPQUFPOzRCQUNILE9BQU8sRUFBRSxLQUFLOzRCQUNkLEtBQUssRUFBRSxLQUFLLENBQUMsT0FBTzt5QkFDdkIsQ0FBQztvQkFDTixDQUFDO2dCQUNMLENBQUM7Z0JBQ0QsS0FBSyxFQUFFLElBQUksQ0FBQyxLQUFLO2FBQ3BCO1NBQ3lCO0tBQ2pDLENBQUM7QUFDTixDQUFDLENBQUMifQ==

View File

@ -0,0 +1,24 @@
import { ChatCompletion, ChatCompletionMessage, ChatCompletionMessageParam } from 'openai/resources';
import { IKBotOptions } from './types_kbot.js';
import OpenAI from 'openai';
import { Logger, ILogObj } from 'tslog';
import { RunnableFunctionWithParse } from 'openai/lib/RunnableFunction';
export type onToolBefore = (ctx: RunnableFunctionWithParse<any>, args: any) => Promise<any>;
export type onToolAfter = (ctx: RunnableFunctionWithParse<any>, args: any, result?: any) => Promise<any>;
export interface ICollector {
onMessage: (message: ChatCompletionMessageParam) => void;
onToolCall: (tool: ChatCompletionMessage.FunctionCall) => void;
onFunctionCallResult: (content: string) => void;
onChatCompletion: (completion: ChatCompletion) => void;
onContent: (content: string) => void;
onTool: (category: string, name: string, args: any, result?: any) => void;
onToolBefore: onToolBefore;
onToolAfter: onToolAfter;
}
export interface IKBotTask extends IKBotOptions {
client?: OpenAI;
collector?: ICollector;
onRun?: (ctx: IKBotTask) => Promise<IKBotTask>;
logger?: Logger<ILogObj>;
customTools?: any[];
}

View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYWktdG9vbHMvdHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9

View File

@ -0,0 +1,569 @@
export interface IKBotOptions {
/** Target directory */
path?: string;
/** The prompt. Supports file paths and environment variables. */
prompt?: string | undefined;
/** Optional output path for modified files (Tool mode only) */
output?: string | undefined;
/** Optional destination path for the result, will substitute ${MODEL_NAME} and ${ROUTER} in the path. Optional, used for "completion" mode */
dst?: string | undefined;
/** How to handle output if --dst file already exists: "concat" (append) or "merge" (try to merge structures if possible, otherwise append). Only used if --dst is specified. */
append?: ("concat" | "merge" | "replace") | undefined;
/** Specify how to wrap the output, "meta (file name, absolute path, cwd)" or "none". */
wrap?: "meta" | "none";
/** Iterate over items, supported: GLOB | Path to JSON File | array of strings (comma separated). To test different models, use --each="gpt-3.5-turbo,gpt-4o", the actual string will exposed as variable `ITEM`, eg: --dst="${ITEM}-output.md" */
each?: string | undefined;
/** Disable tools categories, eg: --disable=fs,git,interact,terminal,search,web,email,user */
disable?: string[];
/** List of specific tools to disable */
disableTools?: string[];
/** List of tools to use. Can be built-in tool names or paths to custom tool files. Default: fs,git,interact,terminal,search,web,email,user */
tools?: (string[] | string);
/** Comma separated glob patterns or paths, eg --include=src/*.tsx,src/*.ts --include=package.json */
include?: string[] | undefined;
/** Comma separated glob patterns or paths, eg --exclude=src/*.tsx,src/*.ts --exclude=package.json */
exclude?: string[] | undefined;
/** Specify a glob extension behavior. Available presets: match-cpp. Also accepts a custom glob pattern with variables like ${SRC_DIR}, ${SRC_NAME}, ${SRC_EXT}. E.g., "match-cpp" or "${SRC_DIR}/${SRC_NAME}*.cpp" */
globExtension?: (("match-cpp") | string) | undefined;
/** Explicit API key to use */
api_key?: string | undefined;
/** AI model to use for processing. Available models:

 OpenRouter models:

ai21/jamba-large-1.7 | paid
aion-labs/aion-1.0 | paid
aion-labs/aion-1.0-mini | paid
aion-labs/aion-2.0 | paid
aion-labs/aion-rp-llama-3.1-8b | paid
alfredpros/codellama-7b-instruct-solidity | paid
allenai/molmo-2-8b | paid
allenai/olmo-2-0325-32b-instruct | paid
allenai/olmo-3-32b-think | paid
allenai/olmo-3-7b-instruct | paid
allenai/olmo-3-7b-think | paid
allenai/olmo-3.1-32b-instruct | paid
allenai/olmo-3.1-32b-think | paid
amazon/nova-2-lite-v1 | paid
amazon/nova-lite-v1 | paid
amazon/nova-micro-v1 | paid
amazon/nova-premier-v1 | paid
amazon/nova-pro-v1 | paid
anthropic/claude-3-haiku | paid
anthropic/claude-3.5-haiku | paid
anthropic/claude-3.5-sonnet | paid
anthropic/claude-3.7-sonnet | paid
anthropic/claude-3.7-sonnet:thinking | paid
anthropic/claude-haiku-4.5 | paid
anthropic/claude-opus-4 | paid
anthropic/claude-opus-4.1 | paid
anthropic/claude-opus-4.5 | paid
anthropic/claude-opus-4.6 | paid
anthropic/claude-sonnet-4 | paid
anthropic/claude-sonnet-4.5 | paid
anthropic/claude-sonnet-4.6 | paid
arcee-ai/coder-large | paid
arcee-ai/maestro-reasoning | paid
arcee-ai/spotlight | paid
arcee-ai/trinity-large-preview:free | free
arcee-ai/trinity-mini | paid
arcee-ai/trinity-mini:free | free
arcee-ai/virtuoso-large | paid
openrouter/auto | paid
baidu/ernie-4.5-21b-a3b | paid
baidu/ernie-4.5-21b-a3b-thinking | paid
baidu/ernie-4.5-300b-a47b | paid
baidu/ernie-4.5-vl-28b-a3b | paid
baidu/ernie-4.5-vl-424b-a47b | paid
openrouter/bodybuilder | paid
bytedance-seed/seed-1.6 | paid
bytedance-seed/seed-1.6-flash | paid
bytedance-seed/seed-2.0-lite | paid
bytedance-seed/seed-2.0-mini | paid
bytedance/ui-tars-1.5-7b | paid
cohere/command-a | paid
cohere/command-r-08-2024 | paid
cohere/command-r-plus-08-2024 | paid
cohere/command-r7b-12-2024 | paid
deepcogito/cogito-v2.1-671b | paid
deepseek/deepseek-chat | paid
deepseek/deepseek-chat-v3-0324 | paid
deepseek/deepseek-chat-v3.1 | paid
deepseek/deepseek-v3.1-terminus | paid
deepseek/deepseek-v3.2 | paid
deepseek/deepseek-v3.2-exp | paid
deepseek/deepseek-v3.2-speciale | paid
deepseek/deepseek-r1 | paid
deepseek/deepseek-r1-0528 | paid
deepseek/deepseek-r1-distill-llama-70b | paid
deepseek/deepseek-r1-distill-qwen-32b | paid
eleutherai/llemma_7b | paid
essentialai/rnj-1-instruct | paid
openrouter/free | paid
alpindale/goliath-120b | paid
google/gemini-2.0-flash-001 | paid
google/gemini-2.0-flash-lite-001 | paid
google/gemini-2.5-flash | paid
google/gemini-2.5-flash-lite | paid
google/gemini-2.5-flash-lite-preview-09-2025 | paid
google/gemini-2.5-pro | paid
google/gemini-2.5-pro-preview-05-06 | paid
google/gemini-2.5-pro-preview | paid
google/gemini-3-flash-preview | paid
google/gemini-3-pro-preview | paid
google/gemini-3.1-flash-lite-preview | paid
google/gemini-3.1-pro-preview | paid
google/gemini-3.1-pro-preview-customtools | paid
google/gemma-2-27b-it | paid
google/gemma-2-9b-it | paid
google/gemma-3-12b-it | paid
google/gemma-3-12b-it:free | free
google/gemma-3-27b-it | paid
google/gemma-3-27b-it:free | free
google/gemma-3-4b-it | paid
google/gemma-3-4b-it:free | free
google/gemma-3n-e2b-it:free | free
google/gemma-3n-e4b-it | paid
google/gemma-3n-e4b-it:free | free
google/gemini-2.5-flash-image | paid
google/gemini-3.1-flash-image-preview | paid
google/gemini-3-pro-image-preview | paid
ibm-granite/granite-4.0-h-micro | paid
inception/mercury | paid
inception/mercury-2 | paid
inception/mercury-coder | paid
inflection/inflection-3-pi | paid
inflection/inflection-3-productivity | paid
kwaipilot/kat-coder-pro | paid
liquid/lfm-2.2-6b | paid
liquid/lfm-2-24b-a2b | paid
liquid/lfm2-8b-a1b | paid
liquid/lfm-2.5-1.2b-instruct:free | free
liquid/lfm-2.5-1.2b-thinking:free | free
meta-llama/llama-guard-3-8b | paid
anthracite-org/magnum-v4-72b | paid
mancer/weaver | paid
meituan/longcat-flash-chat | paid
meta-llama/llama-3-70b-instruct | paid
meta-llama/llama-3-8b-instruct | paid
meta-llama/llama-3.1-405b | paid
meta-llama/llama-3.1-70b-instruct | paid
meta-llama/llama-3.1-8b-instruct | paid
meta-llama/llama-3.2-11b-vision-instruct | paid
meta-llama/llama-3.2-1b-instruct | paid
meta-llama/llama-3.2-3b-instruct | paid
meta-llama/llama-3.2-3b-instruct:free | free
meta-llama/llama-3.3-70b-instruct | paid
meta-llama/llama-3.3-70b-instruct:free | free
meta-llama/llama-4-maverick | paid
meta-llama/llama-4-scout | paid
meta-llama/llama-guard-4-12b | paid
microsoft/phi-4 | paid
minimax/minimax-m1 | paid
minimax/minimax-m2 | paid
minimax/minimax-m2-her | paid
minimax/minimax-m2.1 | paid
minimax/minimax-m2.5 | paid
minimax/minimax-m2.5:free | free
minimax/minimax-m2.7 | paid
minimax/minimax-01 | paid
mistralai/mistral-large | paid
mistralai/mistral-large-2407 | paid
mistralai/mistral-large-2411 | paid
mistralai/codestral-2508 | paid
mistralai/devstral-2512 | paid
mistralai/devstral-medium | paid
mistralai/devstral-small | paid
mistralai/ministral-14b-2512 | paid
mistralai/ministral-3b-2512 | paid
mistralai/ministral-8b-2512 | paid
mistralai/mistral-7b-instruct-v0.1 | paid
mistralai/mistral-large-2512 | paid
mistralai/mistral-medium-3 | paid
mistralai/mistral-medium-3.1 | paid
mistralai/mistral-nemo | paid
mistralai/mistral-small-24b-instruct-2501 | paid
mistralai/mistral-small-3.1-24b-instruct | paid
mistralai/mistral-small-3.1-24b-instruct:free | free
mistralai/mistral-small-3.2-24b-instruct | paid
mistralai/mistral-small-2603 | paid
mistralai/mistral-small-creative | paid
mistralai/mixtral-8x22b-instruct | paid
mistralai/mixtral-8x7b-instruct | paid
mistralai/pixtral-large-2411 | paid
mistralai/mistral-saba | paid
mistralai/voxtral-small-24b-2507 | paid
moonshotai/kimi-k2 | paid
moonshotai/kimi-k2-0905 | paid
moonshotai/kimi-k2-thinking | paid
moonshotai/kimi-k2.5 | paid
morph/morph-v3-fast | paid
morph/morph-v3-large | paid
gryphe/mythomax-l2-13b | paid
nex-agi/deepseek-v3.1-nex-n1 | paid
nousresearch/hermes-3-llama-3.1-405b | paid
nousresearch/hermes-3-llama-3.1-405b:free | free
nousresearch/hermes-3-llama-3.1-70b | paid
nousresearch/hermes-4-405b | paid
nousresearch/hermes-4-70b | paid
nousresearch/hermes-2-pro-llama-3-8b | paid
nvidia/llama-3.1-nemotron-70b-instruct | paid
nvidia/llama-3.3-nemotron-super-49b-v1.5 | paid
nvidia/nemotron-3-nano-30b-a3b | paid
nvidia/nemotron-3-nano-30b-a3b:free | free
nvidia/nemotron-3-super-120b-a12b | paid
nvidia/nemotron-3-super-120b-a12b:free | free
nvidia/nemotron-nano-12b-v2-vl | paid
nvidia/nemotron-nano-12b-v2-vl:free | free
nvidia/nemotron-nano-9b-v2 | paid
nvidia/nemotron-nano-9b-v2:free | free
openai/gpt-audio | paid
openai/gpt-audio-mini | paid
openai/gpt-3.5-turbo | paid
openai/gpt-3.5-turbo-0613 | paid
openai/gpt-3.5-turbo-16k | paid
openai/gpt-3.5-turbo-instruct | paid
openai/gpt-4 | paid
openai/gpt-4-0314 | paid
openai/gpt-4-turbo | paid
openai/gpt-4-1106-preview | paid
openai/gpt-4-turbo-preview | paid
openai/gpt-4.1 | paid
openai/gpt-4.1-mini | paid
openai/gpt-4.1-nano | paid
openai/gpt-4o | paid
openai/gpt-4o-2024-05-13 | paid
openai/gpt-4o-2024-08-06 | paid
openai/gpt-4o-2024-11-20 | paid
openai/gpt-4o:extended | paid
openai/gpt-4o-audio-preview | paid
openai/gpt-4o-search-preview | paid
openai/gpt-4o-mini | paid
openai/gpt-4o-mini-2024-07-18 | paid
openai/gpt-4o-mini-search-preview | paid
openai/gpt-5 | paid
openai/gpt-5-chat | paid
openai/gpt-5-codex | paid
openai/gpt-5-image | paid
openai/gpt-5-image-mini | paid
openai/gpt-5-mini | paid
openai/gpt-5-nano | paid
openai/gpt-5-pro | paid
openai/gpt-5.1 | paid
openai/gpt-5.1-chat | paid
openai/gpt-5.1-codex | paid
openai/gpt-5.1-codex-max | paid
openai/gpt-5.1-codex-mini | paid
openai/gpt-5.2 | paid
openai/gpt-5.2-chat | paid
openai/gpt-5.2-pro | paid
openai/gpt-5.2-codex | paid
openai/gpt-5.3-chat | paid
openai/gpt-5.3-codex | paid
openai/gpt-5.4 | paid
openai/gpt-5.4-mini | paid
openai/gpt-5.4-nano | paid
openai/gpt-5.4-pro | paid
openai/gpt-oss-120b | paid
openai/gpt-oss-120b:free | free
openai/gpt-oss-20b | paid
openai/gpt-oss-20b:free | free
openai/gpt-oss-safeguard-20b | paid
openai/o1 | paid
openai/o1-pro | paid
openai/o3 | paid
openai/o3-deep-research | paid
openai/o3-mini | paid
openai/o3-mini-high | paid
openai/o3-pro | paid
openai/o4-mini | paid
openai/o4-mini-deep-research | paid
openai/o4-mini-high | paid
perplexity/sonar | paid
perplexity/sonar-deep-research | paid
perplexity/sonar-pro | paid
perplexity/sonar-pro-search | paid
perplexity/sonar-reasoning-pro | paid
prime-intellect/intellect-3 | paid
qwen/qwen-plus-2025-07-28 | paid
qwen/qwen-plus-2025-07-28:thinking | paid
qwen/qwen-vl-max | paid
qwen/qwen-vl-plus | paid
qwen/qwen-max | paid
qwen/qwen-plus | paid
qwen/qwen-turbo | paid
qwen/qwen-2.5-7b-instruct | paid
qwen/qwen2.5-coder-7b-instruct | paid
qwen/qwen2.5-vl-32b-instruct | paid
qwen/qwen2.5-vl-72b-instruct | paid
qwen/qwen-2.5-vl-7b-instruct | paid
qwen/qwen3-14b | paid
qwen/qwen3-235b-a22b | paid
qwen/qwen3-235b-a22b-2507 | paid
qwen/qwen3-235b-a22b-thinking-2507 | paid
qwen/qwen3-30b-a3b | paid
qwen/qwen3-30b-a3b-instruct-2507 | paid
qwen/qwen3-30b-a3b-thinking-2507 | paid
qwen/qwen3-32b | paid
qwen/qwen3-4b:free | free
qwen/qwen3-8b | paid
qwen/qwen3-coder-30b-a3b-instruct | paid
qwen/qwen3-coder | paid
qwen/qwen3-coder:free | free
qwen/qwen3-coder-flash | paid
qwen/qwen3-coder-next | paid
qwen/qwen3-coder-plus | paid
qwen/qwen3-max | paid
qwen/qwen3-max-thinking | paid
qwen/qwen3-next-80b-a3b-instruct | paid
qwen/qwen3-next-80b-a3b-instruct:free | free
qwen/qwen3-next-80b-a3b-thinking | paid
qwen/qwen3-vl-235b-a22b-instruct | paid
qwen/qwen3-vl-235b-a22b-thinking | paid
qwen/qwen3-vl-30b-a3b-instruct | paid
qwen/qwen3-vl-30b-a3b-thinking | paid
qwen/qwen3-vl-32b-instruct | paid
qwen/qwen3-vl-8b-instruct | paid
qwen/qwen3-vl-8b-thinking | paid
qwen/qwen3.5-397b-a17b | paid
qwen/qwen3.5-plus-02-15 | paid
qwen/qwen3.5-122b-a10b | paid
qwen/qwen3.5-27b | paid
qwen/qwen3.5-35b-a3b | paid
qwen/qwen3.5-9b | paid
qwen/qwen3.5-flash-02-23 | paid
qwen/qwq-32b | paid
qwen/qwen-2.5-72b-instruct | paid
qwen/qwen-2.5-coder-32b-instruct | paid
relace/relace-apply-3 | paid
relace/relace-search | paid
undi95/remm-slerp-l2-13b | paid
sao10k/l3-lunaris-8b | paid
sao10k/l3-euryale-70b | paid
sao10k/l3.1-70b-hanami-x1 | paid
sao10k/l3.1-euryale-70b | paid
sao10k/l3.3-euryale-70b | paid
stepfun/step-3.5-flash | paid
stepfun/step-3.5-flash:free | free
switchpoint/router | paid
tencent/hunyuan-a13b-instruct | paid
thedrummer/cydonia-24b-v4.1 | paid
thedrummer/rocinante-12b | paid
thedrummer/skyfall-36b-v2 | paid
thedrummer/unslopnemo-12b | paid
tngtech/deepseek-r1t2-chimera | paid
alibaba/tongyi-deepresearch-30b-a3b | paid
upstage/solar-pro-3 | paid
cognitivecomputations/dolphin-mistral-24b-venice-edition:free | free
microsoft/wizardlm-2-8x22b | paid
writer/palmyra-x5 | paid
x-ai/grok-3 | paid
x-ai/grok-3-beta | paid
x-ai/grok-3-mini | paid
x-ai/grok-3-mini-beta | paid
x-ai/grok-4 | paid
x-ai/grok-4-fast | paid
x-ai/grok-4.1-fast | paid
x-ai/grok-4.20-beta | paid
x-ai/grok-4.20-multi-agent-beta | paid
x-ai/grok-code-fast-1 | paid
xiaomi/mimo-v2-flash | paid
xiaomi/mimo-v2-omni | paid
xiaomi/mimo-v2-pro | paid
z-ai/glm-4-32b | paid
z-ai/glm-4.5 | paid
z-ai/glm-4.5-air | paid
z-ai/glm-4.5-air:free | free
z-ai/glm-4.5v | paid
z-ai/glm-4.6 | paid
z-ai/glm-4.6v | paid
z-ai/glm-4.7 | paid
z-ai/glm-4.7-flash | paid
z-ai/glm-5 | paid
z-ai/glm-5-turbo | paid

 OpenAI models:

babbage-002
chatgpt-image-latest
dall-e-2
dall-e-3
davinci-002
gpt-3.5-turbo
gpt-3.5-turbo-0125
gpt-3.5-turbo-1106
gpt-3.5-turbo-16k
gpt-3.5-turbo-instruct
gpt-3.5-turbo-instruct-0914
gpt-4
gpt-4-0125-preview
gpt-4-0613
gpt-4-1106-preview
gpt-4-turbo
gpt-4-turbo-2024-04-09
gpt-4-turbo-preview
gpt-4.1
gpt-4.1-2025-04-14
gpt-4.1-mini
gpt-4.1-mini-2025-04-14
gpt-4.1-nano
gpt-4.1-nano-2025-04-14
gpt-4o
gpt-4o-2024-05-13
gpt-4o-2024-08-06
gpt-4o-2024-11-20
gpt-4o-audio-preview
gpt-4o-audio-preview-2024-12-17
gpt-4o-audio-preview-2025-06-03
gpt-4o-mini
gpt-4o-mini-2024-07-18
gpt-4o-mini-audio-preview
gpt-4o-mini-audio-preview-2024-12-17
gpt-4o-mini-realtime-preview
gpt-4o-mini-realtime-preview-2024-12-17
gpt-4o-mini-search-preview
gpt-4o-mini-search-preview-2025-03-11
gpt-4o-mini-transcribe
gpt-4o-mini-transcribe-2025-03-20
gpt-4o-mini-transcribe-2025-12-15
gpt-4o-mini-tts
gpt-4o-mini-tts-2025-03-20
gpt-4o-mini-tts-2025-12-15
gpt-4o-realtime-preview
gpt-4o-realtime-preview-2024-12-17
gpt-4o-realtime-preview-2025-06-03
gpt-4o-search-preview
gpt-4o-search-preview-2025-03-11
gpt-4o-transcribe
gpt-4o-transcribe-diarize
gpt-5
gpt-5-2025-08-07
gpt-5-chat-latest
gpt-5-codex
gpt-5-mini
gpt-5-mini-2025-08-07
gpt-5-nano
gpt-5-nano-2025-08-07
gpt-5-pro
gpt-5-pro-2025-10-06
gpt-5-search-api
gpt-5-search-api-2025-10-14
gpt-5.1
gpt-5.1-2025-11-13
gpt-5.1-chat-latest
gpt-5.1-codex
gpt-5.1-codex-max
gpt-5.1-codex-mini
gpt-5.2
gpt-5.2-2025-12-11
gpt-5.2-chat-latest
gpt-5.2-codex
gpt-5.2-pro
gpt-5.2-pro-2025-12-11
gpt-5.3-chat-latest
gpt-5.3-codex
gpt-5.4
gpt-5.4-2026-03-05
gpt-5.4-mini
gpt-5.4-mini-2026-03-17
gpt-5.4-nano
gpt-5.4-nano-2026-03-17
gpt-5.4-pro
gpt-5.4-pro-2026-03-05
gpt-audio
gpt-audio-1.5
gpt-audio-2025-08-28
gpt-audio-mini
gpt-audio-mini-2025-10-06
gpt-audio-mini-2025-12-15
gpt-image-1
gpt-image-1-mini
gpt-image-1.5
gpt-realtime
gpt-realtime-1.5
gpt-realtime-2025-08-28
gpt-realtime-mini
gpt-realtime-mini-2025-10-06
gpt-realtime-mini-2025-12-15
o1
o1-2024-12-17
o1-pro
o1-pro-2025-03-19
o3
o3-2025-04-16
o3-mini
o3-mini-2025-01-31
o4-mini
o4-mini-2025-04-16
o4-mini-deep-research
o4-mini-deep-research-2025-06-26
omni-moderation-2024-09-26
omni-moderation-latest
sora-2
sora-2-pro
text-embedding-3-large
text-embedding-3-small
text-embedding-ada-002
tts-1
tts-1-1106
tts-1-hd
tts-1-hd-1106
whisper-1
-----

 Deepseek models:

deepseek-chat
deepseek-reasoner
-----
*/
model?: string | undefined;
/** Router to use: openai, openrouter or deepseek */
router?: string;
/** Chat completion mode:
completion, tools, assistant.
completion: no support for tools, please use --dst parameter to save the output.
tools: allows for tools to be used, eg 'save to ./output.md'. Not all models support this mode.
responses: allows for responses to be used, eg 'save to ./output.md'. Not all models support this mode.
assistant: : allows documents (PDF, DOCX, ...) to be added but dont support tools. Use --dst to save the output. Supported files :
custom: custom mode
*/
mode?: "completion" | "tools" | "assistant" | "responses" | "custom";
/** Logging level for the application */
logLevel?: number;
/** Path to profile for variables. Supports environment variables. */
profile?: string | undefined;
/** Base URL for the API, set via --router or directly */
baseURL?: string | undefined;
/** Path to JSON configuration file (API keys). Supports environment variables. */
config?: string | undefined;
/** Create a script */
dump?: string | undefined;
/** Path to preferences file, eg: location, your email address, gender, etc. Supports environment variables. */
preferences?: string;
/** Logging directory */
logs?: string;
/** Enable streaming (verbose LLM output) */
stream?: boolean;
/** Use alternate tokenizer & instead of $ */
alt?: boolean;
/** Environment (in profile) */
env?: string;
variables?: {
[x: string]: string;
};
/** List of filters to apply to the output.
Used only in completion mode and a given output file specified with --dst.
It unwraps by default any code or data in Markdown.
Choices:
JSON,JSONUnescape,JSONPretty,AlphaSort,code,JSONParse,trim,markdown
*/
filters?: (string | ("JSON" | "JSONUnescape" | "JSONPretty" | "AlphaSort" | "code" | "JSONParse" | "trim" | "markdown")[] | string[] | ((...args_0: unknown[]) => unknown)[]);
/** JSONPath query to be used to transform input objects */
query?: (string | null);
/** Dry run - only write out parameters without making API calls */
dry?: (boolean | string);
/** Format for structured outputs. Can be a Zod schema, a Zod schema string, a JSON schema string, or a path to a JSON file. */
format?: (string | any) | undefined;
}

View File

@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidHlwZXNfa2JvdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9haS10b29scy90eXBlc19rYm90LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIifQ==

20
packages/kbot/dist/package.json vendored Normal file
View File

@ -0,0 +1,20 @@
{
"name": "@plastichub/kbot",
"version": "1.3.0",
"main": "main_node.js",
"author": "",
"license": "ISC",
"description": "",
"bin": {
"kbot": "./main_node.js"
},
"dependencies": {
"node-emoji": "^2.2.0"
},
"publishConfig": {
"access": "public"
},
"optionalDependencies": {
"puppeteer": "^23.11.1"
}
}

0
packages/kbot/src/ai-tools/.gitignore vendored Normal file
View File

View File

@ -0,0 +1,27 @@
import type { Argv } from 'yargs'
import { types } from './types.js'
import { list, options as listOptions } from './list.js'
import { invoke, invokeOptions } from './invoke.js'
import { CONFIG_DEFAULT } from '@polymech/commons'
import { logger } from '../index.js'
export const commands = (yargs: Argv) => {
return yargs
.command('types', 'Generate TypeScript interfaces from Zod schemas', {}, types)
.command('list', 'List all available tools and their descriptions', listOptions, list)
.command('invoke', 'Invoke a specific tool function', invokeOptions, invoke)
.option('env_key', {
type: 'string',
description: 'Environment configuration key'
})
.middleware([(argv) => {
const config = CONFIG_DEFAULT(argv.env_key) as any;
if (!config) {
logger.warn('No config found!');
return;
}
return config;
}])
.strict()
.help();
};

View File

@ -0,0 +1,55 @@
import { tools } from '../lib/tools/tools.js'
import { logger } from '../index.js'
import { InvokeToolSchema } from '../zod_schemas.js'
import { toYargs } from '@polymech/commons/schemas'
import { sync as write } from '@polymech/fs/write'
import type { Argv } from 'yargs'
import * as path from 'path'
const options = (yargs: Argv) => toYargs(yargs, InvokeToolSchema);
export const invoke = async (argv: any) => {
try {
const { tools: toolCategory, function: funcName, target, params, output } = argv;
// Get tool category
const toolSet = tools[toolCategory];
if (!toolSet) {
logger.error(`Tool category '${toolCategory}' not found`);
return;
}
// Initialize tools with target directory
const toolList = toolSet(target);
// Find specific function
const tool = toolList.find(t => t.function.name === funcName);
if (!tool) {
logger.error(`Function '${funcName}' not found in ${toolCategory} tools`);
return;
}
// Parse parameters if provided
const parameters = params ? JSON.parse(params) : {};
// Execute tool function
logger.info(`Invoking ${toolCategory}::${funcName}`);
const result = await tool.function.function(parameters);
// Handle output
if (output) {
const outputPath = path.isAbsolute(output) ? output : path.join(process.cwd(), output);
logger.info(`Writing output to ${outputPath}`);
write(outputPath, JSON.stringify(result, null, 2));
} else {
logger.info('Result:', result);
}
return result;
} catch (error) {
logger.error('Error invoking tool:', error);
throw error;
}
};
export { options as invokeOptions };

View File

@ -0,0 +1,75 @@
import type { Argv } from 'yargs'
import { tools } from '../lib/tools/tools.js'
import { logger } from '../index.js'
import { ListCommandSchema } from '../zod_schemas.js'
import { sync as write } from '@polymech/fs/write'
import { toYargs } from '@polymech/commons/schemas'
export const options = (yargs: Argv) => toYargs(yargs, ListCommandSchema)
interface FSParameters {
type: string;
properties: Record<string, any>;
required: string[];
}
interface FSDefinition {
name: string;
description: string;
category: string;
parameters: FSParameters;
}
interface FSData {
fs: FSDefinition[];
}
export const signature = (definition: FSDefinition): string => {
const { properties } = definition.parameters;
const requiredKeys = definition.parameters.required || [];
const params = Object.entries(properties).map(([key, val]) => {
const isRequired = requiredKeys.includes(key);
const isOptional = !!val.optional || !isRequired;
return isOptional ? `?${key}` : key;
});
return `(${params.join(", ")})`;
}
export function format(category: string, data: any): string {
const lines: string[] = [`## ${category}\n`];
data.forEach(definition => {
const functionName = definition.name
const args = `${signature(definition)}`
const summary = definition.description
lines.push(`- ${functionName}${args}: ${summary}`)
})
return lines.join("\n")
}
export const list = async (argv: any, options?: any) => {
const getCategorizedTools = (category, options) => {
const toolsArray = tools[category](process.cwd(), options);
return toolsArray.map(tool => ({
name: tool.function.name,
description: tool.function.description,
category,
parameters: tool.function.parameters
}));
}
const toolsList = {
email: getCategorizedTools('email', options),
search: getCategorizedTools('search', options),
interact: getCategorizedTools('email', options),
fs: getCategorizedTools('fs', options),
npm: getCategorizedTools('npm', options),
git: getCategorizedTools('git', options),
terminal: getCategorizedTools('terminal', options)
}
//write(argv.output + '.json', Object.keys(toolsList).map((k,v)=>format(k,v as any)).join('\n') );
const shortDescription = Object.keys(toolsList).map((value:string) => {
return format(value,toolsList[value])
}).join('\n\n');
if (argv.output) {
write(argv.output, JSON.stringify(toolsList, null, 2))
write(argv.output + '.md', shortDescription)
}
}

View File

@ -0,0 +1,29 @@
import { generate_interfaces } from '@polymech/commons'
import {
FileListingOptionsSchema,
FileRemovalOptionsSchema,
GitCommitSchema,
GitRevertSchema,
GitSwitchVersionSchema,
InvokeToolSchema,
ToolListingOptionsSchema,
TerminalCommandSchema,
ListCommandSchema,
NpmRunSchema
} from '../zod_schemas.js'
export const types = async () => {
return generate_interfaces([
FileListingOptionsSchema,
FileRemovalOptionsSchema,
GitCommitSchema,
GitRevertSchema,
GitSwitchVersionSchema,
InvokeToolSchema,
ToolListingOptionsSchema,
TerminalCommandSchema,
ListCommandSchema,
NpmRunSchema
], 'src/zod_types.ts')
}

View File

@ -0,0 +1,11 @@
export const LOGGER_NAME = 'llm-tools-cli'
export const EXCLUDE_GLOB = [
"**/node_modules/**",
"**/dist/**",
"**/build/**",
"**/coverage/**",
"*.log",
".kbot",
".git"
]

View File

@ -0,0 +1,186 @@
import * as path from 'path'
import { z } from 'zod'
import { ISettingsParam, Logger } from "tslog"
import * as winston from 'winston'
import TransportStream from 'winston-transport'
import { SeqTransport} from '@datalust/winston-seq'
import { createStream } from "rotating-file-stream"
import { CONFIG_DEFAULT } from '@polymech/commons'
import { sync as read } from '@polymech/fs/read'
import { sync as write } from '@polymech/fs/write'
import { sync as exists } from '@polymech/fs/exists'
import { IKBotTask } from './types.js'
export let logger: Logger<unknown> = createLogger('osr-ai-tools')
export const TLogLevelNameSchema = z.enum(["silly", "trace", "debug", "info", "warn", "error", "fatal"])
export type LogLevel = z.infer<typeof TLogLevelNameSchema>
export enum LogLevelEx {
silly,
trace,
debug,
info,
warn,
error,
fatal
}
export enum ELogTargets {
Console = 1 << 0,
FileText = 1 << 1,
FileJson = 1 << 2,
Seq = 1 << 3
}
export function createLogger(name: string, options?: ISettingsParam<any>) {
return new Logger<unknown>({
name,
type: 'pretty',
...options,
})
}
export const defaultLogger = createLogger('DefaultLogger', {
minLevel: LogLevelEx.info
})
class JsonArrayFileTransport extends TransportStream {
filename: string;
constructor(opts) {
super(opts);
opts.filename = opts.filename
this.filename = opts.filename || 'logs.json';
setImmediate(() => this.emit('opened'))
}
log(info: any, next: () => void): void {
setImmediate(() => this.emit('logged', info))
const { level, message, exception, stack, ...props } = info;
const fileExists = exists(this.filename)
const existingLogs = fileExists
? read(this.filename, 'json') as []
: [];
const entry = {
level: info.level,
message: info.message,
timestamp: new Date().toISOString(),
...info
};
existingLogs.push(entry)
write(this.filename, existingLogs)
next()
}
close(): void {
setImmediate(() => this.emit('closed'))
}
flush(): Promise<any> {
return new Promise((resolve, reject) => {
resolve(true)
})
}
}
class TSLogTransport extends TransportStream {
constructor(opts) {
super(opts);
setImmediate(() => this.emit('opened'))
}
log(info: any, next: () => void): void {
setImmediate(() => this.emit('logged', info))
const { level, message, exception, stack, ...props } = info;
defaultLogger.info(info)
next()
}
}
export const winstonLogger = (name: string, file: string, targets: ELogTargets = ELogTargets.Console | ELogTargets.FileJson) => {
const logger = winston.createLogger({
defaultMeta: { service: name },
level: 'debug',
transports: []
})
if (targets & ELogTargets.Console) {
//logger.add(new TSLogTransport({}))
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp({ format: 'MM/DD/YYYY hh:mm:ss.SSS' }),
///winston.format.json(),
winston.format.colorize(),
winston.format.printf(info => {
let message = null
try {
message = JSON.stringify(info.message)
} catch (e) {
}
return `[${info.level}] [${name}] | message: ${message.substring(0, 200)}`
}))
}))
}
if (targets & ELogTargets.FileText) {
logger.add(new winston.transports.File({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.timestamp({ format: 'MM/DD/YYYY hh:mm:ss.SSS' }),
winston.format.json(),
winston.format.printf(info => {
return JSON.stringify(info, null, 2);
})),
dirname: path.parse(file).dir,
filename: path.parse(file).base
}))
}
if (targets & ELogTargets.FileJson) {
logger.add(new JsonArrayFileTransport({
filename: file
}))
}
if (targets & ELogTargets.Seq) {
const config = CONFIG_DEFAULT() as any
if (config.seq) {
logger.add(new SeqTransport({
...config.seq,
onError: (e => { })
}))
}
}
return logger
}
export const createFileLogger = (logger: Logger<unknown>, level: number, file: string): Logger<unknown> => {
const rfs = createStream(file,
{
size: "10M", // rotate every 10 MegaBytes written
interval: "1d", // rotate daily
compress: "gzip", // compress rotated files
});
const log = new Logger({
type: "json",
attachedTransports: [
(logObj) => {
rfs.write(JSON.stringify(logObj) + "\n");
},
],
});
return log
}
export const toolLoggerTS = (name, options: IKBotTask) => {
let log = createLogger(name)
//log.settings.minLevel = options.logLevel
log = createFileLogger(log,options.logLevel, path.join(options.logs,`tools-${name}.json`))
return log
}
export const toolLogger = (name, options: IKBotTask = { logs: process.cwd() } as IKBotTask ) => {
const logPath = path.resolve(path.join(options.logs || './',`tools-${name}.json`))
const log = winstonLogger(name, logPath, ELogTargets.Console)
return log
}
export * from './types.js'
export * from './types_kbot.js'

View File

@ -0,0 +1,55 @@
import { tools } from '../lib/tools/tools.js'
import { logger } from '../index.js'
import { InvokeToolSchema } from '../zod_schemas.js'
import { toYargs } from '@polymech/commons/schemas'
import { sync as write } from '@polymech/fs/write'
import type { Argv } from 'yargs'
import * as path from 'path'
const options = (yargs: Argv) => toYargs(yargs, InvokeToolSchema);
export const invoke = async (argv: any) => {
try {
const { tools: toolCategory, function: funcName, target, params, output } = argv;
// Get tool category
const toolSet = tools[toolCategory];
if (!toolSet) {
logger.error(`Tool category '${toolCategory}' not found`);
return;
}
// Initialize tools with target directory
const toolList = toolSet(target);
// Find specific function
const tool = toolList.find(t => t.function.name === funcName);
if (!tool) {
logger.error(`Function '${funcName}' not found in ${toolCategory} tools`);
return;
}
// Parse parameters if provided
const parameters = params ? JSON.parse(params) : {};
// Execute tool function
logger.info(`Invoking ${toolCategory}::${funcName}`);
const result = await tool.function.function(parameters);
// Handle output
if (output) {
const outputPath = path.isAbsolute(output) ? output : path.join(process.cwd(), output);
logger.info(`Writing output to ${outputPath}`);
write(outputPath, JSON.stringify(result, null, 2));
} else {
logger.info('Result:', result);
}
return result;
} catch (error) {
logger.error('Error invoking tool:', error);
throw error;
}
};
export { options as invokeOptions };

View File

@ -0,0 +1,435 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { sync as rm } from '@polymech/fs/remove'
import { isString } from '@polymech/core/primitives'
import { sync as write } from '@polymech/fs/write'
import { sync as read } from '@polymech/fs/read'
import { sync as rename } from '@polymech/fs/rename'
import { sync as exists } from '@polymech/fs/exists'
import { sanitize } from "@polymech/fs/utils"
import { filesEx } from '@polymech/commons'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
import { EXCLUDE_GLOB } from '../../constants.js'
import { glob } from 'glob'
const isBase64 = (str: string): boolean => {
// 1. Quick checks for length & allowed characters:
// - Must be multiple of 4 in length
// - Must match Base64 charset (A-Z, a-z, 0-9, +, /) plus optional "=" padding
if (!str || str.length % 4 !== 0) {
return false;
}
const base64Regex = /^[A-Za-z0-9+/]+={0,2}$/;
if (!base64Regex.test(str)) {
return false;
}
// 2. Attempt decodere-encode to confirm validity:
try {
const decoded = atob(str); // Decode from Base64
const reencoded = btoa(decoded); // Re-encode to Base64
// Compare the re-encoded string to original
return reencoded === str;
} catch {
return false;
}
}
export const decode_base64 = (base64: string): string => {
try {
if(!isBase64(base64)) {
return base64
}
return Buffer.from(base64, 'base64').toString('utf-8');
} catch (error) {
throw new Error('Failed to decode base64 string');
}
};
// Helper function for smart Base64 decoding
const decodeContentSmart = (content: string, logger: any, identifier: string): string => {
if (!content || typeof content !== 'string') {
return content; // Return original content if null, undefined, or not a string
}
const lines = content.split(/\r?\n/);
const processedLines = lines.map(line => {
const trimmedLine = line.trim();
if (!trimmedLine) {
return ''; // Preserve empty lines between potential blocks but decode the blocks themselves
}
try {
// Attempt to decode Base64
const decodedLine = Buffer.from(trimmedLine, 'base64').toString('utf-8');
// Validate if it was actually Base64 by re-encoding
const reEncodedLine = Buffer.from(decodedLine, 'utf-8').toString('base64');
// Revised Validation Check:
// Compare original trimmed line with re-encoded line.
// Allow for potential padding differences by checking both exact match and no-pad match.
const originalNoPad = trimmedLine.replace(/={1,2}$/, '');
const reEncodedNoPad = reEncodedLine.replace(/={1,2}$/, '');
if (reEncodedLine === trimmedLine || reEncodedNoPad === originalNoPad) {
logger.debug(`Successfully decoded Base64 line for ${identifier}`);
return decodedLine;
}
// If validation fails, treat as plain text
logger.debug(`Re-encoding mismatch for ${identifier}. Original: '${trimmedLine}', Re-encoded: '${reEncodedLine}', using original trimmed line.`);
return trimmedLine;
} catch (decodeError) {
// If decoding throws an error, assume it's plain text
// Use debug level as this is expected for non-base64 lines
logger.debug(`Base64 decoding failed for line in ${identifier}, assuming plain text. Line: ${trimmedLine}`);
return trimmedLine; // Return original trimmed line
}
});
// Join the processed lines back together
return processedLines.join('\n');
};
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('fs', options)
return [
{
type: 'function',
function: {
name: 'list_files',
description: 'List all files in a directory',
parameters: {
type: 'object',
properties: {
directory: { type: 'string' },
pattern: { type: 'string', optional: true }
},
required: ['directory']
},
function: async (params: any) => {
try {
const directory = path.join(target, sanitize(params.directory));
if (!exists(directory)) {
logger.debug(`Tool::ListFiles Directory ${directory} does not exist`);
return []
}
let pattern = params.pattern || '**/*';
logger.debug(`Tool::ListFiles Listing files in ${directory} with pattern ${pattern}`);
pattern = [
...EXCLUDE_GLOB,
pattern
]
const ret = await glob(pattern, {
cwd: directory,
absolute: false,
ignore: EXCLUDE_GLOB
});
return ret
} catch (error) {
logger.error('Error listing files', error);
throw error;
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'read_files',
description: 'Reads files in a directory with a given pattern',
parameters: {
type: 'object',
properties: {
directory: { type: 'string' },
pattern: { type: 'string', optional: true }
},
required: ['directory']
},
function: async (params: any) => {
try {
const pattern = params.pattern || '**/*';
let entries = filesEx(target, pattern);
let ret = entries.map((entry) => {
try {
let content = read(entry);
return {
path: path.relative(target, entry).replace(/\\/g, '/'),
content: content.toString()
}
} catch (error) {
logger.error(`Error reading file ${entry}:`, error)
return null
}
})
ret = ret.filter((entry) => (entry !== null && entry.content))
logger.debug(`Tool::ReadFiles Reading files in ${target} with pattern ${pattern} : ${ret.length} files`, ret.map((entry) => entry.path));
return ret
} catch (error) {
logger.error('Error listing files', error);
throw error;
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'remove_file',
description: 'Remove a file at given path',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
},
required: ['path']
},
function: async (params: any) => {
try {
const filePath = path.join(target, sanitize(params.path));
logger.debug(`Tool::RemoveFile Removing file ${filePath}`);
rm(filePath);
return true;
} catch (error) {
logger.error('Error removing file', error);
throw error;
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'rename_file',
description: 'Rename or move a file or directory',
parameters: {
type: 'object',
properties: {
src: { type: 'string' },
dst: { type: 'string' }
},
required: ['path']
},
function: async (params: any) => {
try {
const src = path.join(target, sanitize(params.src))
const dst = path.join(target, sanitize(params.dst))
logger.debug(`Tool::Rename file ${src} to ${dst}`)
rename(src, dst)
rm(src)
return true
} catch (error) {
logger.error('Error removing file', error)
throw error
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: "modify_project_files",
description: "Create or modify existing project files in one shot, preferably used for creating project structure)",
parameters: {
type: "object",
properties: {
files: {
type: "array",
items: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string", description: "new file content (Part of JSON payload)" }
},
required: ["path", "content"]
}
}
},
required: ["files"],
},
function: async (ret) => {
try {
if (!target) {
logger.error(`Tool::FS:modify_project_files : Root path required`)
return
}
let { files } = ret as any
if (isString(files)) {
try {
files = JSON.parse(files)
} catch (error: any) {
logger.error(`Tool::modify_project_files : Structure Error parsing files`, error, ret)
// Consider writing the raw input for debugging if JSON parsing fails
// write(path.join(target, 'tools-output-error.json'), files)
return error.message
}
}
for (const file of files) {
const sanitizedPath = sanitize(file.path);
const filePath = path.join(target, sanitizedPath);
logger.debug(`Tool:modify_project_files writing file ${filePath}`)
try {
// const contentToWrite = decodeContentSmart(file.content, logger, sanitizedPath);
try {
await write(filePath, file.content)
} catch (writeError) {
logger.error(`Tool:modify_project_files Error writing file ${filePath}`, writeError)
}
} catch (error) {
logger.error(`Tool:modify_project_files Error processing file content for ${filePath}`, error)
}
}
} catch (error) {
logger.error(`Error creating project structure`, error)
}
},
parse: JSON.parse,
},
} as RunnableToolFunction<{ id: string }>,
{
type: 'function',
function: {
name: "write_file",
description: "Writes to a file, given a path and content (Part of JSON payload). No directory or file exists check needed!",
parameters: {
type: "object",
properties: {
file: {
type: "object",
properties: {
path: { type: "string" },
content: { type: "string", description: "new file content (Part of JSON payload)" }
}
}
},
required: ["file"],
},
function: async (params) => {
let fileInfo;
try {
if (isString(params)) {
try {
params = JSON.parse(params)
} catch (error: any) {
logger.error(`Tool::write_file : Structure Error parsing JSON`, error, params)
return error.message
}
}
fileInfo = (params as any).file; // Keep fileInfo accessible
if (!target || !fileInfo || !fileInfo.path || typeof fileInfo.content === 'undefined') {
logger.error(`Tool::write_file : Path/Target/Content are required`, fileInfo)
return false; // Indicate failure
}
const sanitizedPath = sanitize(fileInfo.path);
const filePath = path.join(target, sanitizedPath)
logger.debug(`Tool::write_file Writing file ${filePath}`)
try {
// Use the smart decoding helper function
// const contentToWrite = decodeContentSmart(fileInfo.content, logger, sanitizedPath);
await write(filePath, fileInfo.content)
return true
} catch (error) {
// Log error related to processing or writing the file
logger.error(`Tool:write_file Error processing or writing file ${sanitizedPath}`, error)
return false // Indicate failure
}
} catch (error) {
logger.error(`Tool:write_file Error writing file ${fileInfo?.path ? sanitize(fileInfo.path) : 'unknown'}`, error)
return false // Indicate failure
}
},
parse: JSON.parse,
},
} as RunnableToolFunction<{ id: string }>,
{
type: 'function',
function: {
name: "file_exists",
description: "check if a file or folder exists",
parameters: {
type: "object",
properties: {
file: {
type: "object",
properties: {
path: { type: "string" }
}
}
},
required: ["file"],
},
function: async (ret) => {
try {
if (isString(ret)) {
try {
ret = JSON.parse(ret)
} catch (error: any) {
logger.error(`Tool::file_exists : Structure Error parsing files`, error, ret)
return error.message
}
}
const { file } = ret as any
if (!target || !file.path) {
logger.error(`Tool::file_exists : Path is required`, ret)
return
}
const sanitizedPath = sanitize(file.path);
const filePath = path.join(target, sanitizedPath)
const res = exists(filePath)
logger.debug(`Tool::file_exists ${filePath} exists: ${res}`)
return res ? true : false
} catch (error) {
logger.error(`Tool:file_exists error`, error)
return false
}
},
parse: JSON.parse,
},
} as RunnableToolFunction<{ id: string }>,
{
type: 'function',
function: {
name: "read_file",
description: "read a file, at given a path",
parameters: {
type: "object",
properties: {
file: {
type: "object",
properties: {
path: { type: "string" }
}
}
},
required: ["file"],
},
function: async (ret) => {
try {
const { file } = ret as any
const sanitizedPath = sanitize(file.path);
const filePath = path.join(target, sanitizedPath)
logger.debug(`Tool::ReadFile Reading file ${filePath}`)
return read(filePath, 'string')
} catch (error) {
logger.error(`Error reading file`, error)
}
},
parse: JSON.parse
}
} as RunnableToolFunction<{ id: string }>
]
};

View File

@ -0,0 +1,157 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { simpleGit } from 'simple-git'
import { sync as exists } from '@polymech/fs/exists'
import { substitute } from '@polymech/commons'
import { logger } from '../../index.js'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
import { findUpSync } from 'find-up'
const commitFiles = async (filePaths: string[], commitMessage: string, targetDirectory: string, variables: Record<string, string> = {}) => {
try {
if (!filePaths || !filePaths.length) {
logger.warn(`No files to commit`)
return
}
if (!exists(path.join(targetDirectory, '.git'))) {
try {
logger.info(`Initializing repository at ${targetDirectory}`)
await initRepository(targetDirectory)
} catch (e: any) {
logger.error(`Error initializing repository at ${targetDirectory} `, e.message, filePaths)
}
}
const git: any = simpleGit(targetDirectory);
try {
await git.add(filePaths);
} catch (e: any) {
logger.error('Error adding files:', e.message, filePaths);
}
await git.commit(commitMessage)
try {
await git.raw(['branch', '-M', 'master']);
const repo = substitute(false, "${GIT_REPO}/${GIT_USER}/${REPO_NAME}.git", {
REPO_NAME: path.basename(targetDirectory),
...variables
})
await git.raw(['remote', 'add', 'origin', repo])
await git.push(['--set-upstream', 'origin', 'master'])
return true
} catch (e: any) {
if (e.message.includes('remote origin already exists')) {
await git.push(['--set-upstream', 'origin', 'master']);
} else {
logger.error('Tools::GIT : Error pushing files:', e.message, filePaths);
}
}
} catch (error: any) {
logger.error('Error committing files:', error.message, filePaths);
throw error
}
}
const initRepository = async (targetDirectory: string, variables: Record<string, string> = {}): Promise<boolean> => {
try {
const git: any = simpleGit(targetDirectory);
if (!exists(path.join(targetDirectory, '.git'))) {
await git.init();
logger.info('Git repository initialized successfully!');
return true;
}
logger.info('Git repository already exists');
return false
} catch (error: any) {
logger.error('Error initializing git repository:', error.message);
return false
}
}
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('git', options)
if (!target) {
logger.warn(`Tools:GIT : Target is required`)
return []
}
if (!exists(target)) {
logger.warn(`Tools:GIT : Project path doesnt exists ${target}`)
return []
}
return [
{
type: 'function',
function: {
name: "init_repository",
description: "Initialize a new git repository",
parameters: {
type: "object",
properties: {},
required: []
},
function: async (params) => {
logger.info(`Tool::init_repository Init Repository in ${target}`)
const gitDir = findUpSync('.git',{ type: 'directory', cwd: target})
if(gitDir && exists(gitDir)){
logger.info(`Repository already exists at ${gitDir}`)
return true
}
try {
const ret = await initRepository(target, options.variables)
return true
} catch (error) {
logger.error(`Error initializing repository`, error)
return false;
}
},
parse: JSON.parse,
}
} as RunnableToolFunction<{ id: string }>,
{
type: 'function',
function: {
name: "commit_files_git",
description: "Commit files using git",
parameters: {
type: "object",
properties: {
files: {
type: "array",
items: {
type: "string"
}
},
message: {
type: "string"
}
},
required: ["files"],
},
function: async (ret) => {
debugger
try {
const { files, message } = ret as any
logger.info(`Tool::GIT Commit files ${files} in ${target}`)
if (!target) {
logger.error(`Tool::Git Commit : Target is required`)
return
}
if (!exists(target)) {
logger.error(`Project doesnt path exists ${target}`)
return
}
await commitFiles(files, message, target, options.variables)
} catch (error: any) {
logger.error(`Error committing dependencies : ${error.message}`)
}
return true
},
parse: JSON.parse,
},
} as RunnableToolFunction<{ id: string }>
]
}

View File

@ -0,0 +1,30 @@
import { RunnableToolFunctionWithParse } from 'openai/lib/RunnableFunction'
import { JSONSchema } from 'openai/lib/jsonschema'
import { ZodSchema, z } from 'zod'
// see https://github.com/openai/openai-node/blob/master/examples/tool-call-helpers-zod.ts
export const zodFunction =<T extends object>({
function: fn,
schema,
description = '',
name,
}: {
function: (args: T) => Promise<object>
schema: ZodSchema<T>
description?: string
name?: string
}): RunnableToolFunctionWithParse<T> => {
return {
type: 'function',
function: {
function: fn,
name: name ?? fn.name,
description: description,
parameters: z.toJSONSchema(schema) as JSONSchema,
parse(input: string): T {
const obj = JSON.parse(input)
return schema.parse(obj)
}
}
}
}

View File

@ -0,0 +1,89 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
import { input, select } from '@inquirer/prompts'
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('interact', options)
return [
{
type: 'function',
function: {
name: 'ask_question',
description: 'Ask user a simple question and get response',
parameters: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'Question to ask the user'
},
default: {
type: 'string',
description: 'Default answer',
optional: true
}
},
required: ['question']
},
function: async (params: any) => {
try {
const answer = await input(
{
message: params.question,
default: params.default
}
)
return { response: answer }
} catch (error: any) {
logger.error('Error asking question:', error.message);
return null;
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'choose_option',
description: 'Ask user to choose from multiple options',
parameters: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'Message to show the user'
},
choices: {
type: 'array',
items: { type: 'string' },
description: 'List of choices'
},
multiple: {
type: 'boolean',
description: 'Allow multiple selections',
optional: true
}
},
required: ['message', 'choices']
},
function: async (params: any) => {
try {
const answer = await select(
{
message: params.message,
choices: params.choices
}
);
return { response: answer }
} catch (error: any) {
logger.error('Error in choice selection:', error.message);
return null;
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
]
}

View File

@ -0,0 +1,21 @@
import * as path from 'path'
import Keyv from 'keyv'
import { KeyvFile } from 'keyv-file'
import { resolve } from '@polymech/commons'
export const store = (storePath: string, ns: string = 'ns-unknown', opts: any = {}) => {
const keyvFile = new KeyvFile({
filename: path.resolve(resolve(storePath)),
writeDelay: 100, // ms
})
return new Keyv({ store: keyvFile, namespace: ns, ...opts })
}
export const get = async (key: string, storePath: string, ns: string = 'ns-unknown', opts: any = {}) => {
const keyv = store(storePath, ns, opts)
return await keyv.get(key)
}
export const set = async (key: string, value: any, storePath: string, ns: string = 'ns-unknown', opts: any = {}) => {
const keyv = store(storePath, ns, opts)
return await keyv.set(key, value)
}

View File

@ -0,0 +1,379 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { isString } from '@polymech/core/primitives'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
import { store, get, set } from './keyv.js'
// Helper function to get storage path
const getStoragePath = (options: IKBotTask): string => {
// For now, use default path. Later this can be configured via options
return path.join(process.cwd(), 'memory.json');
};
// Default collection name when none provided
const DEFAULT_COLLECTION = 'no-collection';
// Supported data formats
type DataFormat = 'text' | 'json' | 'binary';
// Memory entry structure
interface MemoryEntry {
value: string;
meta: {
type: DataFormat;
created: string;
updated: string;
};
}
// Helper function to process value based on format
const processValueForStorage = (value: string, format: DataFormat): string => {
switch (format) {
case 'json':
try {
// Validate JSON by parsing and re-stringifying
JSON.parse(value);
return value;
} catch (error) {
throw new Error('Invalid JSON format provided');
}
case 'binary':
// For binary, we expect base64 encoded data
try {
// Validate base64
Buffer.from(value, 'base64');
return value;
} catch (error) {
throw new Error('Invalid base64 format for binary data');
}
case 'text':
default:
return value;
}
};
// Helper function to create memory entry
const createMemoryEntry = (value: string, format: DataFormat): MemoryEntry => {
const now = new Date().toISOString();
return {
value: processValueForStorage(value, format),
meta: {
type: format,
created: now,
updated: now
}
};
};
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('memory', options)
const storagePath = getStoragePath(options);
return [
{
type: 'function',
function: {
name: 'memorize',
description: `Store information in memory as a key-value collection with format support. Supports text, JSON, and binary (base64) formats.
Returns: {
success: boolean,
message: string,
meta: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
}
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to organize related data (defaults to "no-collection" if not provided). Acts like a namespace.',
optional: true
},
key: {
type: 'string',
description: 'Unique identifier for the data within the collection. Must be a string.'
},
value: {
type: 'string',
description: 'The data to store. For format="text": any string. For format="json": valid JSON string. For format="binary": base64 encoded data.'
},
format: {
type: 'string',
description: 'Data format type. "text" for plain text (default), "json" for JSON data (validates structure), "binary" for base64 encoded binary data.',
enum: ['text', 'json', 'binary'],
optional: true
}
},
required: ['key', 'value']
},
function: async (params: any) => {
try {
const { collection = DEFAULT_COLLECTION, key, value, format = 'text' } = params;
logger.debug(`Tool::Memorize Storing ${key} in collection ${collection} as ${format}`);
const memoryEntry = createMemoryEntry(value, format as DataFormat);
await set(`${collection}:${key}`, memoryEntry, storagePath, collection);
return {
success: true,
message: `Stored ${key} in collection ${collection} as ${format}`,
meta: memoryEntry.meta
};
} catch (error) {
logger.error('Error storing memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'recall',
description: `Retrieve stored information from memory by collection and key, including format metadata.
Returns: {
success: boolean,
value?: string (the stored data),
meta?: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
},
key: string,
collection: string,
message?: string (error message if success=false)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to retrieve from (defaults to "no-collection" if not provided). Must match the collection used when storing.',
optional: true
},
key: {
type: 'string',
description: 'The unique identifier of the data to retrieve. Must match the key used when storing.'
}
},
required: ['key']
},
function: async (params: any) => {
try {
const { collection = DEFAULT_COLLECTION, key } = params;
logger.debug(`Tool::Recall Retrieving ${key} from collection ${collection}`);
const storedData = await get(`${collection}:${key}`, storagePath, collection);
if (storedData === undefined) {
return { success: false, message: `Key ${key} not found in collection ${collection}` };
}
// Handle both old format (plain string) and new format (MemoryEntry)
let memoryEntry: MemoryEntry;
if (typeof storedData === 'string') {
// Legacy format - convert to new format
memoryEntry = {
value: storedData,
meta: {
type: 'text',
created: new Date().toISOString(),
updated: new Date().toISOString()
}
};
} else {
memoryEntry = storedData as MemoryEntry;
}
return {
success: true,
value: memoryEntry.value,
meta: memoryEntry.meta,
key,
collection
};
} catch (error) {
logger.error('Error retrieving memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'forget',
description: `Remove a specific key from memory collection.
Returns: {
success: boolean,
message: string (confirmation or error message)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to remove from (defaults to "no-collection" if not provided). Must match the collection where the key was stored.',
optional: true
},
key: {
type: 'string',
description: 'The unique identifier of the data to remove. Must match exactly the key used when storing.'
}
},
required: ['key']
},
function: async (params: any) => {
try {
const { collection = DEFAULT_COLLECTION, key } = params;
logger.debug(`Tool::Forget Removing ${key} from collection ${collection}`);
const keyv = store(storagePath, collection);
const deleted = await keyv.delete(`${collection}:${key}`);
return { success: deleted, message: deleted ? `Removed ${key} from ${collection}` : `Key ${key} not found in ${collection}` };
} catch (error) {
logger.error('Error removing from memory', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'list_memories',
description: `List all keys in a specific collection using Keyv's iterator method.
Returns: {
success: boolean,
collection: string (the collection name),
keys: string[] (array of key names in the collection),
entries: Array<{
key: string,
meta?: {
type: "text" | "json" | "binary",
created: string (ISO timestamp),
updated: string (ISO timestamp)
}
}>,
count: number (total number of keys),
message?: string (info or error message)
}`,
parameters: {
type: 'object',
properties: {
collection: {
type: 'string',
description: 'Collection name to list keys from (defaults to "no-collection" if not provided). Will return all keys stored in this collection namespace.',
optional: true
}
},
required: []
},
function: async (params: any) => {
try {
const { collection = DEFAULT_COLLECTION } = params;
logger.debug(`Tool::ListMemories Listing keys in collection ${collection}`);
// Create a Keyv instance for the specific collection to use iterator
const keyv = store(storagePath, collection);
const keys: string[] = [];
const entries: { key: string; meta?: any }[] = [];
try {
// Check if iterator method exists and use it
if (typeof keyv.iterator === 'function') {
try {
// Try calling iterator without arguments first
const iterator = (keyv as any).iterator();
for await (const [key, value] of iterator) {
// Remove the collection prefix from the key to get the clean key name
const cleanKey = key.replace(`${collection}:`, '');
keys.push(cleanKey);
// Try to extract metadata if it's a MemoryEntry
let meta = undefined;
if (value && typeof value === 'object' && value.meta) {
meta = value.meta;
}
entries.push({ key: cleanKey, meta });
}
} catch (iteratorCallError) {
logger.warn(`Tool::ListMemories Iterator call failed:`, iteratorCallError);
// Fall through to the not available case
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator call failed. Unable to list keys.'
};
}
} else {
// Iterator not available, provide helpful message
logger.warn(`Tool::ListMemories Iterator method not available for Keyv instance`);
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator method not available in this Keyv version. Use individual key operations instead.'
};
}
return {
success: true,
collection,
keys,
entries,
count: keys.length
};
} catch (iteratorError) {
// If iterator fails, fall back to returning basic info
logger.warn(`Tool::ListMemories Iterator failed for collection ${collection}:`, iteratorError);
return {
success: true,
collection,
keys: [],
entries: [],
count: 0,
message: 'Iterator failed or collection is empty'
};
}
} catch (error) {
logger.error('Error listing memories', error);
return {
success: false,
message: error instanceof Error ? error.message : 'Unknown error occurred'
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
]
};

View File

@ -0,0 +1,151 @@
import path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { exec } from 'child_process'
import { promisify } from 'util'
import { logger } from '../../index.js'
import pMap from "p-map"
import { sync as exists } from '@polymech/fs/exists'
import { IKBotTask } from '../../types.js'
import { toolLogger } from '../../index.js'
const execAsync = promisify(exec)
const install = async (dependency: string, directory: string): Promise<any> => {
return new Promise((resolve, reject) => {
const command = `pnpm add ${dependency} --dir ${directory}`
exec(command, (error, stdout, stderr) => {
if (error) {
logger.error(`Error installing ${dependency}:`, error.message)
return resolve(false)
}
logger.info(`Successfully installed "${dependency}" in "${directory}".`)
})
})
}
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('npm', options)
return [
{
type: 'function',
function: {
name: 'build_project',
description: 'Build project using pnpm build command',
parameters: {
type: 'object',
properties: {},
required: []
},
function: async () => {
try {
logger.debug(`Tool::BuildProject Building project at ${target}`);
const { stdout, stderr } = await execAsync('pnpm build', {
cwd: target
});
return {
success: !stderr,
output: stdout,
error: stderr || null
};
} catch (error: any) {
logger.error('Error building project', error);
return {
success: false,
output: null,
error: error.message
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'run_npm',
description: 'Run an npm/pnpm command',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Command to run (e.g. install, test, etc)' },
args: {
type: 'array',
items: { type: 'string' },
description: 'Additional arguments for the command',
optional: true
}
},
required: ['command']
},
function: async (params: any) => {
try {
const args = params.args ? params.args.join(' ') : '';
const fullCommand = `pnpm ${params.command} ${args}`.trim();
logger.debug(`Tool::RunNpm Running command: ${fullCommand}`);
const { stdout, stderr } = await execAsync(fullCommand, {
cwd: target
});
return {
success: !stderr,
output: stdout,
error: stderr || null
};
} catch (error: any) {
logger.error('Error running npm command', error);
return {
success: false,
output: null,
error: error.message
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: "install_dependency",
description: "Install a dependency using npm",
parameters: {
type: "object",
properties: {
dependencies: {
type: "array",
items: {
type: "string"
}
}
},
required: ["dependencies"],
},
function: async (ret) => {
try {
const { dependencies } = ret as any
if (!target) {
logger.error(`Tool::NPM Target is required to install dependencies`)
return
}
if (!exists(target)) {
logger.error(`Project doesnt path exists ${target}`)
return
}
await pMap(dependencies, (async (dependency: string) => {
logger.info(`Installing dependency`, dependency)
try {
return install(dependency, target)
} catch (error) {
logger.error(`Error installing dependency ${dependency} `, error)
}
}), {
concurrency: 1
})
} catch (error) {
logger.error(`Error installing dependencies`, error)
}
},
parse: JSON.parse,
}
} as RunnableToolFunction<{ id: string }>
]
}

View File

@ -0,0 +1,130 @@
import { logger } from '../../index.js'
import * as stream from 'stream'
import { ChildProcess, spawn } from 'child_process'
export enum STATUS {
OK,
ERROR,
PENDING
}
const fatalHandler = (message: string, fn: (msg: string) => void): boolean => {
if (message.startsWith('fatal:')) {
fn('\t\ ' + message)
return true;
}
return false
}
const defaultFilter = (message: string): boolean => {
return message.length > 0 &&
message !== '\n' &&
message !== '\r' &&
message !== '\r\n' &&
!message.startsWith('Debugger attached') &&
!message.includes('NODE_TLS_REJECT_UNAUTHORIZED') &&
!message.includes('Waiting for the debugger to disconnect')
}
const subscribe = (signal: stream.Readable, collector: (data: any) => void = () => { }) => {
if(!signal || !signal.on) {
return
}
signal.on('message', (message) => logger.debug('message', message))
signal.on('error', (error) => logger.error('std-error', error))
signal.on('data', (data) => {
/*
const msg = data.toString().replace(ansiRegex(), "")
if (!defaultFilter(msg)) {
return
}
collector(msg)*/
process.stdout.write(data)
})
}
const merge = (buffer: string[], data: any): string[] => buffer.concat(data);
const hook = (child: ChildProcess, resolve: any, reject: any, cmd: string, buffer: string[] = []) => {
const collector = (data: any) => { buffer.push(data) }
//subscribe(child.stderr, collector)
//process.stdin.pipe(child.stdin)
debugger
child.on('exit', (code, signal) => {
debugger
if (code) {
resolve({
code: STATUS.ERROR,
command: cmd,
error: code,
messages: buffer
})
} else {
resolve({
code: STATUS.OK,
command: cmd,
messages: buffer
})
}
})
return child
}
export class Process {
public binary = ''
public cwd: string = ''
public args: string = ''
public buffer: string[] = []
constructor(options: any = {}) {
this.binary = options.binary || this.binary
this.cwd = options.cwd || process.cwd()
this.buffer = options.buffer || []
}
public async exec(command: string, args: string[] = []): Promise<any> {
args = [command].concat(args)
try {
let cmd = `${this.binary} ${args.join(' ')}`
/*
const p = new Promise<any>((resolve, reject) => {
const p = exec(cmd, {
cwd: this.cwd
})
return hook(p, resolve, reject, this.binary + ' ' + args.join(' '), this.buffer)
})
return p
*/
try {
//stdio: ['pipe', 'pipe', 'pipe'],
debugger
const p = new Promise<any>((resolve, reject) => {
const cp = spawn(cmd, args, {
cwd: this.cwd,
shell: true,
stdio:'inherit',
env: {
...process.env
},
})
return hook(cp, resolve, reject, cmd, this.buffer)
})
return p
} catch (e) {
logger.error('Error executing command', e)
}
} catch (e) {
logger.error('Error executing command', e)
}
}
}
export class Helper {
public static async run(cwd, cmd: string, args: string[], buffer: string[] = [], debug_stream: boolean = false): Promise<any> {
debug_stream && logger.info(`Run ${cmd} in ${cwd}`, args)
const gitProcess = new Process({
cwd,
binary: cmd,
buffer
})
return gitProcess.exec('', args)
}
}

View File

@ -0,0 +1,102 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { isArray } from '@polymech/core/primitives'
import { CONFIG_DEFAULT } from '@polymech/commons'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('search', options)
return [
{
type: 'function',
function: {
name: 'google',
description: 'Searches Google for the given query',
parameters: {
type: 'object',
properties: {
query: { type: 'string' }
},
required: ['query']
},
function: async (params: any) => {
const { query } = params
const config = CONFIG_DEFAULT() as any
let apiKey = config?.google?.api_key
let cse = config?.google?.cse
if (!config || !apiKey || !cse) {
logger.debug(
"Config not found in $HOME/.osr/config.json. " +
"Optionally, export OSR_CONFIG with the path to the configuration file " +
""
);
return undefined
}
const res = await fetch(
`https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${cse}&q=${encodeURIComponent(
query
)}`
)
const data = await res.json();
let results =
data.items?.map((item: { title?: string; link?: string; snippet?: string }) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
...item
})) ?? [];
return JSON.stringify(results)
},
parse: JSON.parse
}
} as RunnableToolFunction<any>,
{
type: 'function',
function: {
name: 'serpapi',
description: 'Searches Serpapi (finds locations (engine:google_local), places on the map (engine:google_maps) ) for the given query',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
engine: { type: 'string', default: 'google' },
},
required: ['query']
},
function: async (params: any) => {
const { query, engine } = params
const config = CONFIG_DEFAULT() as any
let apiKey = config?.serpapi?.key || config?.serpapi?.api_key
if (!config || !apiKey) {
logger.debug(
"Config not found in $HOME/.osr/config.json. " +
"Optionally, export OSR_CONFIG with the path to the configuration file " +
""
);
return undefined
}
const url = `https://serpapi.com/search?api_key=${apiKey}&engine=${engine || 'google'}&q=${encodeURIComponent(query)}&google_domain=google.com`
const res = await fetch(url)
logger.debug(`Searching ${url}`)
if (!res.ok) {
throw new Error(`HTTP error! status: ${res.status}`);
}
const data = await res.json()
let items = data.organic_results || data.local_results || data.place_results || data.places || data.maps_results || []
if (items && !isArray(items)) {
items = [items]
}
let results = items.map((item: any) => ({
title: item.title,
link: item.link,
snippet: item.snippet,
...item
})) ?? []
return JSON.stringify(results)
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
]
};

View File

@ -0,0 +1,119 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { spawn } from 'child_process'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
import { Helper } from './process.js'
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('terminal', options)
return [
{
type: 'function',
function: {
name: 'execute_command',
description: 'Execute a terminal command and capture output',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Command to execute'
},
args: {
type: 'array',
items: { type: 'string' },
description: 'Command arguments',
optional: true
},
cwd: {
type: 'string',
description: 'Working directory for command execution',
optional: true
},
background: {
type: 'boolean',
description: 'Run command in background (non-blocking)',
optional: true,
default: false
},
window: {
type: 'boolean',
description: 'Open command in new terminal window',
optional: true,
default: false
},
detached: {
type: 'boolean',
description: 'Run process detached from parent',
optional: true,
default: false
}
},
required: ['command']
},
function: async (params: any) => {
try {
debugger
const cwd = params.cwd ? path.join(target, params.cwd) : target;
const args = params.args || [];
logger.debug(`Tool::Terminal : ExecuteCommand Running '${params.command}' in ${cwd}`, params)
if (params.detached) {
const isWindows = process.platform === 'win32';
if (isWindows) {
spawn('cmd', ['/c', 'start', 'cmd', '/k', params.command, ...args], {
cwd: cwd,
detached: true,
stdio: 'ignore'
});
} else {
// For macOS/Linux
spawn('x-terminal-emulator', ['-e', `${params.command} ${args.join(' ')}`], {
cwd: cwd,
detached: true,
stdio: 'ignore'
});
}
return {
success: true,
output: 'Command launched in new window',
error: null
};
}
if (params.background || params.detached) {
const child = spawn(params.command, args, {
cwd: cwd,
detached: params.detached === true,
stdio: 'ignore'
});
if (params.detached) {
child.unref();
}
return {
success: true,
output: `Process started with PID: ${child.pid}`,
error: null
};
}
const cmd = `${params.command} ${args.join(' ')}`.trim();
logger.debug(`Tool::ExecuteCommand Running '${cmd}' in ${cwd}`);
const collector = []
const ret = await Helper.run(cwd, cmd, [], collector, true)
return ret
} catch (error: any) {
logger.error('Error executing command', error);
return {
success: false,
output: null,
error: error.message
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
]
}

View File

@ -0,0 +1,23 @@
import { tools as fsTools } from './fs.js'
import { tools as npmTools } from './npm.js'
import { tools as gitTools } from './git.js'
import { tools as terminalTools } from './terminal.js'
import { tools as interactTools } from './interact.js'
import { tools as userTools } from './user.js'
import { tools as search } from './search.js'
//import { tools as webTools } from './web.js'
import { tools as memoryTools } from './memory.js'
// import { tools as emailTools } from './email'
export const tools = {
fs: fsTools,
npm: npmTools,
git: gitTools,
terminal: terminalTools,
interact: interactTools,
user: userTools,
search: search,
// web: webTools,
memory: memoryTools
// email: emailTools
}

View File

@ -0,0 +1,83 @@
import { parse, join } from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import { sync as write } from '@polymech/fs/write'
import * as fs from 'fs'
import { lookup } from 'mime-types'
import { IKBotTask } from '../../types.js'
import { toolLogger } from '../../index.js'
export const mime = (file: string = '') => parse(file).ext ? lookup(file) : null
//const screenshot = require('screenshot-desktop')
export const fileToBase64 = (filePath: string): string | null => {
try {
const fileBuffer = fs.readFileSync(filePath)
const mimeType = lookup(filePath)
if (!mimeType) {
throw new Error('Unable to determine MIME type.')
}
const base64Data = fileBuffer.toString('base64')
return `data:${mimeType};base64,${base64Data}`
} catch (error) {
console.error('fileToBase64 : Error reading file:', error)
return null
}
}
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('user', options)
return [
{
type: 'function',
function: {
name: 'capture_screen',
description: 'Capture a screenshot and store it as file (jpg). Returns the path to the file',
parameters: {
type: 'object',
properties: {
file: { type: 'string' }
},
required: ['file']
},
function: async (params: any) => {
try {
const outputPath = join(target, params.file)
const takeScreenshot = async () : Promise<any> => {
/*
return new Promise((resolve, reject) => {
screenshot({ format: 'jpg' }).then((img) => {
write(outputPath, img)
resolve({ success: true, path: outputPath})
}).catch(reject)
})
*/
}
const { path } = await takeScreenshot()
return {
"role": "user",
"content":
[
/*
{
type: "image_url",
image_url: {
url: fileToBase64( path),
}
}
*/
]
}
} catch (error: any) {
logger.error('Error capturing screenshot:', error);
return {
success: false,
error: error.message
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
];
};

View File

@ -0,0 +1,112 @@
import * as path from 'path'
import { RunnableToolFunction } from 'openai/lib/RunnableFunction'
import puppeteer from 'puppeteer'
import TurndownService from 'turndown'
import { toolLogger } from '../../index.js'
import { IKBotTask } from '../../types.js'
const turndown = new TurndownService()
export const tools = (target: string, options: IKBotTask): Array<any> => {
const logger = toolLogger('web', options)
return [
{
type: 'function',
function: {
name: 'browse_page',
description: 'Browse a webpage and return its content as markdown, all links, images and pages main image',
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'URL of the webpage to browse'
}
},
required: ['url']
},
function: async (params: any) => {
try {
logger.debug(`Tool::BrowsePage Browsing ${params.url}`);
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
})
try {
const page = await browser.newPage()
logger.debug(`Tool::Web::BrowsePage Opening page ${params.url}`)
await page.goto(params.url, {
waitUntil: 'networkidle2'
})
const pageData = await page.evaluate((selector) => {
const elementsToRemove = document.querySelectorAll(
'script, style, link, meta, noscript, iframe, [style*="display:none"],[style*="display: none"], .hidden'
)
elementsToRemove.forEach(el => el.remove())
const links = Array.from(document.querySelectorAll('a'))
.map(a => ({
text: a.textContent?.trim() || '',
href: a.href
}))
.filter(link => link.href && link.href.startsWith('http'))
.slice(0, 20)
const images = Array.from(document.querySelectorAll('img'))
.map(img => ({
src: img.src,
alt: img.alt || '',
width: img.width,
height: img.height
}))
.filter(img => img.src && img.src.startsWith('http'))
.slice(0, 20)
const mainImage = document.querySelector('meta[property="og:image"]')?.getAttribute('content') ||
document.querySelector('meta[name="og:image"]')?.getAttribute('content')
let content
const body = document.body
content = body ? body.innerHTML : ''
return {
content,
links,
images,
ogImage: mainImage
}
}, null)
const markdown = turndown.turndown(pageData.content)
await browser.close()
const ret = {
success: true,
markdown: markdown,
links: pageData.links,
images: pageData.images,
mainImage: pageData.ogImage,
url: params.url
};
return ret
} catch (error: any) {
logger.debug('Error browsing page:', error.message, error);
await browser.close()
throw error
}
} catch (error: any) {
logger.debug('Error browsing page:', error.message);
return {
success: false,
error: error.message,
url: params.url
};
}
},
parse: JSON.parse
}
} as RunnableToolFunction<any>
]
}

View File

@ -0,0 +1,75 @@
import type { Argv } from 'yargs'
import { tools } from '../lib/tools/tools.js'
import { logger } from '../index.js'
import { ListCommandSchema } from '../zod_schemas.js'
import { sync as write } from '@polymech/fs/write'
import { toYargs } from '@polymech/commons/schemas'
export const options = (yargs: Argv) => toYargs(yargs, ListCommandSchema)
interface FSParameters {
type: string;
properties: Record<string, any>;
required: string[];
}
interface FSDefinition {
name: string;
description: string;
category: string;
parameters: FSParameters;
}
interface FSData {
fs: FSDefinition[];
}
export const signature = (definition: FSDefinition): string => {
const { properties } = definition.parameters;
const requiredKeys = definition.parameters.required || [];
const params = Object.entries(properties).map(([key, val]) => {
const isRequired = requiredKeys.includes(key);
const isOptional = !!val.optional || !isRequired;
return isOptional ? `?${key}` : key;
});
return `(${params.join(", ")})`;
}
export function format(category: string, data: any): string {
const lines: string[] = [`## ${category}\n`];
data.forEach(definition => {
const functionName = definition.name
const args = `${signature(definition)}`
const summary = definition.description
lines.push(`- ${functionName}${args}: ${summary}`)
})
return lines.join("\n")
}
export const list = async (argv: any, options?: any) => {
const getCategorizedTools = (category, options) => {
const toolsArray = tools[category](process.cwd(), options);
return toolsArray.map(tool => ({
name: tool.function.name,
description: tool.function.description,
category,
parameters: tool.function.parameters
}));
}
const toolsList = {
email: getCategorizedTools('email', options),
search: getCategorizedTools('search', options),
interact: getCategorizedTools('email', options),
fs: getCategorizedTools('fs', options),
npm: getCategorizedTools('npm', options),
git: getCategorizedTools('git', options),
terminal: getCategorizedTools('terminal', options)
}
//write(argv.output + '.json', Object.keys(toolsList).map((k,v)=>format(k,v as any)).join('\n') );
const shortDescription = Object.keys(toolsList).map((value:string) => {
return format(value,toolsList[value])
}).join('\n\n');
if (argv.output) {
write(argv.output, JSON.stringify(toolsList, null, 2))
write(argv.output + '.md', shortDescription)
}
}

View File

@ -0,0 +1,16 @@
#!/usr/bin/env node
import { commands } from './commands/index.js'
import { logger } from './index.js'
import cli from 'yargs'
import { hideBin } from 'yargs/helpers'
const yargs = cli(hideBin(process.argv))
async function main() {
try {
const argv = await commands(yargs).argv
} catch (error) {
logger.error('Error executing command:', error);
process.exit(1);
}
}
main()

View File

@ -0,0 +1,30 @@
import { ChatCompletion, ChatCompletionMessage, ChatCompletionMessageParam } from 'openai/resources'
import { IKBotOptions } from './types_kbot.js'
import OpenAI from 'openai'
import { Logger, ILogObj } from 'tslog'
import { RunnableFunctionWithParse } from 'openai/lib/RunnableFunction'
export type onToolBefore = (ctx: RunnableFunctionWithParse<any>,args: any) => Promise<any>
export type onToolAfter = (ctx: RunnableFunctionWithParse<any>, args: any, result?: any) => Promise<any>
export interface ICollector {
//OpenAI
onMessage: (message: ChatCompletionMessageParam) => void
onToolCall: (tool: ChatCompletionMessage.FunctionCall) => void,
onFunctionCallResult: (content: string) => void,
onChatCompletion: (completion: ChatCompletion) => void,
onContent: (content:string) => void,
// internal
onTool: (category: string, name: string, args: any, result?: any) => void
onToolBefore: onToolBefore
onToolAfter: onToolAfter
}
export interface IKBotTask extends IKBotOptions
{
client?: OpenAI
collector?: ICollector
onRun?:(ctx: IKBotTask) => Promise<IKBotTask>
logger?: Logger<ILogObj>
customTools?: any[]
}

View File

@ -0,0 +1,569 @@
export interface IKBotOptions {
/** Target directory */
path?: string;
/** The prompt. Supports file paths and environment variables. */
prompt?: string | undefined;
/** Optional output path for modified files (Tool mode only) */
output?: string | undefined;
/** Optional destination path for the result, will substitute ${MODEL_NAME} and ${ROUTER} in the path. Optional, used for "completion" mode */
dst?: string | undefined;
/** How to handle output if --dst file already exists: "concat" (append) or "merge" (try to merge structures if possible, otherwise append). Only used if --dst is specified. */
append?: ("concat" | "merge" | "replace") | undefined;
/** Specify how to wrap the output, "meta (file name, absolute path, cwd)" or "none". */
wrap?: "meta" | "none";
/** Iterate over items, supported: GLOB | Path to JSON File | array of strings (comma separated). To test different models, use --each="gpt-3.5-turbo,gpt-4o", the actual string will exposed as variable `ITEM`, eg: --dst="${ITEM}-output.md" */
each?: string | undefined;
/** Disable tools categories, eg: --disable=fs,git,interact,terminal,search,web,email,user */
disable?: string[];
/** List of specific tools to disable */
disableTools?: string[];
/** List of tools to use. Can be built-in tool names or paths to custom tool files. Default: fs,git,interact,terminal,search,web,email,user */
tools?: (string[] | string);
/** Comma separated glob patterns or paths, eg --include=src/*.tsx,src/*.ts --include=package.json */
include?: string[] | undefined;
/** Comma separated glob patterns or paths, eg --exclude=src/*.tsx,src/*.ts --exclude=package.json */
exclude?: string[] | undefined;
/** Specify a glob extension behavior. Available presets: match-cpp. Also accepts a custom glob pattern with variables like ${SRC_DIR}, ${SRC_NAME}, ${SRC_EXT}. E.g., "match-cpp" or "${SRC_DIR}/${SRC_NAME}*.cpp" */
globExtension?: (("match-cpp") | string) | undefined;
/** Explicit API key to use */
api_key?: string | undefined;
/** AI model to use for processing. Available models:

 OpenRouter models:

ai21/jamba-large-1.7 | paid
aion-labs/aion-1.0 | paid
aion-labs/aion-1.0-mini | paid
aion-labs/aion-2.0 | paid
aion-labs/aion-rp-llama-3.1-8b | paid
alfredpros/codellama-7b-instruct-solidity | paid
allenai/molmo-2-8b | paid
allenai/olmo-2-0325-32b-instruct | paid
allenai/olmo-3-32b-think | paid
allenai/olmo-3-7b-instruct | paid
allenai/olmo-3-7b-think | paid
allenai/olmo-3.1-32b-instruct | paid
allenai/olmo-3.1-32b-think | paid
amazon/nova-2-lite-v1 | paid
amazon/nova-lite-v1 | paid
amazon/nova-micro-v1 | paid
amazon/nova-premier-v1 | paid
amazon/nova-pro-v1 | paid
anthropic/claude-3-haiku | paid
anthropic/claude-3.5-haiku | paid
anthropic/claude-3.5-sonnet | paid
anthropic/claude-3.7-sonnet | paid
anthropic/claude-3.7-sonnet:thinking | paid
anthropic/claude-haiku-4.5 | paid
anthropic/claude-opus-4 | paid
anthropic/claude-opus-4.1 | paid
anthropic/claude-opus-4.5 | paid
anthropic/claude-opus-4.6 | paid
anthropic/claude-sonnet-4 | paid
anthropic/claude-sonnet-4.5 | paid
anthropic/claude-sonnet-4.6 | paid
arcee-ai/coder-large | paid
arcee-ai/maestro-reasoning | paid
arcee-ai/spotlight | paid
arcee-ai/trinity-large-preview:free | free
arcee-ai/trinity-mini | paid
arcee-ai/trinity-mini:free | free
arcee-ai/virtuoso-large | paid
openrouter/auto | paid
baidu/ernie-4.5-21b-a3b | paid
baidu/ernie-4.5-21b-a3b-thinking | paid
baidu/ernie-4.5-300b-a47b | paid
baidu/ernie-4.5-vl-28b-a3b | paid
baidu/ernie-4.5-vl-424b-a47b | paid
openrouter/bodybuilder | paid
bytedance-seed/seed-1.6 | paid
bytedance-seed/seed-1.6-flash | paid
bytedance-seed/seed-2.0-lite | paid
bytedance-seed/seed-2.0-mini | paid
bytedance/ui-tars-1.5-7b | paid
cohere/command-a | paid
cohere/command-r-08-2024 | paid
cohere/command-r-plus-08-2024 | paid
cohere/command-r7b-12-2024 | paid
deepcogito/cogito-v2.1-671b | paid
deepseek/deepseek-chat | paid
deepseek/deepseek-chat-v3-0324 | paid
deepseek/deepseek-chat-v3.1 | paid
deepseek/deepseek-v3.1-terminus | paid
deepseek/deepseek-v3.2 | paid
deepseek/deepseek-v3.2-exp | paid
deepseek/deepseek-v3.2-speciale | paid
deepseek/deepseek-r1 | paid
deepseek/deepseek-r1-0528 | paid
deepseek/deepseek-r1-distill-llama-70b | paid
deepseek/deepseek-r1-distill-qwen-32b | paid
eleutherai/llemma_7b | paid
essentialai/rnj-1-instruct | paid
openrouter/free | paid
alpindale/goliath-120b | paid
google/gemini-2.0-flash-001 | paid
google/gemini-2.0-flash-lite-001 | paid
google/gemini-2.5-flash | paid
google/gemini-2.5-flash-lite | paid
google/gemini-2.5-flash-lite-preview-09-2025 | paid
google/gemini-2.5-pro | paid
google/gemini-2.5-pro-preview-05-06 | paid
google/gemini-2.5-pro-preview | paid
google/gemini-3-flash-preview | paid
google/gemini-3-pro-preview | paid
google/gemini-3.1-flash-lite-preview | paid
google/gemini-3.1-pro-preview | paid
google/gemini-3.1-pro-preview-customtools | paid
google/gemma-2-27b-it | paid
google/gemma-2-9b-it | paid
google/gemma-3-12b-it | paid
google/gemma-3-12b-it:free | free
google/gemma-3-27b-it | paid
google/gemma-3-27b-it:free | free
google/gemma-3-4b-it | paid
google/gemma-3-4b-it:free | free
google/gemma-3n-e2b-it:free | free
google/gemma-3n-e4b-it | paid
google/gemma-3n-e4b-it:free | free
google/gemini-2.5-flash-image | paid
google/gemini-3.1-flash-image-preview | paid
google/gemini-3-pro-image-preview | paid
ibm-granite/granite-4.0-h-micro | paid
inception/mercury | paid
inception/mercury-2 | paid
inception/mercury-coder | paid
inflection/inflection-3-pi | paid
inflection/inflection-3-productivity | paid
kwaipilot/kat-coder-pro | paid
liquid/lfm-2.2-6b | paid
liquid/lfm-2-24b-a2b | paid
liquid/lfm2-8b-a1b | paid
liquid/lfm-2.5-1.2b-instruct:free | free
liquid/lfm-2.5-1.2b-thinking:free | free
meta-llama/llama-guard-3-8b | paid
anthracite-org/magnum-v4-72b | paid
mancer/weaver | paid
meituan/longcat-flash-chat | paid
meta-llama/llama-3-70b-instruct | paid
meta-llama/llama-3-8b-instruct | paid
meta-llama/llama-3.1-405b | paid
meta-llama/llama-3.1-70b-instruct | paid
meta-llama/llama-3.1-8b-instruct | paid
meta-llama/llama-3.2-11b-vision-instruct | paid
meta-llama/llama-3.2-1b-instruct | paid
meta-llama/llama-3.2-3b-instruct | paid
meta-llama/llama-3.2-3b-instruct:free | free
meta-llama/llama-3.3-70b-instruct | paid
meta-llama/llama-3.3-70b-instruct:free | free
meta-llama/llama-4-maverick | paid
meta-llama/llama-4-scout | paid
meta-llama/llama-guard-4-12b | paid
microsoft/phi-4 | paid
minimax/minimax-m1 | paid
minimax/minimax-m2 | paid
minimax/minimax-m2-her | paid
minimax/minimax-m2.1 | paid
minimax/minimax-m2.5 | paid
minimax/minimax-m2.5:free | free
minimax/minimax-m2.7 | paid
minimax/minimax-01 | paid
mistralai/mistral-large | paid
mistralai/mistral-large-2407 | paid
mistralai/mistral-large-2411 | paid
mistralai/codestral-2508 | paid
mistralai/devstral-2512 | paid
mistralai/devstral-medium | paid
mistralai/devstral-small | paid
mistralai/ministral-14b-2512 | paid
mistralai/ministral-3b-2512 | paid
mistralai/ministral-8b-2512 | paid
mistralai/mistral-7b-instruct-v0.1 | paid
mistralai/mistral-large-2512 | paid
mistralai/mistral-medium-3 | paid
mistralai/mistral-medium-3.1 | paid
mistralai/mistral-nemo | paid
mistralai/mistral-small-24b-instruct-2501 | paid
mistralai/mistral-small-3.1-24b-instruct | paid
mistralai/mistral-small-3.1-24b-instruct:free | free
mistralai/mistral-small-3.2-24b-instruct | paid
mistralai/mistral-small-2603 | paid
mistralai/mistral-small-creative | paid
mistralai/mixtral-8x22b-instruct | paid
mistralai/mixtral-8x7b-instruct | paid
mistralai/pixtral-large-2411 | paid
mistralai/mistral-saba | paid
mistralai/voxtral-small-24b-2507 | paid
moonshotai/kimi-k2 | paid
moonshotai/kimi-k2-0905 | paid
moonshotai/kimi-k2-thinking | paid
moonshotai/kimi-k2.5 | paid
morph/morph-v3-fast | paid
morph/morph-v3-large | paid
gryphe/mythomax-l2-13b | paid
nex-agi/deepseek-v3.1-nex-n1 | paid
nousresearch/hermes-3-llama-3.1-405b | paid
nousresearch/hermes-3-llama-3.1-405b:free | free
nousresearch/hermes-3-llama-3.1-70b | paid
nousresearch/hermes-4-405b | paid
nousresearch/hermes-4-70b | paid
nousresearch/hermes-2-pro-llama-3-8b | paid
nvidia/llama-3.1-nemotron-70b-instruct | paid
nvidia/llama-3.3-nemotron-super-49b-v1.5 | paid
nvidia/nemotron-3-nano-30b-a3b | paid
nvidia/nemotron-3-nano-30b-a3b:free | free
nvidia/nemotron-3-super-120b-a12b | paid
nvidia/nemotron-3-super-120b-a12b:free | free
nvidia/nemotron-nano-12b-v2-vl | paid
nvidia/nemotron-nano-12b-v2-vl:free | free
nvidia/nemotron-nano-9b-v2 | paid
nvidia/nemotron-nano-9b-v2:free | free
openai/gpt-audio | paid
openai/gpt-audio-mini | paid
openai/gpt-3.5-turbo | paid
openai/gpt-3.5-turbo-0613 | paid
openai/gpt-3.5-turbo-16k | paid
openai/gpt-3.5-turbo-instruct | paid
openai/gpt-4 | paid
openai/gpt-4-0314 | paid
openai/gpt-4-turbo | paid
openai/gpt-4-1106-preview | paid
openai/gpt-4-turbo-preview | paid
openai/gpt-4.1 | paid
openai/gpt-4.1-mini | paid
openai/gpt-4.1-nano | paid
openai/gpt-4o | paid
openai/gpt-4o-2024-05-13 | paid
openai/gpt-4o-2024-08-06 | paid
openai/gpt-4o-2024-11-20 | paid
openai/gpt-4o:extended | paid
openai/gpt-4o-audio-preview | paid
openai/gpt-4o-search-preview | paid
openai/gpt-4o-mini | paid
openai/gpt-4o-mini-2024-07-18 | paid
openai/gpt-4o-mini-search-preview | paid
openai/gpt-5 | paid
openai/gpt-5-chat | paid
openai/gpt-5-codex | paid
openai/gpt-5-image | paid
openai/gpt-5-image-mini | paid
openai/gpt-5-mini | paid
openai/gpt-5-nano | paid
openai/gpt-5-pro | paid
openai/gpt-5.1 | paid
openai/gpt-5.1-chat | paid
openai/gpt-5.1-codex | paid
openai/gpt-5.1-codex-max | paid
openai/gpt-5.1-codex-mini | paid
openai/gpt-5.2 | paid
openai/gpt-5.2-chat | paid
openai/gpt-5.2-pro | paid
openai/gpt-5.2-codex | paid
openai/gpt-5.3-chat | paid
openai/gpt-5.3-codex | paid
openai/gpt-5.4 | paid
openai/gpt-5.4-mini | paid
openai/gpt-5.4-nano | paid
openai/gpt-5.4-pro | paid
openai/gpt-oss-120b | paid
openai/gpt-oss-120b:free | free
openai/gpt-oss-20b | paid
openai/gpt-oss-20b:free | free
openai/gpt-oss-safeguard-20b | paid
openai/o1 | paid
openai/o1-pro | paid
openai/o3 | paid
openai/o3-deep-research | paid
openai/o3-mini | paid
openai/o3-mini-high | paid
openai/o3-pro | paid
openai/o4-mini | paid
openai/o4-mini-deep-research | paid
openai/o4-mini-high | paid
perplexity/sonar | paid
perplexity/sonar-deep-research | paid
perplexity/sonar-pro | paid
perplexity/sonar-pro-search | paid
perplexity/sonar-reasoning-pro | paid
prime-intellect/intellect-3 | paid
qwen/qwen-plus-2025-07-28 | paid
qwen/qwen-plus-2025-07-28:thinking | paid
qwen/qwen-vl-max | paid
qwen/qwen-vl-plus | paid
qwen/qwen-max | paid
qwen/qwen-plus | paid
qwen/qwen-turbo | paid
qwen/qwen-2.5-7b-instruct | paid
qwen/qwen2.5-coder-7b-instruct | paid
qwen/qwen2.5-vl-32b-instruct | paid
qwen/qwen2.5-vl-72b-instruct | paid
qwen/qwen-2.5-vl-7b-instruct | paid
qwen/qwen3-14b | paid
qwen/qwen3-235b-a22b | paid
qwen/qwen3-235b-a22b-2507 | paid
qwen/qwen3-235b-a22b-thinking-2507 | paid
qwen/qwen3-30b-a3b | paid
qwen/qwen3-30b-a3b-instruct-2507 | paid
qwen/qwen3-30b-a3b-thinking-2507 | paid
qwen/qwen3-32b | paid
qwen/qwen3-4b:free | free
qwen/qwen3-8b | paid
qwen/qwen3-coder-30b-a3b-instruct | paid
qwen/qwen3-coder | paid
qwen/qwen3-coder:free | free
qwen/qwen3-coder-flash | paid
qwen/qwen3-coder-next | paid
qwen/qwen3-coder-plus | paid
qwen/qwen3-max | paid
qwen/qwen3-max-thinking | paid
qwen/qwen3-next-80b-a3b-instruct | paid
qwen/qwen3-next-80b-a3b-instruct:free | free
qwen/qwen3-next-80b-a3b-thinking | paid
qwen/qwen3-vl-235b-a22b-instruct | paid
qwen/qwen3-vl-235b-a22b-thinking | paid
qwen/qwen3-vl-30b-a3b-instruct | paid
qwen/qwen3-vl-30b-a3b-thinking | paid
qwen/qwen3-vl-32b-instruct | paid
qwen/qwen3-vl-8b-instruct | paid
qwen/qwen3-vl-8b-thinking | paid
qwen/qwen3.5-397b-a17b | paid
qwen/qwen3.5-plus-02-15 | paid
qwen/qwen3.5-122b-a10b | paid
qwen/qwen3.5-27b | paid
qwen/qwen3.5-35b-a3b | paid
qwen/qwen3.5-9b | paid
qwen/qwen3.5-flash-02-23 | paid
qwen/qwq-32b | paid
qwen/qwen-2.5-72b-instruct | paid
qwen/qwen-2.5-coder-32b-instruct | paid
relace/relace-apply-3 | paid
relace/relace-search | paid
undi95/remm-slerp-l2-13b | paid
sao10k/l3-lunaris-8b | paid
sao10k/l3-euryale-70b | paid
sao10k/l3.1-70b-hanami-x1 | paid
sao10k/l3.1-euryale-70b | paid
sao10k/l3.3-euryale-70b | paid
stepfun/step-3.5-flash | paid
stepfun/step-3.5-flash:free | free
switchpoint/router | paid
tencent/hunyuan-a13b-instruct | paid
thedrummer/cydonia-24b-v4.1 | paid
thedrummer/rocinante-12b | paid
thedrummer/skyfall-36b-v2 | paid
thedrummer/unslopnemo-12b | paid
tngtech/deepseek-r1t2-chimera | paid
alibaba/tongyi-deepresearch-30b-a3b | paid
upstage/solar-pro-3 | paid
cognitivecomputations/dolphin-mistral-24b-venice-edition:free | free
microsoft/wizardlm-2-8x22b | paid
writer/palmyra-x5 | paid
x-ai/grok-3 | paid
x-ai/grok-3-beta | paid
x-ai/grok-3-mini | paid
x-ai/grok-3-mini-beta | paid
x-ai/grok-4 | paid
x-ai/grok-4-fast | paid
x-ai/grok-4.1-fast | paid
x-ai/grok-4.20-beta | paid
x-ai/grok-4.20-multi-agent-beta | paid
x-ai/grok-code-fast-1 | paid
xiaomi/mimo-v2-flash | paid
xiaomi/mimo-v2-omni | paid
xiaomi/mimo-v2-pro | paid
z-ai/glm-4-32b | paid
z-ai/glm-4.5 | paid
z-ai/glm-4.5-air | paid
z-ai/glm-4.5-air:free | free
z-ai/glm-4.5v | paid
z-ai/glm-4.6 | paid
z-ai/glm-4.6v | paid
z-ai/glm-4.7 | paid
z-ai/glm-4.7-flash | paid
z-ai/glm-5 | paid
z-ai/glm-5-turbo | paid

 OpenAI models:

babbage-002
chatgpt-image-latest
dall-e-2
dall-e-3
davinci-002
gpt-3.5-turbo
gpt-3.5-turbo-0125
gpt-3.5-turbo-1106
gpt-3.5-turbo-16k
gpt-3.5-turbo-instruct
gpt-3.5-turbo-instruct-0914
gpt-4
gpt-4-0125-preview
gpt-4-0613
gpt-4-1106-preview
gpt-4-turbo
gpt-4-turbo-2024-04-09
gpt-4-turbo-preview
gpt-4.1
gpt-4.1-2025-04-14
gpt-4.1-mini
gpt-4.1-mini-2025-04-14
gpt-4.1-nano
gpt-4.1-nano-2025-04-14
gpt-4o
gpt-4o-2024-05-13
gpt-4o-2024-08-06
gpt-4o-2024-11-20
gpt-4o-audio-preview
gpt-4o-audio-preview-2024-12-17
gpt-4o-audio-preview-2025-06-03
gpt-4o-mini
gpt-4o-mini-2024-07-18
gpt-4o-mini-audio-preview
gpt-4o-mini-audio-preview-2024-12-17
gpt-4o-mini-realtime-preview
gpt-4o-mini-realtime-preview-2024-12-17
gpt-4o-mini-search-preview
gpt-4o-mini-search-preview-2025-03-11
gpt-4o-mini-transcribe
gpt-4o-mini-transcribe-2025-03-20
gpt-4o-mini-transcribe-2025-12-15
gpt-4o-mini-tts
gpt-4o-mini-tts-2025-03-20
gpt-4o-mini-tts-2025-12-15
gpt-4o-realtime-preview
gpt-4o-realtime-preview-2024-12-17
gpt-4o-realtime-preview-2025-06-03
gpt-4o-search-preview
gpt-4o-search-preview-2025-03-11
gpt-4o-transcribe
gpt-4o-transcribe-diarize
gpt-5
gpt-5-2025-08-07
gpt-5-chat-latest
gpt-5-codex
gpt-5-mini
gpt-5-mini-2025-08-07
gpt-5-nano
gpt-5-nano-2025-08-07
gpt-5-pro
gpt-5-pro-2025-10-06
gpt-5-search-api
gpt-5-search-api-2025-10-14
gpt-5.1
gpt-5.1-2025-11-13
gpt-5.1-chat-latest
gpt-5.1-codex
gpt-5.1-codex-max
gpt-5.1-codex-mini
gpt-5.2
gpt-5.2-2025-12-11
gpt-5.2-chat-latest
gpt-5.2-codex
gpt-5.2-pro
gpt-5.2-pro-2025-12-11
gpt-5.3-chat-latest
gpt-5.3-codex
gpt-5.4
gpt-5.4-2026-03-05
gpt-5.4-mini
gpt-5.4-mini-2026-03-17
gpt-5.4-nano
gpt-5.4-nano-2026-03-17
gpt-5.4-pro
gpt-5.4-pro-2026-03-05
gpt-audio
gpt-audio-1.5
gpt-audio-2025-08-28
gpt-audio-mini
gpt-audio-mini-2025-10-06
gpt-audio-mini-2025-12-15
gpt-image-1
gpt-image-1-mini
gpt-image-1.5
gpt-realtime
gpt-realtime-1.5
gpt-realtime-2025-08-28
gpt-realtime-mini
gpt-realtime-mini-2025-10-06
gpt-realtime-mini-2025-12-15
o1
o1-2024-12-17
o1-pro
o1-pro-2025-03-19
o3
o3-2025-04-16
o3-mini
o3-mini-2025-01-31
o4-mini
o4-mini-2025-04-16
o4-mini-deep-research
o4-mini-deep-research-2025-06-26
omni-moderation-2024-09-26
omni-moderation-latest
sora-2
sora-2-pro
text-embedding-3-large
text-embedding-3-small
text-embedding-ada-002
tts-1
tts-1-1106
tts-1-hd
tts-1-hd-1106
whisper-1
-----

 Deepseek models:

deepseek-chat
deepseek-reasoner
-----
*/
model?: string | undefined;
/** Router to use: openai, openrouter or deepseek */
router?: string;
/** Chat completion mode:
completion, tools, assistant.
completion: no support for tools, please use --dst parameter to save the output.
tools: allows for tools to be used, eg 'save to ./output.md'. Not all models support this mode.
responses: allows for responses to be used, eg 'save to ./output.md'. Not all models support this mode.
assistant: : allows documents (PDF, DOCX, ...) to be added but dont support tools. Use --dst to save the output. Supported files :
custom: custom mode
*/
mode?: "completion" | "tools" | "assistant" | "responses" | "custom";
/** Logging level for the application */
logLevel?: number;
/** Path to profile for variables. Supports environment variables. */
profile?: string | undefined;
/** Base URL for the API, set via --router or directly */
baseURL?: string | undefined;
/** Path to JSON configuration file (API keys). Supports environment variables. */
config?: string | undefined;
/** Create a script */
dump?: string | undefined;
/** Path to preferences file, eg: location, your email address, gender, etc. Supports environment variables. */
preferences?: string;
/** Logging directory */
logs?: string;
/** Enable streaming (verbose LLM output) */
stream?: boolean;
/** Use alternate tokenizer & instead of $ */
alt?: boolean;
/** Environment (in profile) */
env?: string;
variables?: {
[x: string]: string;
};
/** List of filters to apply to the output.
Used only in completion mode and a given output file specified with --dst.
It unwraps by default any code or data in Markdown.
Choices:
JSON,JSONUnescape,JSONPretty,AlphaSort,code,JSONParse,trim,markdown
*/
filters?: (string | ("JSON" | "JSONUnescape" | "JSONPretty" | "AlphaSort" | "code" | "JSONParse" | "trim" | "markdown")[] | string[] | ((...args_0: unknown[]) => unknown)[]);
/** JSONPath query to be used to transform input objects */
query?: (string | null);
/** Dry run - only write out parameters without making API calls */
dry?: (boolean | string);
/** Format for structured outputs. Can be a Zod schema, a Zod schema string, a JSON schema string, or a path to a JSON file. */
format?: (string | any) | undefined;
}

View File

@ -0,0 +1,75 @@
import { z } from 'zod';
/** Schema for listing files in a directory */
export const FileListingOptionsSchema = z.object({
directory: z.string().describe('Directory path to list files from'),
pattern: z.string().optional().describe('Glob pattern for filtering files')
}).describe('IFileListingOptions')
/** Schema for file removal operations */
export const FileRemovalOptionsSchema = z.object({
path: z.string().describe('Path of the file to remove')
}).describe('IFileRemovalOptions');
/** Schema for git commit operations */
export const GitCommitSchema = z.object({
files: z.array(z.string()).describe('Files to commit'),
message: z.string().describe('Commit message')
}).describe('IGitCommitOptions');
/** Schema for git revert operations */
export const GitRevertSchema = z.object({
files: z.array(z.string()).describe('Files to revert')
}).describe('IGitRevertOptions');
/** Schema for git version switch operations */
export const GitSwitchVersionSchema = z.object({
branch: z.string().describe('Branch name to switch to'),
remote: z.string().default('origin').describe('Remote name')
}).describe('IGitSwitchVersionOptions');
/** Schema for git raw file retrieval */
export const GitRawFileSchema = z.object({
url: z.string().optional().describe('Full GitHub raw URL'),
repo: z.string().optional().describe('Repository in format owner/repo'),
path: z.string().optional().describe('File path within repository')
}).refine(
data => (data.url) || (data.repo && data.path),
'Either url or both repo and path must be provided'
).describe('IGitRawFileOptions');
/** Schema for npm run command */
export const NpmRunSchema = z.object({
command: z.string().describe('Command to run (e.g. install, test, etc)'),
args: z.array(z.string()).optional().describe('Additional arguments for the command')
}).describe('INpmRunOptions');
/** Schema for terminal command execution */
export const TerminalCommandSchema = z.object({
command: z.string().describe('Command to execute'),
args: z.array(z.string()).optional().describe('Command arguments'),
cwd: z.string().optional().describe('Working directory for command execution'),
background: z.boolean().optional().describe('Run command in background (non-blocking)'),
window: z.boolean().optional().describe('Open command in new terminal window'),
detached: z.boolean().optional().describe('Run process detached from parent')
}).describe('ITerminalCommandOptions');
/** Schema for tool invocation parameters */
export const InvokeToolSchema = z.object({
tools: z.string().describe('Tool category to use (fs, npm, git, terminal)'),
function: z.string().describe('Function name to invoke'),
target: z.string().default(process.cwd()).describe('Target directory'),
params: z.string().optional().describe('JSON string of parameters'),
output: z.string().optional().describe('Path to write the output to'),
env_key: z.string().optional().describe('Environment configuration key')
}).describe('IInvokeToolOptions');
/** Schema for list command options */
export const ListCommandSchema = z.object({
output: z.string().default("./llm-tools.json").describe('Output file path for tools list')
}).describe('IListCommandOptions');
/** Schema for tool listing options */
export const ToolListingOptionsSchema = z.object({
output: z.string().default('./llm-tools.json').describe('Path to write the output to')
}).describe('IToolListingOptions');

View File

@ -0,0 +1,68 @@
export interface IFileListingOptions {
/** Directory path to list files from */
directory: string;
/** Glob pattern for filtering files */
pattern?: string | undefined;
}
export interface IFileRemovalOptions {
/** Path of the file to remove */
path: string;
}
export interface IGitCommitOptions {
/** Files to commit */
files: string[];
/** Commit message */
message: string;
}
export interface IGitRevertOptions {
/** Files to revert */
files: string[];
}
export interface IGitSwitchVersionOptions {
/** Branch name to switch to */
branch: string;
/** Remote name */
remote: string;
}
export interface IInvokeToolOptions {
/** Tool category to use (fs, npm, git, terminal) */
tools: string;
/** Function name to invoke */
function: string;
/** Target directory */
target: string;
/** JSON string of parameters */
params?: string | undefined;
/** Path to write the output to */
output?: string | undefined;
/** Environment configuration key */
env_key?: string | undefined;
}
export interface IToolListingOptions {
/** Path to write the output to */
output: string;
}
export interface ITerminalCommandOptions {
/** Command to execute */
command: string;
/** Command arguments */
args?: string[] | undefined;
/** Working directory for command execution */
cwd?: string | undefined;
/** Run command in background (non-blocking) */
background?: boolean | undefined;
/** Open command in new terminal window */
window?: boolean | undefined;
/** Run process detached from parent */
detached?: boolean | undefined;
}
export interface IListCommandOptions {
/** Output file path for tools list */
output: string;
}
export interface INpmRunOptions {
/** Command to run (e.g. install, test, etc) */
command: string;
/** Additional arguments for the command */
args?: string[] | undefined;
}

View File

@ -0,0 +1,251 @@
import { describe, it, expect } from 'vitest'
import { sync as exists } from "@polymech/fs/exists"
import { z } from 'zod'
import {
TEST_TIMEOUT,
TestResult,
runTest,
generateTestReport,
getReportPaths
} from './commons'
import { zodFunction } from '../../src/ai-tools/lib/tools/index.js'
const models = ['qwen2.5:3b']
// ---------------------------------------------------------------------------
// Mock tool implementations
// ---------------------------------------------------------------------------
const addTool = zodFunction({
name: 'add',
description: 'Add two numbers together and return the sum.',
schema: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
}),
function: async ({ a, b }) => ({ result: a + b }),
})
const multiplyTool = zodFunction({
name: 'multiply',
description: 'Multiply two numbers and return the product.',
schema: z.object({
a: z.number().describe('First number'),
b: z.number().describe('Second number'),
}),
function: async ({ a, b }) => ({ result: a * b }),
})
const getWeatherTool = zodFunction({
name: 'get_weather',
description: 'Get the current weather for a city. Returns temperature in Celsius and a condition string.',
schema: z.object({
city: z.string().describe('The city name to get weather for'),
}),
function: async ({ city }) => ({
city,
temperature_c: 22,
condition: 'sunny',
}),
})
const formatNumberTool = zodFunction({
name: 'format_number',
description: 'Format a number with thousand separators and optional decimal places.',
schema: z.object({
value: z.number().describe('The number to format'),
decimals: z.number().optional().describe('Number of decimal places (default 2)'),
}),
function: async ({ value, decimals = 2 }) => ({
formatted: value.toLocaleString('en-US', { minimumFractionDigits: decimals, maximumFractionDigits: decimals }),
}),
})
// ---------------------------------------------------------------------------
// Basic Ollama Operations
// ---------------------------------------------------------------------------
describe('Ollama Basic Operations', () => {
let testResults: TestResult[] = []
const TEST_LOG_PATH = getReportPaths('ollama-basics', 'json')
const TEST_REPORT_PATH = getReportPaths('ollama-basics', 'md')
it.each(models)('should add two numbers with model %s', async (modelName) => {
const result = await runTest(
'add 5 and 3. Return only the number, no explanation.',
'8',
'addition',
modelName,
TEST_LOG_PATH,
'completion',
{ router: 'ollama' }
)
testResults.push(result)
expect(result.result[0]?.trim()?.toLowerCase()).toEqual('8')
}, { timeout: TEST_TIMEOUT })
it.each(models)('should multiply two numbers with model %s', async (modelName) => {
const result = await runTest(
'multiply 8 and 3. Return only the number, no explanation.',
'24',
'multiplication',
modelName,
TEST_LOG_PATH,
'completion',
{ router: 'ollama' }
)
testResults.push(result)
expect(result.result[0]?.trim()?.toLowerCase()).toEqual('24')
}, { timeout: TEST_TIMEOUT })
it.each(models)('should divide two numbers with model %s', async (modelName) => {
const result = await runTest(
'divide 15 by 3. Return only the number, no explanation.',
'5',
'division',
modelName,
TEST_LOG_PATH,
'completion',
{ router: 'ollama' }
)
testResults.push(result)
expect(result.result[0]?.trim()?.toLowerCase()).toEqual('5')
}, { timeout: TEST_TIMEOUT })
it('should generate markdown report', () => {
generateTestReport(testResults, 'Ollama Basic Operations Test Results', TEST_REPORT_PATH)
expect(exists(TEST_REPORT_PATH) === 'file').toBe(true)
})
})
// ---------------------------------------------------------------------------
// Custom Tool Call Quality
// ---------------------------------------------------------------------------
describe('Ollama Custom Tool Call Quality', () => {
let testResults: TestResult[] = []
const TEST_LOG_PATH = getReportPaths('ollama-tools', 'json')
const TEST_REPORT_PATH = getReportPaths('ollama-tools', 'md')
it.each(models)(
'should call add tool and return correct sum [%s]',
async (modelName) => {
const result = await runTest(
'Use the add tool to add 17 and 25. Report back the result.',
'42',
'tool-add',
modelName,
TEST_LOG_PATH,
'tools',
{
router: 'ollama',
customTools: [addTool],
equalityCheck: 'llm_equal',
}
)
testResults.push(result)
// Result must contain 42
expect(result.result[0]).toMatch(/42/)
},
{ timeout: TEST_TIMEOUT }
)
it.each(models)(
'should call multiply tool and return correct product [%s]',
async (modelName) => {
const result = await runTest(
'Use the multiply tool to compute 6 times 7. Tell me the answer.',
'42',
'tool-multiply',
modelName,
TEST_LOG_PATH,
'tools',
{
router: 'ollama',
customTools: [multiplyTool],
equalityCheck: 'llm_equal',
}
)
testResults.push(result)
expect(result.result[0]).toMatch(/42/)
},
{ timeout: TEST_TIMEOUT }
)
it.each(models)(
'should call get_weather tool with correct city argument [%s]',
async (modelName) => {
const result = await runTest(
"What's the weather like in Paris? Use the get_weather tool.",
'sunny',
'tool-weather',
modelName,
TEST_LOG_PATH,
'tools',
{
router: 'ollama',
customTools: [getWeatherTool],
equalityCheck: 'llm_equal',
}
)
testResults.push(result)
// Response must mention the mocked condition "sunny" and/or 22°C
const lower = result.result[0]?.toLowerCase() ?? ''
expect(lower).toMatch(/sunny|22/)
},
{ timeout: TEST_TIMEOUT }
)
it.each(models)(
'should select the correct tool from multiple available tools [%s]',
async (modelName) => {
const result = await runTest(
'Use the appropriate tool to add 100 and 200.',
'300',
'tool-selection',
modelName,
TEST_LOG_PATH,
'tools',
{
router: 'ollama',
// Both tools available — model must pick add, not multiply
customTools: [addTool, multiplyTool, getWeatherTool],
equalityCheck: 'llm_equal',
}
)
testResults.push(result)
expect(result.result[0]).toMatch(/300/)
},
{ timeout: TEST_TIMEOUT }
)
it.each(models)(
'should chain two tool calls: multiply then format [%s]',
async (modelName) => {
const result = await runTest(
'First multiply 123 by 456, then format the result with 2 decimal places.',
'56,088.00',
'tool-chain',
modelName,
TEST_LOG_PATH,
'tools',
{
router: 'ollama',
customTools: [multiplyTool, formatNumberTool],
equalityCheck: 'llm_equal',
}
)
testResults.push(result)
// 123 * 456 = 56088 → formatted as 56,088.00
expect(result.result[0]).toMatch(/56[,.]?088/)
},
{ timeout: TEST_TIMEOUT }
)
it('should generate tool quality markdown report', () => {
generateTestReport(testResults, 'Ollama Custom Tool Call Quality Results', TEST_REPORT_PATH)
expect(exists(TEST_REPORT_PATH) === 'file').toBe(true)
})
})

View File

@ -0,0 +1,79 @@
{
"results": [
{
"test": "addition",
"prompt": "add 5 and 3. Return only the number, no explanation.",
"result": [
"8"
],
"expected": "8",
"model": "qwen2.5:3b",
"router": "qwen2.5:3b",
"timestamp": "2026-03-19T15:42:19.097Z",
"passed": true,
"duration": 738,
"category": "ollama-basics"
},
{
"test": "multiplication",
"prompt": "multiply 8 and 3. Return only the number, no explanation.",
"result": [
"24"
],
"expected": "24",
"model": "qwen2.5:3b",
"router": "qwen2.5:3b",
"timestamp": "2026-03-19T15:42:19.848Z",
"passed": true,
"duration": 745,
"category": "ollama-basics"
},
{
"test": "division",
"prompt": "divide 15 by 3. Return only the number, no explanation.",
"result": [
"5"
],
"expected": "5",
"model": "qwen2.5:3b",
"router": "qwen2.5:3b",
"timestamp": "2026-03-19T15:42:20.529Z",
"passed": true,
"duration": 677,
"category": "ollama-basics"
}
],
"highscores": [
{
"test": "addition",
"rankings": [
{
"model": "qwen2.5:3b",
"duration": 738,
"duration_secs": 0.738
}
]
},
{
"test": "multiplication",
"rankings": [
{
"model": "qwen2.5:3b",
"duration": 745,
"duration_secs": 0.745
}
]
},
{
"test": "division",
"rankings": [
{
"model": "qwen2.5:3b",
"duration": 677,
"duration_secs": 0.677
}
]
}
],
"lastUpdated": "2026-03-19T15:42:20.529Z"
}

View File

@ -0,0 +1,50 @@
# Ollama Basic Operations Test Results
## Highscores
### Performance Rankings (Duration)
| Test | Model | Duration (ms) | Duration (s) |
|------|-------|--------------|--------------|
| addition | qwen2.5:3b | 738 | 0.74 |
| multiplication | qwen2.5:3b | 745 | 0.74 |
| division | qwen2.5:3b | 677 | 0.68 |
## Summary
- Total Tests: 3
- Passed: 3
- Failed: 0
- Success Rate: 100.00%
- Average Duration: 720ms (0.72s)
## Failed Tests
*No failed tests*
## Passed Tests
### addition - qwen2.5:3b
- Prompt: `add 5 and 3. Return only the number, no explanation.`
- Expected: `8`
- Actual: `8`
- Duration: 738ms (0.74s)
- Timestamp: 3/19/2026, 4:42:19 PM
### multiplication - qwen2.5:3b
- Prompt: `multiply 8 and 3. Return only the number, no explanation.`
- Expected: `24`
- Actual: `24`
- Duration: 745ms (0.74s)
- Timestamp: 3/19/2026, 4:42:19 PM
### division - qwen2.5:3b
- Prompt: `divide 15 by 3. Return only the number, no explanation.`
- Expected: `5`
- Actual: `5`
- Duration: 677ms (0.68s)
- Timestamp: 3/19/2026, 4:42:20 PM

1386
packages/tm/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

16
packages/tm/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "tm",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@plastichub/kbot": "^1.1.23"
}
}