@saccolabs/tars 1.30.0 → 1.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/context/skills/create-extension/SKILL.md +2 -2
- package/context/skills/manage-extensions/SKILL.md +3 -3
- package/dist/channels/discord/discord-channel.js +2 -3
- package/dist/channels/discord/discord-channel.js.map +1 -1
- package/dist/channels/discord/message-formatter.d.ts +1 -1
- package/dist/channels/discord/message-formatter.js +1 -1
- package/dist/cli/commands/quota.js +13 -48
- package/dist/cli/commands/quota.js.map +1 -1
- package/dist/cli/commands/refresh.js +61 -9
- package/dist/cli/commands/refresh.js.map +1 -1
- package/dist/cli/commands/setup.js +192 -467
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/index.js +1 -13
- package/dist/cli/index.js.map +1 -1
- package/dist/config/config.d.ts +5 -10
- package/dist/config/config.js +21 -27
- package/dist/config/config.js.map +1 -1
- package/dist/memory/knowledge-store.js +10 -1
- package/dist/memory/knowledge-store.js.map +1 -1
- package/dist/memory/memory-manager.d.ts +0 -3
- package/dist/memory/memory-manager.js +26 -34
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/scripts/debug-cli.js +2 -2
- package/dist/scripts/debug-cli.js.map +1 -1
- package/dist/supervisor/heartbeat-service.js +1 -1
- package/dist/supervisor/heartbeat-service.js.map +1 -1
- package/dist/supervisor/main.js +37 -79
- package/dist/supervisor/main.js.map +1 -1
- package/dist/supervisor/mcp-bridge.d.ts +25 -0
- package/dist/supervisor/mcp-bridge.js +157 -0
- package/dist/supervisor/mcp-bridge.js.map +1 -0
- package/dist/supervisor/session-manager.d.ts +1 -1
- package/dist/supervisor/session-manager.js +1 -1
- package/dist/supervisor/supervisor.d.ts +14 -7
- package/dist/supervisor/supervisor.js +87 -29
- package/dist/supervisor/supervisor.js.map +1 -1
- package/dist/supervisor/{gemini-engine.d.ts → tars-engine.d.ts} +39 -30
- package/dist/supervisor/tars-engine.js +698 -0
- package/dist/supervisor/tars-engine.js.map +1 -0
- package/dist/tools/get-quota.d.ts +38 -12
- package/dist/tools/get-quota.js +37 -94
- package/dist/tools/get-quota.js.map +1 -1
- package/dist/tools/send-notification.d.ts +32 -7
- package/dist/tools/send-notification.js +31 -37
- package/dist/tools/send-notification.js.map +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/utils/brain-audit.js +4 -4
- package/dist/utils/brain-audit.js.map +1 -1
- package/dist/utils/migration-manager.d.ts +4 -0
- package/dist/utils/migration-manager.js +205 -0
- package/dist/utils/migration-manager.js.map +1 -0
- package/extensions/memory/dist/store.js +29 -20
- package/extensions/memory/dist/store.js.map +1 -1
- package/extensions/memory/src/store.ts +33 -23
- package/package.json +4 -3
- package/src/prompts/system.md +3 -14
- package/context/agents/scaffolder.md +0 -22
- package/dist/auth/credential-manager.d.ts +0 -14
- package/dist/auth/credential-manager.js +0 -60
- package/dist/auth/credential-manager.js.map +0 -1
- package/dist/auth/oauth-service.d.ts +0 -24
- package/dist/auth/oauth-service.js +0 -89
- package/dist/auth/oauth-service.js.map +0 -1
- package/dist/auth/workspace-auth-service.d.ts +0 -10
- package/dist/auth/workspace-auth-service.js +0 -78
- package/dist/auth/workspace-auth-service.js.map +0 -1
- package/dist/cli/commands/swarm.d.ts +0 -13
- package/dist/cli/commands/swarm.js +0 -250
- package/dist/cli/commands/swarm.js.map +0 -1
- package/dist/inference/LlamaCppGenerator.d.ts +0 -25
- package/dist/inference/LlamaCppGenerator.js +0 -461
- package/dist/inference/LlamaCppGenerator.js.map +0 -1
- package/dist/scripts/test-local-llamacpp.d.ts +0 -1
- package/dist/scripts/test-local-llamacpp.js +0 -77
- package/dist/scripts/test-local-llamacpp.js.map +0 -1
- package/dist/supervisor/gemini-engine.js +0 -983
- package/dist/supervisor/gemini-engine.js.map +0 -1
- package/dist/swarm/agent-card.d.ts +0 -14
- package/dist/swarm/agent-card.js +0 -93
- package/dist/swarm/agent-card.js.map +0 -1
- package/dist/swarm/rpc-handler.d.ts +0 -27
- package/dist/swarm/rpc-handler.js +0 -235
- package/dist/swarm/rpc-handler.js.map +0 -1
- package/dist/swarm/swarm-service.d.ts +0 -47
- package/dist/swarm/swarm-service.js +0 -207
- package/dist/swarm/swarm-service.js.map +0 -1
- package/dist/swarm/types.d.ts +0 -109
- package/dist/swarm/types.js +0 -15
- package/dist/swarm/types.js.map +0 -1
- /package/extensions/memory/{gemini-extension.json → tars-extension.json} +0 -0
- /package/extensions/tasks/{gemini-extension.json → tars-extension.json} +0 -0
|
@@ -6,58 +6,16 @@ import fs from 'fs/promises';
|
|
|
6
6
|
import fsSync from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { Client, GatewayIntentBits } from 'discord.js';
|
|
9
|
-
import { TarsOAuthService } from '../../auth/oauth-service.js';
|
|
10
|
-
import { WorkspaceOAuthService } from '../../auth/workspace-auth-service.js';
|
|
11
9
|
import { BrainAuditor } from '../../utils/brain-audit.js';
|
|
12
10
|
import { getTarsHome } from '../../utils/paths.js';
|
|
13
11
|
import { SecretsManager } from '../../utils/secrets-manager.js';
|
|
14
|
-
import
|
|
15
|
-
/**
|
|
16
|
-
* Check if the isolated tars environment is authenticated
|
|
17
|
-
*/
|
|
18
|
-
async function checkTarsAuth(tarsHome) {
|
|
19
|
-
const oauthService = new TarsOAuthService(tarsHome);
|
|
20
|
-
return await oauthService.isAuthenticated();
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Helper to setup Workspace Auth
|
|
24
|
-
*/
|
|
25
|
-
async function setupWorkspaceAuth(tarsHome, wsService) {
|
|
26
|
-
const secretsManager = new SecretsManager(tarsHome);
|
|
27
|
-
const secrets = secretsManager.load();
|
|
28
|
-
if (!secrets.GOOGLE_WORKSPACE_CLIENT_ID || !secrets.GOOGLE_WORKSPACE_CLIENT_SECRET) {
|
|
29
|
-
console.log(chalk.yellow('\n Workspace integration requires an OAuth Client ID and Secret.'));
|
|
30
|
-
console.log(chalk.dim(' Create one in the Google Cloud Console (Desktop App type).'));
|
|
31
|
-
const answers = await inquirer.prompt([
|
|
32
|
-
{
|
|
33
|
-
type: 'input',
|
|
34
|
-
name: 'clientId',
|
|
35
|
-
message: ' Enter Google Workspace Client ID:',
|
|
36
|
-
default: secrets.GOOGLE_WORKSPACE_CLIENT_ID,
|
|
37
|
-
validate: (i) => i.length > 10 || 'Required'
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
type: 'password',
|
|
41
|
-
name: 'clientSecret',
|
|
42
|
-
message: ' Enter Google Workspace Client Secret:',
|
|
43
|
-
default: secrets.GOOGLE_WORKSPACE_CLIENT_SECRET,
|
|
44
|
-
validate: (i) => i.length > 5 || 'Required'
|
|
45
|
-
}
|
|
46
|
-
]);
|
|
47
|
-
secretsManager.set('GOOGLE_WORKSPACE_CLIENT_ID', answers.clientId);
|
|
48
|
-
secretsManager.set('GOOGLE_WORKSPACE_CLIENT_SECRET', answers.clientSecret);
|
|
49
|
-
// Refresh env for the service
|
|
50
|
-
process.env.GOOGLE_WORKSPACE_CLIENT_ID = answers.clientId;
|
|
51
|
-
process.env.GOOGLE_WORKSPACE_CLIENT_SECRET = answers.clientSecret;
|
|
52
|
-
}
|
|
53
|
-
await wsService.login();
|
|
54
|
-
}
|
|
12
|
+
import { migrateLegacyConfig } from '../../utils/migration-manager.js';
|
|
55
13
|
/**
|
|
56
14
|
* tars setup - The Onboarding Wizard
|
|
57
15
|
*/
|
|
58
16
|
export async function setup() {
|
|
59
|
-
console.log(chalk.cyan.bold('\n🤖 Welcome to Tars Setup!'));
|
|
60
|
-
console.log(chalk.cyan('
|
|
17
|
+
console.log(chalk.cyan.bold('\n🤖 Welcome to Tars Setup! (Pi Agent SDK Edition)'));
|
|
18
|
+
console.log(chalk.cyan('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
61
19
|
// ── Prerequisites ──────────────────────────────────────
|
|
62
20
|
const spinner = ora('Checking prerequisites...').start();
|
|
63
21
|
// Check Node version
|
|
@@ -69,6 +27,8 @@ export async function setup() {
|
|
|
69
27
|
}
|
|
70
28
|
const tarsHome = getTarsHome();
|
|
71
29
|
spinner.succeed(`Prerequisites met (Node ${nodeVersion})`);
|
|
30
|
+
// Run automated migration manager
|
|
31
|
+
await migrateLegacyConfig(tarsHome);
|
|
72
32
|
// Load existing config for defaults
|
|
73
33
|
let existingConfig = {};
|
|
74
34
|
try {
|
|
@@ -78,94 +38,148 @@ export async function setup() {
|
|
|
78
38
|
catch {
|
|
79
39
|
/* ignore */
|
|
80
40
|
}
|
|
41
|
+
const secretsManager = new SecretsManager(tarsHome);
|
|
42
|
+
const secrets = secretsManager.load();
|
|
81
43
|
// ══════════════════════════════════════════════════════════
|
|
82
|
-
// ── Step 1:
|
|
83
|
-
// This is asked FIRST because it determines the entire flow.
|
|
44
|
+
// ── Step 1: Model Provider ────────────────────────────────
|
|
84
45
|
// ══════════════════════════════════════════════════════════
|
|
85
|
-
console.log(chalk.bold('\nStep 1:
|
|
86
|
-
console.log(chalk.dim('
|
|
87
|
-
console.log(chalk.dim(
|
|
88
|
-
|
|
89
|
-
const { inferenceBackend } = await inquirer.prompt([
|
|
46
|
+
console.log(chalk.bold('\nStep 1: Model Provider'));
|
|
47
|
+
console.log(chalk.dim('──────────────────────'));
|
|
48
|
+
console.log(chalk.dim(' Choose the AI provider and API configurations for Tars.'));
|
|
49
|
+
const { piProvider } = await inquirer.prompt([
|
|
90
50
|
{
|
|
91
51
|
type: 'list',
|
|
92
|
-
name: '
|
|
93
|
-
message: '
|
|
52
|
+
name: 'piProvider',
|
|
53
|
+
message: 'Select AI Model Provider:',
|
|
94
54
|
choices: [
|
|
95
|
-
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: `🖥️ Local Model ${chalk.dim('— LlamaCpp / OpenAI-compatible endpoint, runs on your hardware')}`,
|
|
101
|
-
value: 'llamacpp'
|
|
102
|
-
}
|
|
55
|
+
{ name: 'Google (Gemini SDK / API Key)', value: 'google' },
|
|
56
|
+
{ name: 'OpenAI (GPT-4o, etc.)', value: 'openai' },
|
|
57
|
+
{ name: 'Anthropic (Claude 3.5 Sonnet, etc.)', value: 'anthropic' },
|
|
58
|
+
{ name: 'Local Stark (Qwen 3.6 @ stark:8086)', value: 'local-stark' },
|
|
59
|
+
{ name: 'Custom (OpenAI-compatible proxy/local endpoint)', value: 'custom' }
|
|
103
60
|
],
|
|
104
|
-
default: existingConfig.
|
|
61
|
+
default: existingConfig.piProvider || 'google'
|
|
105
62
|
}
|
|
106
63
|
]);
|
|
107
|
-
const isLocal = inferenceBackend === 'llamacpp';
|
|
108
64
|
// ══════════════════════════════════════════════════════════
|
|
109
|
-
// ── Step 2:
|
|
110
|
-
// Gemini: Google OAuth required. Local: skipped entirely.
|
|
65
|
+
// ── Step 2: Credentials & Model Configuration ─────────────
|
|
111
66
|
// ══════════════════════════════════════════════════════════
|
|
112
|
-
console.log(chalk.bold('\nStep 2:
|
|
113
|
-
console.log(chalk.dim('
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
67
|
+
console.log(chalk.bold('\nStep 2: Credentials & Model ID'));
|
|
68
|
+
console.log(chalk.dim('──────────────────────────────'));
|
|
69
|
+
let piBaseUrl = '';
|
|
70
|
+
let piApiKey = '';
|
|
71
|
+
let defaultModel = '';
|
|
72
|
+
if (piProvider === 'google') {
|
|
73
|
+
const answers = await inquirer.prompt([
|
|
74
|
+
{
|
|
75
|
+
type: 'password',
|
|
76
|
+
name: 'apiKey',
|
|
77
|
+
message: 'Enter TARS_API_KEY (Google Cloud API Key):',
|
|
78
|
+
default: secrets.TARS_API_KEY ||
|
|
79
|
+
secrets.GEMINI_API_KEY ||
|
|
80
|
+
process.env.TARS_API_KEY ||
|
|
81
|
+
process.env.GEMINI_API_KEY ||
|
|
82
|
+
'',
|
|
83
|
+
validate: (input) => input.length > 0 || 'API Key is required'
|
|
84
|
+
}
|
|
85
|
+
]);
|
|
86
|
+
piApiKey = answers.apiKey;
|
|
87
|
+
secretsManager.set('TARS_API_KEY', piApiKey);
|
|
88
|
+
process.env.TARS_API_KEY = piApiKey;
|
|
89
|
+
secretsManager.set('GEMINI_API_KEY', piApiKey);
|
|
90
|
+
process.env.GEMINI_API_KEY = piApiKey;
|
|
91
|
+
defaultModel = 'gemini-2.5-flash';
|
|
92
|
+
}
|
|
93
|
+
else if (piProvider === 'openai') {
|
|
94
|
+
const answers = await inquirer.prompt([
|
|
95
|
+
{
|
|
96
|
+
type: 'password',
|
|
97
|
+
name: 'apiKey',
|
|
98
|
+
message: 'Enter OPENAI_API_KEY:',
|
|
99
|
+
default: secrets.OPENAI_API_KEY || process.env.OPENAI_API_KEY || '',
|
|
100
|
+
validate: (input) => input.length > 0 || 'API Key is required'
|
|
101
|
+
}
|
|
102
|
+
]);
|
|
103
|
+
piApiKey = answers.apiKey;
|
|
104
|
+
secretsManager.set('OPENAI_API_KEY', piApiKey);
|
|
105
|
+
process.env.OPENAI_API_KEY = piApiKey;
|
|
106
|
+
defaultModel = 'gpt-4o';
|
|
107
|
+
}
|
|
108
|
+
else if (piProvider === 'anthropic') {
|
|
109
|
+
const answers = await inquirer.prompt([
|
|
110
|
+
{
|
|
111
|
+
type: 'password',
|
|
112
|
+
name: 'apiKey',
|
|
113
|
+
message: 'Enter ANTHROPIC_API_KEY:',
|
|
114
|
+
default: secrets.ANTHROPIC_API_KEY || process.env.ANTHROPIC_API_KEY || '',
|
|
115
|
+
validate: (input) => input.length > 0 || 'API Key is required'
|
|
116
|
+
}
|
|
117
|
+
]);
|
|
118
|
+
piApiKey = answers.apiKey;
|
|
119
|
+
secretsManager.set('ANTHROPIC_API_KEY', piApiKey);
|
|
120
|
+
process.env.ANTHROPIC_API_KEY = piApiKey;
|
|
121
|
+
defaultModel = 'claude-3-5-sonnet-latest';
|
|
122
|
+
}
|
|
123
|
+
else if (piProvider === 'local-stark') {
|
|
124
|
+
const answers = await inquirer.prompt([
|
|
125
|
+
{
|
|
126
|
+
type: 'input',
|
|
127
|
+
name: 'baseUrl',
|
|
128
|
+
message: 'Stark Endpoint URL:',
|
|
129
|
+
default: existingConfig.piBaseUrl || 'http://stark:8086/v1'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'password',
|
|
133
|
+
name: 'apiKey',
|
|
134
|
+
message: 'Stark API Key:',
|
|
135
|
+
default: secrets.STARK_API_KEY || 'dummy-key'
|
|
136
|
+
}
|
|
137
|
+
]);
|
|
138
|
+
piBaseUrl = answers.baseUrl;
|
|
139
|
+
piApiKey = answers.apiKey;
|
|
140
|
+
secretsManager.set('STARK_API_KEY', piApiKey);
|
|
141
|
+
process.env.STARK_API_KEY = piApiKey;
|
|
142
|
+
defaultModel = 'Qwen3.6-35B-A3B-Q8';
|
|
117
143
|
}
|
|
118
144
|
else {
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
else {
|
|
134
|
-
const { authNow } = await inquirer.prompt([
|
|
135
|
-
{
|
|
136
|
-
type: 'confirm',
|
|
137
|
-
name: 'authNow',
|
|
138
|
-
message: 'Gemini requires Google OAuth for inference. Continue with authentication?',
|
|
139
|
-
default: true
|
|
140
|
-
}
|
|
141
|
-
]);
|
|
142
|
-
performAuth = authNow;
|
|
143
|
-
}
|
|
144
|
-
if (performAuth) {
|
|
145
|
-
console.log(chalk.cyan('\n Running Google Authentication...'));
|
|
146
|
-
console.log(chalk.dim(' 1. Copy the URL provided below into Chrome.'));
|
|
147
|
-
console.log(chalk.dim(' 2. Sign in and copy the authorization code.'));
|
|
148
|
-
console.log(chalk.dim(' 3. Paste the code back here.'));
|
|
149
|
-
console.log(chalk.dim(' -----------------------------------'));
|
|
150
|
-
try {
|
|
151
|
-
const oauthService = new TarsOAuthService(tarsHome);
|
|
152
|
-
await oauthService.login(isAuthed);
|
|
153
|
-
const freshStatus = await oauthService.isAuthenticated();
|
|
154
|
-
if (freshStatus) {
|
|
155
|
-
console.log(chalk.green(' ✓ Authentication successful!'));
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
console.log(chalk.yellow(' ⚠ Warning: Could not verify authentication.'));
|
|
145
|
+
const answers = await inquirer.prompt([
|
|
146
|
+
{
|
|
147
|
+
type: 'input',
|
|
148
|
+
name: 'baseUrl',
|
|
149
|
+
message: 'Custom Endpoint Base URL:',
|
|
150
|
+
default: existingConfig.piBaseUrl || 'http://localhost:8080/v1',
|
|
151
|
+
validate: (input) => {
|
|
152
|
+
try {
|
|
153
|
+
new URL(input);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return 'Invalid URL';
|
|
158
|
+
}
|
|
159
159
|
}
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
type: 'password',
|
|
163
|
+
name: 'apiKey',
|
|
164
|
+
message: 'Custom Endpoint API Key:',
|
|
165
|
+
default: secrets.CUSTOM_API_KEY || 'dummy-key'
|
|
160
166
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
167
|
+
]);
|
|
168
|
+
piBaseUrl = answers.baseUrl;
|
|
169
|
+
piApiKey = answers.apiKey;
|
|
170
|
+
secretsManager.set('CUSTOM_API_KEY', piApiKey);
|
|
171
|
+
process.env.CUSTOM_API_KEY = piApiKey;
|
|
172
|
+
defaultModel = 'custom-model';
|
|
168
173
|
}
|
|
174
|
+
const { piModel } = await inquirer.prompt([
|
|
175
|
+
{
|
|
176
|
+
type: 'input',
|
|
177
|
+
name: 'piModel',
|
|
178
|
+
message: `Enter Model ID (default recommended: ${defaultModel}):`,
|
|
179
|
+
default: existingConfig.piModel || defaultModel,
|
|
180
|
+
validate: (input) => input.length > 0 || 'Model ID is required'
|
|
181
|
+
}
|
|
182
|
+
]);
|
|
169
183
|
// ══════════════════════════════════════════════════════════
|
|
170
184
|
// ── Step 3: Communication Channel ─────────────────────────
|
|
171
185
|
// ══════════════════════════════════════════════════════════
|
|
@@ -218,262 +232,54 @@ export async function setup() {
|
|
|
218
232
|
}
|
|
219
233
|
}
|
|
220
234
|
// ══════════════════════════════════════════════════════════
|
|
221
|
-
// ── Step 4: Identity
|
|
222
|
-
// The questions differ based on inference backend.
|
|
235
|
+
// ── Step 4: Identity ──────────────────────────────────────
|
|
223
236
|
// ══════════════════════════════════════════════════════════
|
|
224
|
-
console.log(chalk.bold('\nStep 4: Identity
|
|
225
|
-
console.log(chalk.dim('
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
name: '
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const probeSpinner = ora(`Testing endpoint ${basicConfig.localInferenceUrl}...`).start();
|
|
257
|
-
const probe = await probeEndpoint(basicConfig.localInferenceUrl);
|
|
258
|
-
let selectedModel = existingConfig.geminiModel || 'auto';
|
|
259
|
-
if (probe.reachable) {
|
|
260
|
-
if (probe.models.length > 0) {
|
|
261
|
-
probeSpinner.succeed(`Endpoint reachable! Found ${probe.models.length} model(s).`);
|
|
262
|
-
// Build choices from discovered models + custom option
|
|
263
|
-
const modelChoices = probe.models.map((m) => ({
|
|
264
|
-
name: `${m}`,
|
|
265
|
-
value: m
|
|
266
|
-
}));
|
|
267
|
-
modelChoices.push({
|
|
268
|
-
name: chalk.dim('Custom (enter manually)'),
|
|
269
|
-
value: '__custom__'
|
|
270
|
-
});
|
|
271
|
-
const { modelChoice } = await inquirer.prompt([
|
|
272
|
-
{
|
|
273
|
-
type: 'list',
|
|
274
|
-
name: 'modelChoice',
|
|
275
|
-
message: 'Which model should Tars use?',
|
|
276
|
-
choices: modelChoices,
|
|
277
|
-
default: probe.models.includes(selectedModel)
|
|
278
|
-
? selectedModel
|
|
279
|
-
: probe.models[0]
|
|
280
|
-
}
|
|
281
|
-
]);
|
|
282
|
-
if (modelChoice === '__custom__') {
|
|
283
|
-
const { customModel } = await inquirer.prompt([
|
|
284
|
-
{
|
|
285
|
-
type: 'input',
|
|
286
|
-
name: 'customModel',
|
|
287
|
-
message: 'Enter model name:',
|
|
288
|
-
default: selectedModel,
|
|
289
|
-
validate: (input) => input.length > 0 || 'Model name is required'
|
|
290
|
-
}
|
|
291
|
-
]);
|
|
292
|
-
selectedModel = customModel;
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
selectedModel = modelChoice;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
probeSpinner.succeed('Endpoint reachable! (no model list available)');
|
|
300
|
-
const { manualModel } = await inquirer.prompt([
|
|
301
|
-
{
|
|
302
|
-
type: 'input',
|
|
303
|
-
name: 'manualModel',
|
|
304
|
-
message: 'Model Name (sent in the OpenAI `model` field — use the name your server expects):',
|
|
305
|
-
default: selectedModel,
|
|
306
|
-
validate: (input) => input.length > 0 || 'Model name is required'
|
|
307
|
-
}
|
|
308
|
-
]);
|
|
309
|
-
selectedModel = manualModel;
|
|
237
|
+
console.log(chalk.bold('\nStep 4: Identity'));
|
|
238
|
+
console.log(chalk.dim('────────────────'));
|
|
239
|
+
const identityConfig = await inquirer.prompt([
|
|
240
|
+
{
|
|
241
|
+
type: 'input',
|
|
242
|
+
name: 'assistantName',
|
|
243
|
+
message: 'Assistant Name (Display identity):',
|
|
244
|
+
default: existingConfig.assistantName || 'Tars'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
type: 'list',
|
|
248
|
+
name: 'heartbeatMinutes',
|
|
249
|
+
message: 'Heartbeat Interval (How often Tars checks in):',
|
|
250
|
+
choices: [
|
|
251
|
+
{ name: '30 Minutes (Recommended)', value: 30 },
|
|
252
|
+
{ name: '1 Hour', value: 60 },
|
|
253
|
+
{ name: '2 Hours', value: 120 },
|
|
254
|
+
{ name: '4 Hours', value: 240 },
|
|
255
|
+
{ name: 'Custom', value: 'custom' }
|
|
256
|
+
],
|
|
257
|
+
default: existingConfig.heartbeatIntervalSec
|
|
258
|
+
? Math.floor(existingConfig.heartbeatIntervalSec / 60)
|
|
259
|
+
: 30
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
type: 'input',
|
|
263
|
+
name: 'customHeartbeat',
|
|
264
|
+
message: 'Enter custom heartbeat interval in minutes:',
|
|
265
|
+
when: (answers) => answers.heartbeatMinutes === 'custom',
|
|
266
|
+
validate: (input) => {
|
|
267
|
+
const n = parseInt(input, 10);
|
|
268
|
+
return (!isNaN(n) && n > 0) || 'Must be a positive number';
|
|
310
269
|
}
|
|
311
270
|
}
|
|
312
|
-
|
|
313
|
-
probeSpinner.warn(`Could not reach endpoint: ${probe.error || 'unknown error'}`);
|
|
314
|
-
console.log(chalk.yellow(' ⚠ The endpoint is not responding. Configuration will continue but\n' +
|
|
315
|
-
' make sure the server is running when you start Tars.'));
|
|
316
|
-
const { manualModel } = await inquirer.prompt([
|
|
317
|
-
{
|
|
318
|
-
type: 'input',
|
|
319
|
-
name: 'manualModel',
|
|
320
|
-
message: 'Model Name (enter manually):',
|
|
321
|
-
default: selectedModel,
|
|
322
|
-
validate: (input) => input.length > 0 || 'Model name is required'
|
|
323
|
-
}
|
|
324
|
-
]);
|
|
325
|
-
selectedModel = manualModel;
|
|
326
|
-
}
|
|
327
|
-
// Step 4c: Context window + heartbeat
|
|
328
|
-
const cwChoices = [];
|
|
329
|
-
if (probe.contextWindow) {
|
|
330
|
-
cwChoices.push({
|
|
331
|
-
name: `🤖 Auto-Detect (${probe.contextWindow} tokens from server)`,
|
|
332
|
-
value: probe.contextWindow
|
|
333
|
-
});
|
|
334
|
-
}
|
|
335
|
-
cwChoices.push({ name: '4K tokens — Small models (TinyLlama)', value: 4096 }, { name: '8K tokens — Standard (Llama 3 8B)', value: 8192 }, { name: '16K tokens — Extended (Mistral 7B)', value: 16384 }, { name: '32K tokens — Large context (Qwen 3.5)', value: 32768 }, { name: '128K tokens — Very large context (Llama 3.1 70B)', value: 131072 }, { name: 'Custom', value: 'custom' });
|
|
336
|
-
const advancedConfig = await inquirer.prompt([
|
|
337
|
-
{
|
|
338
|
-
type: 'list',
|
|
339
|
-
name: 'contextWindowTokens',
|
|
340
|
-
message: 'Context Window Size (depends on your model):',
|
|
341
|
-
choices: cwChoices,
|
|
342
|
-
default: probe.contextWindow
|
|
343
|
-
? probe.contextWindow
|
|
344
|
-
: existingConfig.contextWindowTokens || 8192
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
type: 'input',
|
|
348
|
-
name: 'customContextWindow',
|
|
349
|
-
message: 'Enter context window size (number of tokens):',
|
|
350
|
-
when: (answers) => answers.contextWindowTokens === 'custom',
|
|
351
|
-
validate: (input) => {
|
|
352
|
-
const n = parseInt(input, 10);
|
|
353
|
-
return (!isNaN(n) && n > 0) || 'Must be a positive number';
|
|
354
|
-
}
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
type: 'list',
|
|
358
|
-
name: 'heartbeatMinutes',
|
|
359
|
-
message: 'Heartbeat Interval (How often Tars checks in):',
|
|
360
|
-
choices: [
|
|
361
|
-
{ name: '30 Minutes (Recommended)', value: 30 },
|
|
362
|
-
{ name: '1 Hour', value: 60 },
|
|
363
|
-
{ name: '2 Hours', value: 120 },
|
|
364
|
-
{ name: '4 Hours', value: 240 },
|
|
365
|
-
{ name: 'Custom', value: 'custom' }
|
|
366
|
-
],
|
|
367
|
-
default: existingConfig.heartbeatIntervalSec
|
|
368
|
-
? Math.floor(existingConfig.heartbeatIntervalSec / 60)
|
|
369
|
-
: 30
|
|
370
|
-
}
|
|
371
|
-
]);
|
|
372
|
-
config = {
|
|
373
|
-
...basicConfig,
|
|
374
|
-
...advancedConfig,
|
|
375
|
-
geminiModel: selectedModel,
|
|
376
|
-
inferenceBackend: 'llamacpp'
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
console.log(chalk.dim(' Tars is your personal assistant and sidekick.'));
|
|
381
|
-
console.log(chalk.dim(' Every Google account includes free Gemini inference!'));
|
|
382
|
-
config = await inquirer.prompt([
|
|
383
|
-
{
|
|
384
|
-
type: 'input',
|
|
385
|
-
name: 'assistantName',
|
|
386
|
-
message: 'Assistant Name (Display identity):',
|
|
387
|
-
default: existingConfig.assistantName || 'Tars'
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
type: 'list',
|
|
391
|
-
name: 'geminiModel',
|
|
392
|
-
message: 'Select Gemini Model:',
|
|
393
|
-
choices: [
|
|
394
|
-
{ name: 'Auto (Recommended - High IQ)', value: 'auto' },
|
|
395
|
-
{ name: 'Auto (Gemini 2.5 Path)', value: 'auto-gemini-2.5' },
|
|
396
|
-
{ name: 'Gemini 2.0 Flash (Fastest)', value: 'gemini-2.0-flash' },
|
|
397
|
-
{ name: 'Gemini 2.5 Flash', value: 'gemini-2.5-flash' },
|
|
398
|
-
{ name: 'Gemini 2.5 Pro (Balanced)', value: 'gemini-2.5-pro' },
|
|
399
|
-
{ name: 'Gemini 3 Flash (Preview)', value: 'gemini-3-flash-preview' },
|
|
400
|
-
{ name: 'Gemini 3 Pro (Preview)', value: 'gemini-3-pro-preview' },
|
|
401
|
-
{ name: 'Custom (Advanced)', value: 'custom' }
|
|
402
|
-
],
|
|
403
|
-
default: existingConfig.geminiModel || 'auto'
|
|
404
|
-
},
|
|
405
|
-
{
|
|
406
|
-
type: 'input',
|
|
407
|
-
name: 'customModel',
|
|
408
|
-
message: 'Enter custom model name:',
|
|
409
|
-
when: (answers) => answers.geminiModel === 'custom'
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
type: 'list',
|
|
413
|
-
name: 'heartbeatMinutes',
|
|
414
|
-
message: 'Heartbeat Interval (How often Tars checks in):',
|
|
415
|
-
choices: [
|
|
416
|
-
{ name: '30 Minutes (Recommended)', value: 30 },
|
|
417
|
-
{ name: '1 Hour', value: 60 },
|
|
418
|
-
{ name: '2 Hours', value: 120 },
|
|
419
|
-
{ name: '4 Hours', value: 240 },
|
|
420
|
-
{ name: 'Custom', value: 'custom' }
|
|
421
|
-
],
|
|
422
|
-
default: existingConfig.heartbeatIntervalSec
|
|
423
|
-
? Math.floor(existingConfig.heartbeatIntervalSec / 60)
|
|
424
|
-
: 30
|
|
425
|
-
}
|
|
426
|
-
]);
|
|
427
|
-
config.inferenceBackend = 'gemini';
|
|
428
|
-
}
|
|
271
|
+
]);
|
|
429
272
|
// ══════════════════════════════════════════════════════════
|
|
430
273
|
// ── Step 5: Integrations ──────────────────────────────────
|
|
431
|
-
// Google Workspace is only relevant for Gemini backend.
|
|
432
274
|
// ══════════════════════════════════════════════════════════
|
|
433
275
|
console.log(chalk.bold('\nStep 5: Integrations'));
|
|
434
276
|
console.log(chalk.dim('────────────────────'));
|
|
435
|
-
|
|
436
|
-
console.log(chalk.dim(' Google Workspace integration requires cloud auth and is not available with local inference.'));
|
|
437
|
-
console.log(chalk.dim(' Skipping.'));
|
|
438
|
-
}
|
|
439
|
-
else {
|
|
440
|
-
console.log(chalk.dim(' Enables Tars to read verification emails, manage'));
|
|
441
|
-
console.log(chalk.dim(' your calendar, and interact with your files.'));
|
|
442
|
-
const wsService = new WorkspaceOAuthService(tarsHome);
|
|
443
|
-
const isWsAuthed = await wsService.isAuthenticated();
|
|
444
|
-
if (isWsAuthed) {
|
|
445
|
-
console.log(chalk.green(' ✓ Google Workspace already authenticated.'));
|
|
446
|
-
const { reAuthWs } = await inquirer.prompt([
|
|
447
|
-
{
|
|
448
|
-
type: 'confirm',
|
|
449
|
-
name: 'reAuthWs',
|
|
450
|
-
message: 'Do you want to re-authenticate Workspace access?',
|
|
451
|
-
default: false
|
|
452
|
-
}
|
|
453
|
-
]);
|
|
454
|
-
if (reAuthWs)
|
|
455
|
-
await setupWorkspaceAuth(tarsHome, wsService);
|
|
456
|
-
}
|
|
457
|
-
else {
|
|
458
|
-
const { setupWs } = await inquirer.prompt([
|
|
459
|
-
{
|
|
460
|
-
type: 'confirm',
|
|
461
|
-
name: 'setupWs',
|
|
462
|
-
message: 'Enable Google Workspace integration (Gmail, Drive, Calendar)?',
|
|
463
|
-
default: true
|
|
464
|
-
}
|
|
465
|
-
]);
|
|
466
|
-
if (setupWs)
|
|
467
|
-
await setupWorkspaceAuth(tarsHome, wsService);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
277
|
+
console.log(chalk.dim(' Workspace integration has been deprecated. Skipping.'));
|
|
470
278
|
// ══════════════════════════════════════════════════════════
|
|
471
279
|
// ── Step 6: Tars Dashboard ────────────────────────────────
|
|
472
280
|
// ══════════════════════════════════════════════════════════
|
|
473
281
|
console.log(chalk.bold('\nStep 6: Tars Dashboard'));
|
|
474
|
-
console.log(chalk.dim('
|
|
475
|
-
const secretsManager = new SecretsManager(tarsHome);
|
|
476
|
-
const secrets = secretsManager.load();
|
|
282
|
+
console.log(chalk.dim('──────────────────────'));
|
|
477
283
|
const dashConfig = await inquirer.prompt([
|
|
478
284
|
{
|
|
479
285
|
type: 'confirm',
|
|
@@ -510,124 +316,45 @@ export async function setup() {
|
|
|
510
316
|
console.log(chalk.green(' ✓ Dashboard configuration saved.'));
|
|
511
317
|
}
|
|
512
318
|
// ══════════════════════════════════════════════════════════
|
|
513
|
-
// ── Step 7:
|
|
514
|
-
// Allows other Tars instances to delegate tasks to this one.
|
|
515
|
-
// ══════════════════════════════════════════════════════════
|
|
516
|
-
console.log(chalk.bold('\nStep 7: Swarm Mode (A2A)'));
|
|
517
|
-
console.log(chalk.dim('────────────────────────'));
|
|
518
|
-
console.log(chalk.dim(' Allow other Tars instances to discover and'));
|
|
519
|
-
console.log(chalk.dim(' delegate tasks to this agent using the A2A protocol.'));
|
|
520
|
-
const existingSwarm = existingConfig.swarm || {};
|
|
521
|
-
const existingSwarmKey = secrets.SWARM_API_KEY || '';
|
|
522
|
-
const swarmConfig = await inquirer.prompt([
|
|
523
|
-
{
|
|
524
|
-
type: 'confirm',
|
|
525
|
-
name: 'enableSwarm',
|
|
526
|
-
message: 'Enable Swarm Mode (allow other agents to connect)?',
|
|
527
|
-
default: existingSwarm.enabled || false
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
type: 'input',
|
|
531
|
-
name: 'swarmPort',
|
|
532
|
-
message: 'Swarm API Port:',
|
|
533
|
-
default: String(existingSwarm.port || '3100'),
|
|
534
|
-
when: (a) => a.enableSwarm,
|
|
535
|
-
validate: (input) => {
|
|
536
|
-
const n = parseInt(input, 10);
|
|
537
|
-
if (isNaN(n) || n < 1024 || n > 65535) {
|
|
538
|
-
return 'Port must be a number between 1024 and 65535';
|
|
539
|
-
}
|
|
540
|
-
return true;
|
|
541
|
-
}
|
|
542
|
-
},
|
|
543
|
-
{
|
|
544
|
-
type: 'input',
|
|
545
|
-
name: 'swarmDescription',
|
|
546
|
-
message: 'Instance description (for other agents):',
|
|
547
|
-
default: existingSwarm.description ||
|
|
548
|
-
`${config.assistantName || existingConfig.assistantName || 'Tars'} — Autonomous AI assistant`,
|
|
549
|
-
when: (a) => a.enableSwarm
|
|
550
|
-
}
|
|
551
|
-
]);
|
|
552
|
-
if (swarmConfig.enableSwarm) {
|
|
553
|
-
// Auto-generate API key if one doesn't exist
|
|
554
|
-
const apiKey = existingSwarmKey || `tars_swarm_${crypto.randomBytes(24).toString('hex')}`;
|
|
555
|
-
secretsManager.set('SWARM_API_KEY', apiKey);
|
|
556
|
-
console.log(chalk.green(' ✓ Swarm mode configured.'));
|
|
557
|
-
console.log(chalk.dim(` Port: ${swarmConfig.swarmPort}`));
|
|
558
|
-
console.log(chalk.dim(` API Key: ${apiKey.substring(0, 16)}...${apiKey.substring(apiKey.length - 4)}`));
|
|
559
|
-
console.log('');
|
|
560
|
-
console.log(chalk.dim(' To register this instance on another Tars, run:'));
|
|
561
|
-
console.log(chalk.cyan(` tars swarm add --name ${(config.assistantName || 'tars').toLowerCase()} \\`));
|
|
562
|
-
console.log(chalk.cyan(` --url http://<this-host>:${swarmConfig.swarmPort}/.well-known/agent.json \\`));
|
|
563
|
-
console.log(chalk.cyan(` --key ${apiKey}`));
|
|
564
|
-
}
|
|
565
|
-
// ══════════════════════════════════════════════════════════
|
|
566
|
-
// ── Step 8: Installing ────────────────────────────────────
|
|
319
|
+
// ── Step 7: Installing ────────────────────────────────────
|
|
567
320
|
// ══════════════════════════════════════════════════════════
|
|
568
|
-
console.log(chalk.bold('\nStep
|
|
321
|
+
console.log(chalk.bold('\nStep 7: Installing'));
|
|
569
322
|
console.log(chalk.dim('──────────────────'));
|
|
570
|
-
//
|
|
323
|
+
// Audit and Heal
|
|
571
324
|
const auditor = new BrainAuditor(tarsHome);
|
|
572
325
|
await auditor.audit({ silent: true });
|
|
573
|
-
//
|
|
326
|
+
// Provision isolated environment
|
|
574
327
|
const installSpinner = ora('Provisioning environment...').start();
|
|
575
|
-
const geminiDir = path.join(tarsHome, '.gemini');
|
|
576
328
|
await fs.mkdir(path.join(tarsHome, 'data', 'uploads'), { recursive: true });
|
|
577
329
|
await fs.mkdir(path.join(tarsHome, 'logs'), { recursive: true });
|
|
578
330
|
await fs.mkdir(path.join(tarsHome, 'apps'), { recursive: true });
|
|
579
|
-
await fs.mkdir(path.join(
|
|
580
|
-
await fs.mkdir(path.join(
|
|
581
|
-
await fs.mkdir(path.join(
|
|
582
|
-
installSpinner.succeed('Directories created (~/.tars
|
|
583
|
-
//
|
|
331
|
+
await fs.mkdir(path.join(tarsHome, 'extensions'), { recursive: true });
|
|
332
|
+
await fs.mkdir(path.join(tarsHome, 'tmp'), { recursive: true });
|
|
333
|
+
await fs.mkdir(path.join(tarsHome, 'chats'), { recursive: true });
|
|
334
|
+
installSpinner.succeed('Directories created (~/.tars/)');
|
|
335
|
+
// Legacy Cleanup
|
|
584
336
|
const cleanupSpinner = ora('Checking for legacy components...').start();
|
|
585
337
|
const oldDash = path.join(tarsHome, 'dashboard');
|
|
586
338
|
if (fsSync.existsSync(oldDash)) {
|
|
587
339
|
await fs.rm(oldDash, { recursive: true, force: true, maxRetries: 3, retryDelay: 200 });
|
|
588
340
|
cleanupSpinner.text = 'Cleaned up legacy dashboard directory.';
|
|
589
341
|
}
|
|
590
|
-
const oldStandaloneDash = path.resolve(tarsHome, '..', 'apps', 'tars-dash');
|
|
591
|
-
if (fsSync.existsSync(oldStandaloneDash)) {
|
|
592
|
-
await fs.rm(oldStandaloneDash, {
|
|
593
|
-
recursive: true,
|
|
594
|
-
force: true,
|
|
595
|
-
maxRetries: 3,
|
|
596
|
-
retryDelay: 200
|
|
597
|
-
});
|
|
598
|
-
cleanupSpinner.text = 'Cleaned up legacy standalone dashboard.';
|
|
599
|
-
}
|
|
600
342
|
cleanupSpinner.succeed('Cleanup complete.');
|
|
601
|
-
//
|
|
343
|
+
// Save final configuration
|
|
602
344
|
const saveSpinner = ora('Saving configuration...').start();
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
60;
|
|
606
|
-
const contextTokens = config.contextWindowTokens === 'custom'
|
|
607
|
-
? parseInt(config.customContextWindow, 10)
|
|
608
|
-
: config.contextWindowTokens;
|
|
345
|
+
const intervalSec = (identityConfig.heartbeatMinutes === 'custom'
|
|
346
|
+
? identityConfig.customHeartbeat
|
|
347
|
+
: identityConfig.heartbeatMinutes) * 60;
|
|
609
348
|
const configData = {
|
|
610
|
-
assistantName:
|
|
349
|
+
assistantName: identityConfig.assistantName,
|
|
611
350
|
discordToken,
|
|
612
351
|
discordOwnerId: existingConfig.discordOwnerId,
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
352
|
+
piProvider,
|
|
353
|
+
piModel,
|
|
354
|
+
piBaseUrl,
|
|
355
|
+
heartbeatIntervalSec: intervalSec,
|
|
356
|
+
inferenceBackend: 'tars'
|
|
616
357
|
};
|
|
617
|
-
// Backend-specific config
|
|
618
|
-
if (isLocal) {
|
|
619
|
-
configData.localInferenceUrl = config.localInferenceUrl;
|
|
620
|
-
configData.contextWindowTokens = contextTokens;
|
|
621
|
-
}
|
|
622
|
-
// Swarm config (only written if enabled)
|
|
623
|
-
if (swarmConfig.enableSwarm) {
|
|
624
|
-
configData.swarm = {
|
|
625
|
-
enabled: true,
|
|
626
|
-
port: parseInt(swarmConfig.swarmPort, 10),
|
|
627
|
-
description: swarmConfig.swarmDescription || '',
|
|
628
|
-
skills: existingSwarm.skills || []
|
|
629
|
-
};
|
|
630
|
-
}
|
|
631
358
|
await fs.writeFile(path.join(tarsHome, 'config.json'), JSON.stringify(configData, null, 2));
|
|
632
359
|
saveSpinner.succeed('Configuration saved.');
|
|
633
360
|
// Hydrate extensions
|
|
@@ -644,14 +371,12 @@ export async function setup() {
|
|
|
644
371
|
console.log(chalk.dim(' Run "tars refresh" to force-update the dashboard.'));
|
|
645
372
|
}
|
|
646
373
|
}
|
|
647
|
-
//
|
|
374
|
+
// Done
|
|
648
375
|
console.log(chalk.green.bold('\n✅ Tars is ready!'));
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
else {
|
|
654
|
-
console.log(chalk.dim(`\n Backend: Gemini Cloud (${finalModel})`));
|
|
376
|
+
console.log(chalk.dim(`\n Provider: ${piProvider}`));
|
|
377
|
+
console.log(chalk.dim(` Model: ${piModel}`));
|
|
378
|
+
if (piBaseUrl) {
|
|
379
|
+
console.log(chalk.dim(` Base URL: ${piBaseUrl}`));
|
|
655
380
|
}
|
|
656
381
|
console.log(`\n Start Tars: ${chalk.cyan('tars start')}`);
|
|
657
382
|
console.log(` Check status: ${chalk.cyan('tars status')}`);
|