@saccolabs/tars 1.31.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.
Files changed (91) hide show
  1. package/context/skills/create-extension/SKILL.md +2 -2
  2. package/context/skills/manage-extensions/SKILL.md +3 -3
  3. package/dist/channels/discord/discord-channel.js +2 -3
  4. package/dist/channels/discord/discord-channel.js.map +1 -1
  5. package/dist/channels/discord/message-formatter.d.ts +1 -1
  6. package/dist/channels/discord/message-formatter.js +1 -1
  7. package/dist/cli/commands/quota.js +13 -48
  8. package/dist/cli/commands/quota.js.map +1 -1
  9. package/dist/cli/commands/refresh.js +40 -3
  10. package/dist/cli/commands/refresh.js.map +1 -1
  11. package/dist/cli/commands/setup.js +192 -467
  12. package/dist/cli/commands/setup.js.map +1 -1
  13. package/dist/cli/index.js +1 -13
  14. package/dist/cli/index.js.map +1 -1
  15. package/dist/config/config.d.ts +5 -10
  16. package/dist/config/config.js +21 -27
  17. package/dist/config/config.js.map +1 -1
  18. package/dist/memory/knowledge-store.js +10 -1
  19. package/dist/memory/knowledge-store.js.map +1 -1
  20. package/dist/memory/memory-manager.d.ts +0 -3
  21. package/dist/memory/memory-manager.js +26 -34
  22. package/dist/memory/memory-manager.js.map +1 -1
  23. package/dist/scripts/debug-cli.js +2 -2
  24. package/dist/scripts/debug-cli.js.map +1 -1
  25. package/dist/supervisor/heartbeat-service.js +1 -1
  26. package/dist/supervisor/heartbeat-service.js.map +1 -1
  27. package/dist/supervisor/main.js +37 -79
  28. package/dist/supervisor/main.js.map +1 -1
  29. package/dist/supervisor/mcp-bridge.d.ts +25 -0
  30. package/dist/supervisor/mcp-bridge.js +157 -0
  31. package/dist/supervisor/mcp-bridge.js.map +1 -0
  32. package/dist/supervisor/session-manager.d.ts +1 -1
  33. package/dist/supervisor/session-manager.js +1 -1
  34. package/dist/supervisor/supervisor.d.ts +14 -7
  35. package/dist/supervisor/supervisor.js +87 -29
  36. package/dist/supervisor/supervisor.js.map +1 -1
  37. package/dist/supervisor/{gemini-engine.d.ts → tars-engine.d.ts} +38 -33
  38. package/dist/supervisor/tars-engine.js +698 -0
  39. package/dist/supervisor/tars-engine.js.map +1 -0
  40. package/dist/tools/get-quota.d.ts +38 -12
  41. package/dist/tools/get-quota.js +37 -94
  42. package/dist/tools/get-quota.js.map +1 -1
  43. package/dist/tools/send-notification.d.ts +32 -7
  44. package/dist/tools/send-notification.js +31 -37
  45. package/dist/tools/send-notification.js.map +1 -1
  46. package/dist/types/index.d.ts +2 -2
  47. package/dist/utils/brain-audit.js +4 -4
  48. package/dist/utils/brain-audit.js.map +1 -1
  49. package/dist/utils/migration-manager.d.ts +4 -0
  50. package/dist/utils/migration-manager.js +205 -0
  51. package/dist/utils/migration-manager.js.map +1 -0
  52. package/extensions/memory/dist/store.js +29 -20
  53. package/extensions/memory/dist/store.js.map +1 -1
  54. package/extensions/memory/src/store.ts +33 -23
  55. package/package.json +4 -3
  56. package/src/prompts/system.md +3 -14
  57. package/context/agents/scaffolder.md +0 -22
  58. package/dist/auth/credential-manager.d.ts +0 -14
  59. package/dist/auth/credential-manager.js +0 -60
  60. package/dist/auth/credential-manager.js.map +0 -1
  61. package/dist/auth/oauth-service.d.ts +0 -24
  62. package/dist/auth/oauth-service.js +0 -89
  63. package/dist/auth/oauth-service.js.map +0 -1
  64. package/dist/auth/workspace-auth-service.d.ts +0 -10
  65. package/dist/auth/workspace-auth-service.js +0 -78
  66. package/dist/auth/workspace-auth-service.js.map +0 -1
  67. package/dist/cli/commands/swarm.d.ts +0 -13
  68. package/dist/cli/commands/swarm.js +0 -250
  69. package/dist/cli/commands/swarm.js.map +0 -1
  70. package/dist/inference/LlamaCppGenerator.d.ts +0 -25
  71. package/dist/inference/LlamaCppGenerator.js +0 -461
  72. package/dist/inference/LlamaCppGenerator.js.map +0 -1
  73. package/dist/scripts/test-local-llamacpp.d.ts +0 -1
  74. package/dist/scripts/test-local-llamacpp.js +0 -77
  75. package/dist/scripts/test-local-llamacpp.js.map +0 -1
  76. package/dist/supervisor/gemini-engine.js +0 -1056
  77. package/dist/supervisor/gemini-engine.js.map +0 -1
  78. package/dist/swarm/agent-card.d.ts +0 -14
  79. package/dist/swarm/agent-card.js +0 -93
  80. package/dist/swarm/agent-card.js.map +0 -1
  81. package/dist/swarm/rpc-handler.d.ts +0 -27
  82. package/dist/swarm/rpc-handler.js +0 -235
  83. package/dist/swarm/rpc-handler.js.map +0 -1
  84. package/dist/swarm/swarm-service.d.ts +0 -47
  85. package/dist/swarm/swarm-service.js +0 -207
  86. package/dist/swarm/swarm-service.js.map +0 -1
  87. package/dist/swarm/types.d.ts +0 -109
  88. package/dist/swarm/types.js +0 -15
  89. package/dist/swarm/types.js.map +0 -1
  90. /package/extensions/memory/{gemini-extension.json → tars-extension.json} +0 -0
  91. /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 crypto from 'node:crypto';
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('━━━━━━━━━━━━━━━━━━━━━━━━\n'));
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: Inference Backend ─────────────────────────────
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: Inference Backend'));
86
- console.log(chalk.dim('─────────────────────────'));
87
- console.log(chalk.dim(" Choose how Tars will think. Gemini uses Google's cloud"));
88
- console.log(chalk.dim(' models (free tier included). Local runs your own model.'));
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: 'inferenceBackend',
93
- message: 'How should Tars run inference?',
52
+ name: 'piProvider',
53
+ message: 'Select AI Model Provider:',
94
54
  choices: [
95
- {
96
- name: `☁️ Gemini Cloud ${chalk.dim('— Google AI, free tier, tool-calling, no GPU needed')}`,
97
- value: 'gemini'
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.inferenceBackend || 'gemini'
61
+ default: existingConfig.piProvider || 'google'
105
62
  }
106
63
  ]);
107
- const isLocal = inferenceBackend === 'llamacpp';
108
64
  // ══════════════════════════════════════════════════════════
109
- // ── Step 2: Authentication ────────────────────────────────
110
- // Gemini: Google OAuth required. Local: skipped entirely.
65
+ // ── Step 2: Credentials & Model Configuration ─────────────
111
66
  // ══════════════════════════════════════════════════════════
112
- console.log(chalk.bold('\nStep 2: Authentication'));
113
- console.log(chalk.dim('──────────────────────'));
114
- if (isLocal) {
115
- console.log(chalk.green(' ✓ Local inference selected — no cloud authentication needed.'));
116
- console.log(chalk.dim(' Tars will connect directly to your local model endpoint.'));
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 isAuthed = await checkTarsAuth(tarsHome);
120
- let performAuth = false;
121
- if (isAuthed) {
122
- console.log(chalk.green(' ✓ Already authenticated with Google.'));
123
- const { reAuth } = await inquirer.prompt([
124
- {
125
- type: 'confirm',
126
- name: 'reAuth',
127
- message: 'Do you want to re-authenticate with a different account?',
128
- default: false
129
- }
130
- ]);
131
- performAuth = reAuth;
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
- catch (err) {
162
- console.error(chalk.red(` Failed to authenticate: ${err.message}`));
163
- }
164
- }
165
- else if (!isAuthed) {
166
- console.log(chalk.yellow(' Skipped. Tars will not be able to communicate with Gemini without auth.'));
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 & Engine ─────────────────────────────
222
- // The questions differ based on inference backend.
235
+ // ── Step 4: Identity ──────────────────────────────────────
223
236
  // ══════════════════════════════════════════════════════════
224
- console.log(chalk.bold('\nStep 4: Identity & Engine'));
225
- console.log(chalk.dim('─────────────────────────'));
226
- let config = {};
227
- if (isLocal) {
228
- console.log(chalk.dim(' Configure your local inference endpoint and context limits.'));
229
- console.log(chalk.dim(' Use a model with tool-calling support (Qwen 3.5, Llama 3.1, etc.).'));
230
- // Step 4a: Collect basic identity + endpoint URL
231
- const basicConfig = await inquirer.prompt([
232
- {
233
- type: 'input',
234
- name: 'assistantName',
235
- message: 'Assistant Name (Display identity):',
236
- default: existingConfig.assistantName || 'Tars'
237
- },
238
- {
239
- type: 'input',
240
- name: 'localInferenceUrl',
241
- message: 'Local Inference URL (OpenAI-compatible endpoint):',
242
- default: existingConfig.localInferenceUrl || 'http://localhost:8080',
243
- validate: (input) => {
244
- try {
245
- new URL(input);
246
- return true;
247
- }
248
- catch {
249
- return 'Please enter a valid URL (e.g., http://localhost:8080)';
250
- }
251
- }
252
- }
253
- ]);
254
- // Step 4b: Probe the endpoint for health + available models
255
- const { probeEndpoint } = await import('../../utils/endpoint-probe.js');
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
- else {
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
- if (isLocal) {
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: Swarm Mode ────────────────────────────────────
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 8: Installing'));
321
+ console.log(chalk.bold('\nStep 7: Installing'));
569
322
  console.log(chalk.dim('──────────────────'));
570
- // 1. Audit and Heal
323
+ // Audit and Heal
571
324
  const auditor = new BrainAuditor(tarsHome);
572
325
  await auditor.audit({ silent: true });
573
- // 2. Provision isolated environment
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(geminiDir, 'extensions'), { recursive: true });
580
- await fs.mkdir(path.join(geminiDir, 'tmp'), { recursive: true });
581
- await fs.mkdir(path.join(geminiDir, 'history'), { recursive: true });
582
- installSpinner.succeed('Directories created (~/.tars/.gemini/)');
583
- // ── Legacy Cleanup ──────────────────────────────
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
- // ── Write Tars configuration ─────────────────────
343
+ // Save final configuration
602
344
  const saveSpinner = ora('Saving configuration...').start();
603
- const finalModel = config.geminiModel === 'custom' ? config.customModel : config.geminiModel;
604
- const intervalSec = (config.heartbeatMinutes === 'custom' ? config.customHeartbeat : config.heartbeatMinutes) *
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: config.assistantName,
349
+ assistantName: identityConfig.assistantName,
611
350
  discordToken,
612
351
  discordOwnerId: existingConfig.discordOwnerId,
613
- geminiModel: finalModel,
614
- inferenceBackend: config.inferenceBackend,
615
- heartbeatIntervalSec: intervalSec
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
- // ── Done ──────────────────────────────────────────────
374
+ // Done
648
375
  console.log(chalk.green.bold('\n✅ Tars is ready!'));
649
- if (isLocal) {
650
- console.log(chalk.dim(`\n Backend: Local Model @ ${config.localInferenceUrl}`));
651
- console.log(chalk.dim(` Context Window: ${(contextTokens || 8192).toLocaleString()} tokens`));
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')}`);