@jive-ai/cli 0.0.1
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/LICENSE +2 -0
- package/dist/config-BP7v03In.mjs +3 -0
- package/dist/index.mjs +2292 -0
- package/package.json +35 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2292 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import prompts from "prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import axios from "axios";
|
|
7
|
+
import fs from "fs/promises";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import "crypto";
|
|
11
|
+
import matter from "gray-matter";
|
|
12
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
13
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
15
|
+
import * as ts from "typescript";
|
|
16
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
17
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
18
|
+
import fs$1 from "fs";
|
|
19
|
+
import { fileURLToPath } from "url";
|
|
20
|
+
|
|
21
|
+
//#region src/lib/config.ts
|
|
22
|
+
const CREDENTIALS_PATH = path.join(os.homedir(), ".jive", "credentials.json");
|
|
23
|
+
const PROJECT_CONFIG_PATH = path.join(process.cwd(), ".jive", "config.json");
|
|
24
|
+
const SYNC_STATE_PATH = path.join(process.cwd(), ".jive", "sync.json");
|
|
25
|
+
async function ensureDir(filePath) {
|
|
26
|
+
const dir = path.dirname(filePath);
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(dir);
|
|
29
|
+
} catch {
|
|
30
|
+
await fs.mkdir(dir, {
|
|
31
|
+
recursive: true,
|
|
32
|
+
mode: 448
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function getCredentials() {
|
|
37
|
+
try {
|
|
38
|
+
const data = await fs.readFile(CREDENTIALS_PATH, "utf-8");
|
|
39
|
+
return JSON.parse(data);
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (error.code === "ENOENT") return null;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function saveCredentials(credentials) {
|
|
46
|
+
await ensureDir(CREDENTIALS_PATH);
|
|
47
|
+
await fs.writeFile(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2), { mode: 384 });
|
|
48
|
+
}
|
|
49
|
+
async function clearCredentials() {
|
|
50
|
+
try {
|
|
51
|
+
await fs.unlink(CREDENTIALS_PATH);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error.code !== "ENOENT") throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function requireAuth() {
|
|
57
|
+
const credentials = await getCredentials();
|
|
58
|
+
if (!credentials) {
|
|
59
|
+
console.error(chalk.red("Not authenticated. Please run `jive login` first."));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
return credentials;
|
|
63
|
+
}
|
|
64
|
+
async function getProjectConfig() {
|
|
65
|
+
try {
|
|
66
|
+
const data = await fs.readFile(PROJECT_CONFIG_PATH, "utf-8");
|
|
67
|
+
return JSON.parse(data);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (error.code === "ENOENT") return null;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function saveProjectConfig(config) {
|
|
74
|
+
await ensureDir(PROJECT_CONFIG_PATH);
|
|
75
|
+
await fs.writeFile(PROJECT_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
76
|
+
}
|
|
77
|
+
async function updateProjectConfig(updates) {
|
|
78
|
+
const config = await getProjectConfig();
|
|
79
|
+
if (!config) throw new Error("Project not initialized. Run `jive init` first.");
|
|
80
|
+
await saveProjectConfig({
|
|
81
|
+
...config,
|
|
82
|
+
...updates
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
async function requireProjectConfig() {
|
|
86
|
+
const config = await getProjectConfig();
|
|
87
|
+
if (!config) {
|
|
88
|
+
console.error(chalk.red("Project not initialized. Please run `jive init` first."));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
return config;
|
|
92
|
+
}
|
|
93
|
+
async function getActiveTeamId() {
|
|
94
|
+
const config = await requireProjectConfig();
|
|
95
|
+
return config.activeTeamId || config.teamId;
|
|
96
|
+
}
|
|
97
|
+
async function isProjectInitialized() {
|
|
98
|
+
return await getProjectConfig() !== null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/lib/api-client.ts
|
|
103
|
+
var ApiClient = class {
|
|
104
|
+
client;
|
|
105
|
+
baseURL;
|
|
106
|
+
constructor(baseURL) {
|
|
107
|
+
this.baseURL = baseURL || process.env.JIVE_API_URL || "https://next.getjive.app";
|
|
108
|
+
this.client = axios.create({
|
|
109
|
+
baseURL: this.baseURL,
|
|
110
|
+
timeout: 3e4,
|
|
111
|
+
headers: { "Content-Type": "application/json" }
|
|
112
|
+
});
|
|
113
|
+
this.client.interceptors.request.use(async (config) => {
|
|
114
|
+
const credentials = await getCredentials();
|
|
115
|
+
if (credentials?.token) config.headers["X-API-Key"] = credentials.token;
|
|
116
|
+
return config;
|
|
117
|
+
}, (error) => Promise.reject(error));
|
|
118
|
+
this.client.interceptors.response.use((response) => response, async (error) => {
|
|
119
|
+
if (error.response?.status === 401) {
|
|
120
|
+
console.error(chalk.red("Authentication failed. Please run `jive login` again."));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
return Promise.reject(this.formatError(error));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
formatError(error) {
|
|
127
|
+
if (error.response) return {
|
|
128
|
+
message: error.response.data?.message || error.message,
|
|
129
|
+
status: error.response.status,
|
|
130
|
+
details: error.response.data
|
|
131
|
+
};
|
|
132
|
+
else if (error.request) return { message: `Unable to connect to ${this.baseURL}. Please check your internet connection.` };
|
|
133
|
+
else return { message: error.message };
|
|
134
|
+
}
|
|
135
|
+
async signup(email, password) {
|
|
136
|
+
return (await this.client.post("/api/auth/signup", {
|
|
137
|
+
email,
|
|
138
|
+
password
|
|
139
|
+
})).data;
|
|
140
|
+
}
|
|
141
|
+
async login(email, password) {
|
|
142
|
+
return (await this.client.post("/api/auth/login", {
|
|
143
|
+
email,
|
|
144
|
+
password
|
|
145
|
+
})).data;
|
|
146
|
+
}
|
|
147
|
+
async logout() {
|
|
148
|
+
await this.client.post("/api/auth/logout");
|
|
149
|
+
}
|
|
150
|
+
async getTeams() {
|
|
151
|
+
return (await this.client.get("/api/teams")).data;
|
|
152
|
+
}
|
|
153
|
+
async createTeam(name, description) {
|
|
154
|
+
return (await this.client.post("/api/teams", {
|
|
155
|
+
name,
|
|
156
|
+
description
|
|
157
|
+
})).data;
|
|
158
|
+
}
|
|
159
|
+
async inviteToTeam(teamId, email) {
|
|
160
|
+
await this.client.post(`/api/teams/${teamId}/invite`, { email });
|
|
161
|
+
}
|
|
162
|
+
async getSubagents(teamId) {
|
|
163
|
+
return (await this.client.get(`/api/teams/${teamId}/subagents`)).data;
|
|
164
|
+
}
|
|
165
|
+
async createSubagent(teamId, data) {
|
|
166
|
+
return (await this.client.post(`/api/teams/${teamId}/subagents`, data)).data;
|
|
167
|
+
}
|
|
168
|
+
async updateSubagent(id, data) {
|
|
169
|
+
return (await this.client.put(`/api/subagents/${id}`, data)).data;
|
|
170
|
+
}
|
|
171
|
+
async deleteSubagent(id) {
|
|
172
|
+
await this.client.delete(`/api/subagents/${id}`);
|
|
173
|
+
}
|
|
174
|
+
async getMcpServers(teamId) {
|
|
175
|
+
return (await this.client.get(`/api/teams/${teamId}/mcp-servers`)).data;
|
|
176
|
+
}
|
|
177
|
+
async createMcpServer(teamId, data) {
|
|
178
|
+
return (await this.client.post(`/api/teams/${teamId}/mcp-servers`, data)).data;
|
|
179
|
+
}
|
|
180
|
+
async updateMcpServer(id, data) {
|
|
181
|
+
return (await this.client.put(`/api/mcp-servers/${id}`, data)).data;
|
|
182
|
+
}
|
|
183
|
+
async deleteMcpServer(id) {
|
|
184
|
+
await this.client.delete(`/api/mcp-servers/${id}`);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
let apiClient = null;
|
|
188
|
+
function getApiClient() {
|
|
189
|
+
if (!apiClient) apiClient = new ApiClient();
|
|
190
|
+
return apiClient;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/commands/auth.ts
|
|
195
|
+
async function signupCommand() {
|
|
196
|
+
console.log(chalk.bold("\n🚀 Create Your Jive Account\n"));
|
|
197
|
+
const response = await prompts([
|
|
198
|
+
{
|
|
199
|
+
type: "text",
|
|
200
|
+
name: "email",
|
|
201
|
+
message: "Email address:",
|
|
202
|
+
validate: (value) => {
|
|
203
|
+
if (!value) return "Email is required";
|
|
204
|
+
if (!value.includes("@")) return "Please enter a valid email";
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
type: "password",
|
|
210
|
+
name: "password",
|
|
211
|
+
message: "Password (min 8 characters):",
|
|
212
|
+
validate: (value) => {
|
|
213
|
+
if (!value) return "Password is required";
|
|
214
|
+
if (value.length < 8) return "Password must be at least 8 characters";
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
type: "password",
|
|
220
|
+
name: "confirmPassword",
|
|
221
|
+
message: "Confirm password:",
|
|
222
|
+
validate: (value, prev) => {
|
|
223
|
+
if (value !== prev.password) return "Passwords do not match";
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]);
|
|
228
|
+
if (!response.email || !response.password) {
|
|
229
|
+
console.log(chalk.yellow("\n❌ Signup cancelled"));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const spinner = ora("Creating account...").start();
|
|
233
|
+
try {
|
|
234
|
+
const result = await getApiClient().signup(response.email, response.password);
|
|
235
|
+
spinner.succeed(chalk.green("Account created successfully!"));
|
|
236
|
+
console.log(chalk.cyan(`\n📧 Logged in as: ${result.email}`));
|
|
237
|
+
console.log(chalk.gray(`\n${result.message || "Next, create or join a team to get started."}`));
|
|
238
|
+
console.log(chalk.white("\nRun"), chalk.cyan("jive team create"), chalk.white("to create a team"));
|
|
239
|
+
console.log(chalk.white("Or"), chalk.cyan("jive init"), chalk.white("to initialize your project with an existing team\n"));
|
|
240
|
+
await saveCredentials({
|
|
241
|
+
token: result.token,
|
|
242
|
+
userId: result.userId,
|
|
243
|
+
email: result.email
|
|
244
|
+
});
|
|
245
|
+
} catch (error) {
|
|
246
|
+
spinner.fail(chalk.red("Signup failed"));
|
|
247
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
248
|
+
if (error.details) console.error(chalk.gray(JSON.stringify(error.details, null, 2)));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function loginCommand() {
|
|
253
|
+
console.log(chalk.bold("\n👋 Login to Jive\n"));
|
|
254
|
+
const response = await prompts([{
|
|
255
|
+
type: "text",
|
|
256
|
+
name: "email",
|
|
257
|
+
message: "Email address:",
|
|
258
|
+
validate: (value) => value ? true : "Email is required"
|
|
259
|
+
}, {
|
|
260
|
+
type: "password",
|
|
261
|
+
name: "password",
|
|
262
|
+
message: "Password:",
|
|
263
|
+
validate: (value) => value ? true : "Password is required"
|
|
264
|
+
}]);
|
|
265
|
+
if (!response.email || !response.password) {
|
|
266
|
+
console.log(chalk.yellow("\n❌ Login cancelled"));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const spinner = ora("Logging in...").start();
|
|
270
|
+
try {
|
|
271
|
+
const result = await getApiClient().login(response.email, response.password);
|
|
272
|
+
spinner.succeed(chalk.green("Logged in successfully!"));
|
|
273
|
+
console.log(chalk.cyan(`\n📧 ${result.email}`));
|
|
274
|
+
console.log(chalk.green("✓ API key configured\n"));
|
|
275
|
+
await saveCredentials({
|
|
276
|
+
token: result.token,
|
|
277
|
+
userId: result.userId,
|
|
278
|
+
email: result.email
|
|
279
|
+
});
|
|
280
|
+
console.log(chalk.white("Next steps:"));
|
|
281
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive team create"), chalk.white("to create a team"));
|
|
282
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive init"), chalk.white("to initialize your project with an existing team\n"));
|
|
283
|
+
} catch (error) {
|
|
284
|
+
spinner.fail(chalk.red("Login failed"));
|
|
285
|
+
console.error(chalk.red(`\n❌ ${error.message || "Invalid email or password"}`));
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async function logoutCommand() {
|
|
290
|
+
const credentials = await getCredentials();
|
|
291
|
+
if (!credentials) {
|
|
292
|
+
console.log(chalk.yellow("\n⚠️ You are not logged in"));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!(await prompts({
|
|
296
|
+
type: "confirm",
|
|
297
|
+
name: "confirm",
|
|
298
|
+
message: `Logout from ${credentials.email}?`,
|
|
299
|
+
initial: true
|
|
300
|
+
})).confirm) {
|
|
301
|
+
console.log(chalk.yellow("\n❌ Logout cancelled"));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
await clearCredentials();
|
|
305
|
+
console.log(chalk.green("\n✅ Logged out successfully"));
|
|
306
|
+
console.log(chalk.gray(`Run ${chalk.cyan("jive login")} to login again\n`));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/commands/team.ts
|
|
311
|
+
const teamCommands = {
|
|
312
|
+
async create() {
|
|
313
|
+
await requireAuth();
|
|
314
|
+
console.log(chalk.bold("\n🎉 Create a New Team\n"));
|
|
315
|
+
const response = await prompts([{
|
|
316
|
+
type: "text",
|
|
317
|
+
name: "name",
|
|
318
|
+
message: "Team name:",
|
|
319
|
+
validate: (value) => {
|
|
320
|
+
if (!value) return "Team name is required";
|
|
321
|
+
if (value.length < 3) return "Team name must be at least 3 characters";
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}, {
|
|
325
|
+
type: "text",
|
|
326
|
+
name: "description",
|
|
327
|
+
message: "Description (optional):"
|
|
328
|
+
}]);
|
|
329
|
+
if (!response.name) {
|
|
330
|
+
console.log(chalk.yellow("\n❌ Team creation cancelled"));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const spinner = ora("Creating team...").start();
|
|
334
|
+
try {
|
|
335
|
+
const result = await getApiClient().createTeam(response.name, response.description);
|
|
336
|
+
spinner.succeed(chalk.green("Team created successfully!"));
|
|
337
|
+
console.log(chalk.cyan(`\n✨ Team: ${result.name}`));
|
|
338
|
+
console.log(chalk.gray(` ID: ${result.id}`));
|
|
339
|
+
if (result.token) {
|
|
340
|
+
const credentials = await import("./config-BP7v03In.mjs").then((m) => m.getCredentials());
|
|
341
|
+
if (credentials) await saveCredentials({
|
|
342
|
+
...credentials,
|
|
343
|
+
token: result.token
|
|
344
|
+
});
|
|
345
|
+
console.log(chalk.green("\n✓ New API key configured"));
|
|
346
|
+
}
|
|
347
|
+
console.log(chalk.white("\nNext step:"));
|
|
348
|
+
console.log(chalk.white(" Run"), chalk.cyan("jive init"), chalk.white("in your project to get started\n"));
|
|
349
|
+
} catch (error) {
|
|
350
|
+
spinner.fail(chalk.red("Failed to create team"));
|
|
351
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
async list() {
|
|
356
|
+
await requireAuth();
|
|
357
|
+
const spinner = ora("Fetching teams...").start();
|
|
358
|
+
try {
|
|
359
|
+
const result = await getApiClient().getTeams();
|
|
360
|
+
spinner.stop();
|
|
361
|
+
if (result.teams.length === 0) {
|
|
362
|
+
console.log(chalk.yellow("\n⚠️ You are not a member of any teams"));
|
|
363
|
+
console.log(chalk.white("Run"), chalk.cyan("jive team create"), chalk.white("to create a team\n"));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
console.log(chalk.bold("\n📋 Your Teams:\n"));
|
|
367
|
+
let activeTeamId = null;
|
|
368
|
+
try {
|
|
369
|
+
activeTeamId = await getActiveTeamId();
|
|
370
|
+
} catch {}
|
|
371
|
+
for (const team$1 of result.teams) {
|
|
372
|
+
const isActive = activeTeamId === team$1.id.toString();
|
|
373
|
+
const marker = isActive ? chalk.green("* ") : " ";
|
|
374
|
+
const name = isActive ? chalk.green.bold(team$1.name) : chalk.white(team$1.name);
|
|
375
|
+
const suffix = isActive ? chalk.gray(" (active)") : "";
|
|
376
|
+
console.log(`${marker}${name}${suffix}`);
|
|
377
|
+
console.log(chalk.gray(` ID: ${team$1.id}`));
|
|
378
|
+
}
|
|
379
|
+
console.log();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
spinner.fail(chalk.red("Failed to fetch teams"));
|
|
382
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
async switch() {
|
|
387
|
+
await requireAuth();
|
|
388
|
+
const spinner = ora("Fetching teams...").start();
|
|
389
|
+
try {
|
|
390
|
+
const result = await getApiClient().getTeams();
|
|
391
|
+
spinner.stop();
|
|
392
|
+
if (result.teams.length === 0) {
|
|
393
|
+
console.log(chalk.yellow("\n⚠️ You are not a member of any teams"));
|
|
394
|
+
console.log(chalk.white("Run"), chalk.cyan("jive team create"), chalk.white("to create a team\n"));
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const response = await prompts({
|
|
398
|
+
type: "select",
|
|
399
|
+
name: "teamId",
|
|
400
|
+
message: "Select a team:",
|
|
401
|
+
choices: result.teams.map((team$1) => ({
|
|
402
|
+
title: team$1.name,
|
|
403
|
+
value: team$1.id.toString(),
|
|
404
|
+
description: `ID: ${team$1.id}`
|
|
405
|
+
}))
|
|
406
|
+
});
|
|
407
|
+
if (!response.teamId) {
|
|
408
|
+
console.log(chalk.yellow("\n❌ Team switch cancelled"));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const switchSpinner = ora("Switching team...").start();
|
|
412
|
+
if (await getProjectConfig()) await updateProjectConfig({
|
|
413
|
+
activeTeamId: response.teamId,
|
|
414
|
+
teamId: response.teamId
|
|
415
|
+
});
|
|
416
|
+
switchSpinner.succeed(chalk.green("Team switched successfully!"));
|
|
417
|
+
const selectedTeam = result.teams.find((t) => t.id.toString() === response.teamId);
|
|
418
|
+
console.log(chalk.cyan(`\n✨ Active team: ${selectedTeam?.name}`));
|
|
419
|
+
console.log(chalk.white("✅ Your existing API key works for all teams\n"));
|
|
420
|
+
} catch (error) {
|
|
421
|
+
spinner.fail(chalk.red("Failed to switch team"));
|
|
422
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
423
|
+
process.exit(1);
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
async invite(email) {
|
|
427
|
+
await requireAuth();
|
|
428
|
+
const spinner = ora(`Inviting ${email}...`).start();
|
|
429
|
+
try {
|
|
430
|
+
const activeTeamId = await getActiveTeamId();
|
|
431
|
+
await getApiClient().inviteToTeam(activeTeamId, email);
|
|
432
|
+
spinner.succeed(chalk.green(`Invitation sent to ${email}!`));
|
|
433
|
+
console.log(chalk.gray("\nThey will receive an email with instructions to join the team\n"));
|
|
434
|
+
} catch (error) {
|
|
435
|
+
spinner.fail(chalk.red("Failed to send invitation"));
|
|
436
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
//#region src/lib/fs-utils.ts
|
|
444
|
+
const MCP_JSON_PATH = path.join(process.cwd(), ".mcp.json");
|
|
445
|
+
const CLAUDE_AGENTS_DIR = path.join(process.cwd(), ".claude", "agents");
|
|
446
|
+
const GITIGNORE_PATH = path.join(process.cwd(), ".gitignore");
|
|
447
|
+
async function getMcpConfig() {
|
|
448
|
+
try {
|
|
449
|
+
const data = await fs.readFile(MCP_JSON_PATH, "utf-8");
|
|
450
|
+
return JSON.parse(data);
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (error.code === "ENOENT") return null;
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
async function saveMcpConfig(config) {
|
|
457
|
+
await fs.writeFile(MCP_JSON_PATH, JSON.stringify(config, null, 2));
|
|
458
|
+
}
|
|
459
|
+
async function addMcpServer(name, serverConfig) {
|
|
460
|
+
const config = await getMcpConfig() || { mcpServers: {} };
|
|
461
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
462
|
+
config.mcpServers[name] = serverConfig;
|
|
463
|
+
await saveMcpConfig(config);
|
|
464
|
+
}
|
|
465
|
+
async function removeMcpServer(name) {
|
|
466
|
+
const config = await getMcpConfig();
|
|
467
|
+
if (config?.mcpServers) {
|
|
468
|
+
delete config.mcpServers[name];
|
|
469
|
+
await saveMcpConfig(config);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
async function backupMcpConfig() {
|
|
473
|
+
try {
|
|
474
|
+
const data = await fs.readFile(MCP_JSON_PATH, "utf-8");
|
|
475
|
+
const backupPath = `${MCP_JSON_PATH}.backup`;
|
|
476
|
+
await fs.writeFile(backupPath, data);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
if (error.code !== "ENOENT") throw error;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
async function ensureAgentsDir() {
|
|
482
|
+
try {
|
|
483
|
+
await fs.access(CLAUDE_AGENTS_DIR);
|
|
484
|
+
} catch {
|
|
485
|
+
await fs.mkdir(CLAUDE_AGENTS_DIR, { recursive: true });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
async function getSubagentFiles() {
|
|
489
|
+
try {
|
|
490
|
+
await fs.access(CLAUDE_AGENTS_DIR);
|
|
491
|
+
const mdFiles = (await fs.readdir(CLAUDE_AGENTS_DIR)).filter((f) => f.endsWith(".md"));
|
|
492
|
+
const subagents$1 = [];
|
|
493
|
+
for (const file of mdFiles) {
|
|
494
|
+
const filePath = path.join(CLAUDE_AGENTS_DIR, file);
|
|
495
|
+
const parsed = matter(await fs.readFile(filePath, "utf-8"));
|
|
496
|
+
subagents$1.push({
|
|
497
|
+
name: parsed.data.name || path.basename(file, ".md"),
|
|
498
|
+
description: parsed.data.description || "",
|
|
499
|
+
prompt: parsed.content.trim(),
|
|
500
|
+
jiveId: parsed.data["jive-id"],
|
|
501
|
+
filePath
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return subagents$1;
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (error.code === "ENOENT") return [];
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function saveSubagentFile(subagent) {
|
|
511
|
+
await ensureAgentsDir();
|
|
512
|
+
const frontmatter = {
|
|
513
|
+
name: subagent.name,
|
|
514
|
+
description: subagent.description
|
|
515
|
+
};
|
|
516
|
+
if (subagent.jiveId) frontmatter["jive-id"] = subagent.jiveId;
|
|
517
|
+
const content = matter.stringify(subagent.prompt, frontmatter);
|
|
518
|
+
const filePath = path.join(CLAUDE_AGENTS_DIR, `${subagent.name}.md`);
|
|
519
|
+
await fs.writeFile(filePath, content);
|
|
520
|
+
return filePath;
|
|
521
|
+
}
|
|
522
|
+
async function deleteSubagentFile(name) {
|
|
523
|
+
const filePath = path.join(CLAUDE_AGENTS_DIR, `${name}.md`);
|
|
524
|
+
try {
|
|
525
|
+
await fs.unlink(filePath);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (error.code !== "ENOENT") throw error;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async function updateSubagentJiveId(name, jiveId) {
|
|
531
|
+
const filePath = path.join(CLAUDE_AGENTS_DIR, `${name}.md`);
|
|
532
|
+
const parsed = matter(await fs.readFile(filePath, "utf-8"));
|
|
533
|
+
parsed.data["jive-id"] = jiveId;
|
|
534
|
+
const updated = matter.stringify(parsed.content, parsed.data);
|
|
535
|
+
await fs.writeFile(filePath, updated);
|
|
536
|
+
}
|
|
537
|
+
async function createSubagentRunner() {
|
|
538
|
+
await ensureAgentsDir();
|
|
539
|
+
const runnerContent = `---
|
|
540
|
+
name: subagent-runner
|
|
541
|
+
description: Execute dynamically loaded subagent prompts
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
You are a subagent runner. When invoked, you will receive a subagent prompt
|
|
545
|
+
from the Jive platform. Execute the instructions in that prompt exactly.
|
|
546
|
+
|
|
547
|
+
The prompt you should execute will be provided via the Task tool's prompt parameter.
|
|
548
|
+
`;
|
|
549
|
+
const filePath = path.join(CLAUDE_AGENTS_DIR, "subagent-runner.md");
|
|
550
|
+
await fs.writeFile(filePath, runnerContent);
|
|
551
|
+
}
|
|
552
|
+
async function addToGitignore(pattern) {
|
|
553
|
+
try {
|
|
554
|
+
let content = "";
|
|
555
|
+
try {
|
|
556
|
+
content = await fs.readFile(GITIGNORE_PATH, "utf-8");
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (error.code !== "ENOENT") throw error;
|
|
559
|
+
}
|
|
560
|
+
if (content.split("\n").some((line) => line.trim() === pattern)) return;
|
|
561
|
+
const newContent = content.trim() + "\n" + pattern + "\n";
|
|
562
|
+
await fs.writeFile(GITIGNORE_PATH, newContent);
|
|
563
|
+
} catch (error) {
|
|
564
|
+
console.warn(`Warning: Could not update .gitignore: ${error}`);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const CLAUDE_PLUGINS_DIR = path.join(process.cwd(), ".claude", "plugins");
|
|
568
|
+
const JIVE_CONFIG_DIR = path.join(process.cwd(), ".jive");
|
|
569
|
+
const JIVE_CONFIG_PATH = path.join(JIVE_CONFIG_DIR, "config.json");
|
|
570
|
+
async function installTelemetryPlugin(apiKey, apiUrl) {
|
|
571
|
+
await fs.mkdir(CLAUDE_PLUGINS_DIR, { recursive: true });
|
|
572
|
+
const pluginSourceDir = path.join(process.cwd(), "plugins", "jive-mcp-telemetry");
|
|
573
|
+
const pluginDestDir = path.join(CLAUDE_PLUGINS_DIR, "jive-mcp-telemetry");
|
|
574
|
+
let pluginExists = false;
|
|
575
|
+
try {
|
|
576
|
+
await fs.access(pluginSourceDir);
|
|
577
|
+
pluginExists = true;
|
|
578
|
+
} catch {
|
|
579
|
+
pluginExists = false;
|
|
580
|
+
}
|
|
581
|
+
if (pluginExists) await copyDirectory(pluginSourceDir, pluginDestDir);
|
|
582
|
+
else await createMinimalPlugin(pluginDestDir);
|
|
583
|
+
await fs.mkdir(JIVE_CONFIG_DIR, { recursive: true });
|
|
584
|
+
let config = {};
|
|
585
|
+
try {
|
|
586
|
+
const existingConfig = await fs.readFile(JIVE_CONFIG_PATH, "utf-8");
|
|
587
|
+
config = JSON.parse(existingConfig);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (error.code !== "ENOENT") throw error;
|
|
590
|
+
}
|
|
591
|
+
config.apiUrl = apiUrl;
|
|
592
|
+
config.apiKey = apiKey;
|
|
593
|
+
await fs.writeFile(JIVE_CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
594
|
+
}
|
|
595
|
+
async function copyDirectory(src, dest) {
|
|
596
|
+
await fs.mkdir(dest, { recursive: true });
|
|
597
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
598
|
+
for (const entry of entries) {
|
|
599
|
+
const srcPath = path.join(src, entry.name);
|
|
600
|
+
const destPath = path.join(dest, entry.name);
|
|
601
|
+
if (entry.isDirectory()) await copyDirectory(srcPath, destPath);
|
|
602
|
+
else await fs.copyFile(srcPath, destPath);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async function createMinimalPlugin(pluginDir) {
|
|
606
|
+
await fs.mkdir(path.join(pluginDir, ".claude-plugin"), { recursive: true });
|
|
607
|
+
await fs.mkdir(path.join(pluginDir, "hooks"), { recursive: true });
|
|
608
|
+
await fs.mkdir(path.join(pluginDir, "scripts"), { recursive: true });
|
|
609
|
+
await fs.writeFile(path.join(pluginDir, ".claude-plugin", "plugin.json"), JSON.stringify({
|
|
610
|
+
name: "jive-mcp-telemetry",
|
|
611
|
+
version: "1.0.0",
|
|
612
|
+
description: "Captures subagent execution traces and tool calls for Jive",
|
|
613
|
+
author: {
|
|
614
|
+
name: "Jive",
|
|
615
|
+
url: "https://github.com/jive-mcp"
|
|
616
|
+
},
|
|
617
|
+
license: "MIT",
|
|
618
|
+
keywords: [
|
|
619
|
+
"subagents",
|
|
620
|
+
"telemetry",
|
|
621
|
+
"monitoring",
|
|
622
|
+
"debugging"
|
|
623
|
+
],
|
|
624
|
+
hooks: "../hooks/hooks.json"
|
|
625
|
+
}, null, 2));
|
|
626
|
+
await fs.writeFile(path.join(pluginDir, "hooks", "hooks.json"), JSON.stringify({ hooks: { PostToolUse: [{
|
|
627
|
+
matcher: ".*",
|
|
628
|
+
hooks: [{
|
|
629
|
+
type: "command",
|
|
630
|
+
command: "node ${CLAUDE_PLUGIN_ROOT}/scripts/capture-tool-use.js"
|
|
631
|
+
}]
|
|
632
|
+
}] } }, null, 2));
|
|
633
|
+
await fs.writeFile(path.join(pluginDir, "scripts", "capture-tool-use.js"), `#!/usr/bin/env node
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Jive Telemetry Capture Script
|
|
637
|
+
*
|
|
638
|
+
* This script runs as a PostToolUse hook to capture tool calls made during
|
|
639
|
+
* subagent execution and send them to the Jive API for tracking.
|
|
640
|
+
*/
|
|
641
|
+
|
|
642
|
+
const fs = require('fs');
|
|
643
|
+
const path = require('path');
|
|
644
|
+
const https = require('https');
|
|
645
|
+
const http = require('http');
|
|
646
|
+
|
|
647
|
+
// Find the project's .jive/config.json by searching upward from cwd
|
|
648
|
+
function findProjectConfig() {
|
|
649
|
+
let dir = process.cwd();
|
|
650
|
+
while (dir !== path.parse(dir).root) {
|
|
651
|
+
const configPath = path.join(dir, '.jive', 'config.json');
|
|
652
|
+
if (fs.existsSync(configPath)) {
|
|
653
|
+
return configPath;
|
|
654
|
+
}
|
|
655
|
+
dir = path.dirname(dir);
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const CONFIG_FILE = findProjectConfig();
|
|
661
|
+
const STATE_DIR = path.join(process.env.HOME || process.env.USERPROFILE, '.jive-mcp', 'telemetry');
|
|
662
|
+
const LOG_FILE = path.join(STATE_DIR, 'telemetry.log');
|
|
663
|
+
|
|
664
|
+
// Ensure state directory exists
|
|
665
|
+
try {
|
|
666
|
+
if (!fs.existsSync(STATE_DIR)) {
|
|
667
|
+
fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
668
|
+
}
|
|
669
|
+
} catch (err) {
|
|
670
|
+
process.exit(0);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function log(message) {
|
|
674
|
+
try {
|
|
675
|
+
const timestamp = new Date().toISOString();
|
|
676
|
+
fs.appendFileSync(LOG_FILE, \`[\${timestamp}] \${message}\\n\`);
|
|
677
|
+
} catch (err) {
|
|
678
|
+
// Ignore logging errors
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function loadConfig() {
|
|
683
|
+
try {
|
|
684
|
+
if (!CONFIG_FILE || !fs.existsSync(CONFIG_FILE)) {
|
|
685
|
+
log('Config file not found, telemetry disabled');
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
689
|
+
return config;
|
|
690
|
+
} catch (err) {
|
|
691
|
+
log(\`Error loading config: \${err.message}\`);
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function getCurrentInvocation() {
|
|
697
|
+
try {
|
|
698
|
+
const stateFile = path.join(STATE_DIR, 'current-invocation.json');
|
|
699
|
+
if (!fs.existsSync(stateFile)) {
|
|
700
|
+
return null;
|
|
701
|
+
}
|
|
702
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
703
|
+
const age = Date.now() - state.timestamp;
|
|
704
|
+
if (age > 3600000) {
|
|
705
|
+
fs.unlinkSync(stateFile);
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
708
|
+
return state;
|
|
709
|
+
} catch (err) {
|
|
710
|
+
log(\`Error reading invocation state: \${err.message}\`);
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
function getNextSequenceNumber(invocationId) {
|
|
716
|
+
const seqFile = path.join(STATE_DIR, \`seq-\${invocationId}.txt\`);
|
|
717
|
+
try {
|
|
718
|
+
let seq = 1;
|
|
719
|
+
if (fs.existsSync(seqFile)) {
|
|
720
|
+
seq = parseInt(fs.readFileSync(seqFile, 'utf8')) + 1;
|
|
721
|
+
}
|
|
722
|
+
fs.writeFileSync(seqFile, seq.toString());
|
|
723
|
+
return seq;
|
|
724
|
+
} catch (err) {
|
|
725
|
+
log(\`Error managing sequence number: \${err.message}\`);
|
|
726
|
+
return 1;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// Simplified version - full implementation in the source repository
|
|
731
|
+
log('Telemetry hook triggered (minimal version)');
|
|
732
|
+
process.exit(0);
|
|
733
|
+
`);
|
|
734
|
+
await fs.chmod(path.join(pluginDir, "scripts", "capture-tool-use.js"), 493);
|
|
735
|
+
}
|
|
736
|
+
async function getPluginStatus() {
|
|
737
|
+
const pluginPath = path.join(CLAUDE_PLUGINS_DIR, "jive-mcp-telemetry");
|
|
738
|
+
const configPath = JIVE_CONFIG_PATH;
|
|
739
|
+
let installed = false;
|
|
740
|
+
let configured = false;
|
|
741
|
+
try {
|
|
742
|
+
await fs.access(pluginPath);
|
|
743
|
+
installed = true;
|
|
744
|
+
} catch {
|
|
745
|
+
installed = false;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
await fs.access(configPath);
|
|
749
|
+
configured = true;
|
|
750
|
+
} catch {
|
|
751
|
+
configured = false;
|
|
752
|
+
}
|
|
753
|
+
return {
|
|
754
|
+
installed,
|
|
755
|
+
configured,
|
|
756
|
+
pluginPath: installed ? pluginPath : void 0,
|
|
757
|
+
configPath: configured ? configPath : void 0
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
//#endregion
|
|
762
|
+
//#region src/commands/init.ts
|
|
763
|
+
async function initCommand() {
|
|
764
|
+
console.log(chalk.bold("\n🚀 Initialize Jive\n"));
|
|
765
|
+
if (await isProjectInitialized()) {
|
|
766
|
+
if (!(await prompts({
|
|
767
|
+
type: "confirm",
|
|
768
|
+
name: "continue",
|
|
769
|
+
message: "Project is already initialized. Continue anyway?",
|
|
770
|
+
initial: false
|
|
771
|
+
})).continue) {
|
|
772
|
+
console.log(chalk.yellow("\n❌ Initialization cancelled"));
|
|
773
|
+
return;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
await requireAuth();
|
|
777
|
+
let teamId;
|
|
778
|
+
const apiClient$1 = getApiClient();
|
|
779
|
+
const credentials = await getCredentials();
|
|
780
|
+
if (!credentials) {
|
|
781
|
+
console.error(chalk.red("Not authenticated"));
|
|
782
|
+
process.exit(1);
|
|
783
|
+
}
|
|
784
|
+
const teamsResult = await apiClient$1.getTeams();
|
|
785
|
+
if (teamsResult.teams.length === 0) {
|
|
786
|
+
console.log(chalk.yellow("\n⚠️ You are not a member of any teams"));
|
|
787
|
+
console.log(chalk.white("Run"), chalk.cyan("jive team create"), chalk.white("to create a team first\n"));
|
|
788
|
+
process.exit(1);
|
|
789
|
+
}
|
|
790
|
+
if (teamsResult.teams.length === 1) {
|
|
791
|
+
teamId = teamsResult.teams[0].id.toString();
|
|
792
|
+
console.log(chalk.white(`Using team: ${chalk.cyan(teamsResult.teams[0].name)}\n`));
|
|
793
|
+
} else {
|
|
794
|
+
const response = await prompts({
|
|
795
|
+
type: "select",
|
|
796
|
+
name: "teamId",
|
|
797
|
+
message: "Select a team to initialize for:",
|
|
798
|
+
choices: teamsResult.teams.map((team$1) => ({
|
|
799
|
+
title: team$1.name,
|
|
800
|
+
value: team$1.id.toString(),
|
|
801
|
+
description: `ID: ${team$1.id}`
|
|
802
|
+
}))
|
|
803
|
+
});
|
|
804
|
+
if (!response.teamId) {
|
|
805
|
+
console.log(chalk.yellow("\n❌ Initialization cancelled"));
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
teamId = response.teamId;
|
|
809
|
+
}
|
|
810
|
+
const spinner = ora("Initializing project...").start();
|
|
811
|
+
try {
|
|
812
|
+
spinner.text = "Scanning for existing MCP servers...";
|
|
813
|
+
const mcpConfig = await getMcpConfig();
|
|
814
|
+
let uploadedServers = 0;
|
|
815
|
+
if (mcpConfig?.mcpServers) {
|
|
816
|
+
const serverNames = Object.keys(mcpConfig.mcpServers).filter((name) => name !== "jive-mcp");
|
|
817
|
+
if (serverNames.length > 0) {
|
|
818
|
+
spinner.text = `Found ${serverNames.length} MCP server(s), uploading...`;
|
|
819
|
+
for (const name of serverNames) {
|
|
820
|
+
const serverConfig = mcpConfig.mcpServers[name];
|
|
821
|
+
try {
|
|
822
|
+
await apiClient$1.createMcpServer(teamId, {
|
|
823
|
+
name,
|
|
824
|
+
description: `Imported from local .mcp.json`,
|
|
825
|
+
config: serverConfig
|
|
826
|
+
});
|
|
827
|
+
uploadedServers++;
|
|
828
|
+
} catch (error) {
|
|
829
|
+
console.warn(chalk.gray(`\nWarning: Could not upload MCP server "${name}": ${error.message}`));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
spinner.text = "Scanning for existing subagents...";
|
|
835
|
+
const subagentFiles = await getSubagentFiles();
|
|
836
|
+
let uploadedSubagents = 0;
|
|
837
|
+
if (subagentFiles.length > 0) {
|
|
838
|
+
spinner.text = `Found ${subagentFiles.length} subagent(s), uploading...`;
|
|
839
|
+
for (const subagent of subagentFiles) {
|
|
840
|
+
if (subagent.name === "subagent-runner") continue;
|
|
841
|
+
try {
|
|
842
|
+
const created = await apiClient$1.createSubagent(teamId, {
|
|
843
|
+
name: subagent.name,
|
|
844
|
+
description: subagent.description,
|
|
845
|
+
prompt: subagent.prompt
|
|
846
|
+
});
|
|
847
|
+
await updateSubagentJiveId(subagent.name, created.id);
|
|
848
|
+
uploadedSubagents++;
|
|
849
|
+
} catch (error) {
|
|
850
|
+
console.warn(chalk.gray(`\nWarning: Could not upload subagent "${subagent.name}": ${error.message}`));
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
spinner.text = "Adding Jive server to .mcp.json...";
|
|
855
|
+
await addMcpServer("jive-mcp", {
|
|
856
|
+
command: "npx",
|
|
857
|
+
args: ["-y", "@jive/mcp-server"],
|
|
858
|
+
env: {
|
|
859
|
+
JIVE_API_URL: process.env.JIVE_API_URL || "http://localhost:5173",
|
|
860
|
+
JIVE_TEAM_ID: teamId,
|
|
861
|
+
JIVE_AUTH_TOKEN: credentials.token
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
spinner.text = "Creating subagent-runner...";
|
|
865
|
+
await createSubagentRunner();
|
|
866
|
+
spinner.text = "Updating .gitignore...";
|
|
867
|
+
await addToGitignore(".jive/config.json");
|
|
868
|
+
await addToGitignore(".jive/sync.json");
|
|
869
|
+
await addToGitignore(".claude/plugins/");
|
|
870
|
+
spinner.text = "Creating project configuration...";
|
|
871
|
+
await saveProjectConfig({
|
|
872
|
+
teamId,
|
|
873
|
+
activeTeamId: teamId,
|
|
874
|
+
lastSync: (/* @__PURE__ */ new Date()).toISOString()
|
|
875
|
+
});
|
|
876
|
+
spinner.text = "Installing Claude Code telemetry plugin...";
|
|
877
|
+
const apiUrl = process.env.JIVE_API_URL || "http://localhost:5173";
|
|
878
|
+
try {
|
|
879
|
+
await installTelemetryPlugin(credentials.token, apiUrl);
|
|
880
|
+
spinner.text = "Telemetry plugin installed";
|
|
881
|
+
} catch (error) {
|
|
882
|
+
console.warn(chalk.yellow(`\nWarning: Could not install telemetry plugin: ${error.message}`));
|
|
883
|
+
}
|
|
884
|
+
spinner.succeed(chalk.green("Project initialized successfully!"));
|
|
885
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
886
|
+
if (uploadedServers > 0) console.log(chalk.green(` ✓ Uploaded ${uploadedServers} MCP server(s) to team`));
|
|
887
|
+
if (uploadedSubagents > 0) console.log(chalk.green(` ✓ Uploaded ${uploadedSubagents} subagent(s) to team`));
|
|
888
|
+
console.log(chalk.green(" ✓ Added @jive/mcp server to .mcp.json"));
|
|
889
|
+
console.log(chalk.green(" ✓ Created .claude/agents/subagent-runner.md"));
|
|
890
|
+
console.log(chalk.green(" ✓ Created .jive/config.json"));
|
|
891
|
+
console.log(chalk.green(" ✓ Updated .gitignore"));
|
|
892
|
+
const pluginStatus = await getPluginStatus();
|
|
893
|
+
if (pluginStatus.installed) console.log(chalk.green(" ✓ Installed telemetry plugin to .claude/plugins/"));
|
|
894
|
+
console.log(chalk.bold("\n✨ Next Steps:\n"));
|
|
895
|
+
console.log(chalk.white(" • Restart Claude Code to load the Jive server and telemetry plugin"));
|
|
896
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive sync"), chalk.white("to sync resources"));
|
|
897
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive status"), chalk.white("to check sync status"));
|
|
898
|
+
if (pluginStatus.installed) {
|
|
899
|
+
console.log(chalk.bold("\n🔍 Subagent Visibility:\n"));
|
|
900
|
+
console.log(chalk.white(" • The telemetry plugin will track subagent executions"));
|
|
901
|
+
console.log(chalk.white(" • View execution traces in the web UI (View Executions button)"));
|
|
902
|
+
console.log(chalk.white(" • Generate an API key in the web UI for full telemetry support"));
|
|
903
|
+
console.log(chalk.gray(" (Settings > API Keys > Create New Key)\n"));
|
|
904
|
+
} else {
|
|
905
|
+
console.log(chalk.yellow("\n⚠️ Telemetry plugin installation failed"));
|
|
906
|
+
console.log(chalk.white(" • Subagent execution tracking will not work"));
|
|
907
|
+
console.log(chalk.white(" • See SUBAGENT_VISIBILITY.md for manual installation\n"));
|
|
908
|
+
}
|
|
909
|
+
} catch (error) {
|
|
910
|
+
spinner.fail(chalk.red("Initialization failed"));
|
|
911
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
912
|
+
if (error.stack) console.error(chalk.gray(error.stack));
|
|
913
|
+
process.exit(1);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
//#endregion
|
|
918
|
+
//#region src/commands/detach.ts
|
|
919
|
+
async function detachCommand() {
|
|
920
|
+
console.log(chalk.bold("\n🔌 Detach from Jive\n"));
|
|
921
|
+
console.log(chalk.white("This will pull all your content from the platform and remove Jive integrations.\n"));
|
|
922
|
+
if (!(await prompts({
|
|
923
|
+
type: "confirm",
|
|
924
|
+
name: "confirm",
|
|
925
|
+
message: "Are you sure you want to detach from Jive?",
|
|
926
|
+
initial: false
|
|
927
|
+
})).confirm) {
|
|
928
|
+
console.log(chalk.yellow("\n❌ Detach cancelled"));
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
const spinner = ora("Detaching from Jive...").start();
|
|
932
|
+
try {
|
|
933
|
+
let pulledSubagents = 0;
|
|
934
|
+
let pulledMcpServers = 0;
|
|
935
|
+
const projectConfig = await getProjectConfig();
|
|
936
|
+
await requireAuth();
|
|
937
|
+
const apiClient$1 = getApiClient();
|
|
938
|
+
if (projectConfig) {
|
|
939
|
+
const teamId = projectConfig.activeTeamId || projectConfig.teamId;
|
|
940
|
+
spinner.text = "Pulling subagents from platform...";
|
|
941
|
+
try {
|
|
942
|
+
const subagents$1 = await apiClient$1.getSubagents(teamId);
|
|
943
|
+
for (const subagent of subagents$1) {
|
|
944
|
+
await saveSubagentFile({
|
|
945
|
+
name: subagent.name,
|
|
946
|
+
description: subagent.description,
|
|
947
|
+
prompt: subagent.prompt
|
|
948
|
+
});
|
|
949
|
+
pulledSubagents++;
|
|
950
|
+
}
|
|
951
|
+
} catch (error) {
|
|
952
|
+
console.warn(chalk.yellow(`\n⚠️ Could not pull subagents: ${error.message}`));
|
|
953
|
+
}
|
|
954
|
+
spinner.text = "Pulling MCP servers from platform...";
|
|
955
|
+
try {
|
|
956
|
+
const servers = await apiClient$1.getMcpServers(teamId);
|
|
957
|
+
for (const server of servers) {
|
|
958
|
+
if (server.name === "jive-mcp") continue;
|
|
959
|
+
await addMcpServer(server.name, server.config);
|
|
960
|
+
pulledMcpServers++;
|
|
961
|
+
}
|
|
962
|
+
} catch (error) {
|
|
963
|
+
console.warn(chalk.yellow(`\n⚠️ Could not pull MCP servers: ${error.message}`));
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
spinner.text = "Removing Jive server from .mcp.json...";
|
|
967
|
+
if ((await getMcpConfig())?.mcpServers?.["jive-mcp"]) await removeMcpServer("jive-mcp");
|
|
968
|
+
spinner.text = "Removing telemetry plugin...";
|
|
969
|
+
const pluginPath = path.join(process.cwd(), ".claude", "plugins", "jive-mcp-telemetry");
|
|
970
|
+
try {
|
|
971
|
+
await fs.access(pluginPath);
|
|
972
|
+
await fs.rm(pluginPath, {
|
|
973
|
+
recursive: true,
|
|
974
|
+
force: true
|
|
975
|
+
});
|
|
976
|
+
} catch (error) {
|
|
977
|
+
if (error.code !== "ENOENT") throw error;
|
|
978
|
+
}
|
|
979
|
+
spinner.text = "Removing subagent-runner...";
|
|
980
|
+
const runnerPath = path.join(process.cwd(), ".claude", "agents", "subagent-runner.md");
|
|
981
|
+
try {
|
|
982
|
+
await fs.unlink(runnerPath);
|
|
983
|
+
} catch (error) {
|
|
984
|
+
if (error.code !== "ENOENT") throw error;
|
|
985
|
+
}
|
|
986
|
+
spinner.text = "Removing .jive directory...";
|
|
987
|
+
const jivePath = path.join(process.cwd(), ".jive");
|
|
988
|
+
try {
|
|
989
|
+
await fs.rm(jivePath, {
|
|
990
|
+
recursive: true,
|
|
991
|
+
force: true
|
|
992
|
+
});
|
|
993
|
+
} catch (error) {
|
|
994
|
+
if (error.code !== "ENOENT") throw error;
|
|
995
|
+
}
|
|
996
|
+
spinner.text = "Cleaning up .gitignore...";
|
|
997
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
998
|
+
try {
|
|
999
|
+
let content = await fs.readFile(gitignorePath, "utf-8");
|
|
1000
|
+
const linesToRemove = [
|
|
1001
|
+
".jive/config.json",
|
|
1002
|
+
".jive/sync.json",
|
|
1003
|
+
".claude/plugins/"
|
|
1004
|
+
];
|
|
1005
|
+
const lines = content.split("\n");
|
|
1006
|
+
const filteredLines = lines.filter((line) => !linesToRemove.includes(line.trim()));
|
|
1007
|
+
if (filteredLines.length !== lines.length) await fs.writeFile(gitignorePath, filteredLines.join("\n"));
|
|
1008
|
+
} catch (error) {
|
|
1009
|
+
if (error.code !== "ENOENT") console.warn(chalk.gray("\nWarning: Could not update .gitignore"));
|
|
1010
|
+
}
|
|
1011
|
+
spinner.succeed(chalk.green("Successfully detached from Jive!"));
|
|
1012
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
1013
|
+
if (pulledSubagents > 0) console.log(chalk.green(` ✓ Pulled ${pulledSubagents} subagent(s) from platform`));
|
|
1014
|
+
if (pulledMcpServers > 0) console.log(chalk.green(` ✓ Pulled ${pulledMcpServers} MCP server(s) from platform`));
|
|
1015
|
+
console.log(chalk.green(" ✓ Removed Jive server from .mcp.json"));
|
|
1016
|
+
console.log(chalk.green(" ✓ Removed telemetry plugin"));
|
|
1017
|
+
console.log(chalk.green(" ✓ Removed subagent-runner"));
|
|
1018
|
+
console.log(chalk.green(" ✓ Removed .jive directory"));
|
|
1019
|
+
console.log(chalk.green(" ✓ Cleaned up .gitignore"));
|
|
1020
|
+
console.log(chalk.bold("\n✨ Next Steps:\n"));
|
|
1021
|
+
console.log(chalk.white(" • Restart Claude Code to unload Jive integrations"));
|
|
1022
|
+
console.log(chalk.white(" • All your subagents and MCP servers are now local-only"));
|
|
1023
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive init"), chalk.white("anytime to reconnect\n"));
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
spinner.fail(chalk.red("Detach failed"));
|
|
1026
|
+
console.error(chalk.red(`\n❌ ${error.message || "An error occurred"}`));
|
|
1027
|
+
if (error.stack) console.error(chalk.gray(error.stack));
|
|
1028
|
+
process.exit(1);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
//#endregion
|
|
1033
|
+
//#region src/commands/subagents.ts
|
|
1034
|
+
const subagentCommands = {
|
|
1035
|
+
async list() {
|
|
1036
|
+
await requireAuth();
|
|
1037
|
+
const teamId = await getActiveTeamId();
|
|
1038
|
+
const spinner = ora("Fetching subagents...").start();
|
|
1039
|
+
try {
|
|
1040
|
+
const subagents$1 = await getApiClient().getSubagents(teamId);
|
|
1041
|
+
spinner.stop();
|
|
1042
|
+
if (subagents$1.length === 0) {
|
|
1043
|
+
console.log(chalk.yellow("\n⚠️ No subagents found for this team"));
|
|
1044
|
+
console.log(chalk.white("Run"), chalk.cyan("jive subagents create"), chalk.white("to create one\n"));
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
console.log(chalk.bold(`\n📋 Team Subagents (${subagents$1.length}):\n`));
|
|
1048
|
+
for (const subagent of subagents$1) {
|
|
1049
|
+
console.log(chalk.cyan(` ${subagent.name}`));
|
|
1050
|
+
console.log(chalk.gray(` ID: ${subagent.id}`));
|
|
1051
|
+
if (subagent.description) console.log(chalk.white(` ${subagent.description}`));
|
|
1052
|
+
console.log();
|
|
1053
|
+
}
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
spinner.fail(chalk.red("Failed to fetch subagents"));
|
|
1056
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1057
|
+
process.exit(1);
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
async pull(options) {
|
|
1061
|
+
await requireAuth();
|
|
1062
|
+
const teamId = await getActiveTeamId();
|
|
1063
|
+
const spinner = ora("Pulling subagents...").start();
|
|
1064
|
+
try {
|
|
1065
|
+
const remoteSubagents = await getApiClient().getSubagents(teamId);
|
|
1066
|
+
if (remoteSubagents.length === 0) {
|
|
1067
|
+
spinner.info(chalk.yellow("No subagents to pull"));
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const localSubagents = await getSubagentFiles();
|
|
1071
|
+
const localNames = new Set(localSubagents.map((s) => s.name));
|
|
1072
|
+
let pulled = 0;
|
|
1073
|
+
let skipped = 0;
|
|
1074
|
+
for (const subagent of remoteSubagents) {
|
|
1075
|
+
if (localNames.has(subagent.name) && !options.overwrite) {
|
|
1076
|
+
skipped++;
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
await saveSubagentFile({
|
|
1080
|
+
name: subagent.name,
|
|
1081
|
+
description: subagent.description,
|
|
1082
|
+
prompt: subagent.prompt,
|
|
1083
|
+
jiveId: subagent.id
|
|
1084
|
+
});
|
|
1085
|
+
pulled++;
|
|
1086
|
+
}
|
|
1087
|
+
spinner.succeed(chalk.green("Subagents pulled successfully!"));
|
|
1088
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
1089
|
+
console.log(chalk.green(` ✓ Pulled ${pulled} subagent(s)`));
|
|
1090
|
+
if (skipped > 0) console.log(chalk.yellow(` ⚠ Skipped ${skipped} existing file(s) (use --overwrite to replace)`));
|
|
1091
|
+
console.log();
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
spinner.fail(chalk.red("Failed to pull subagents"));
|
|
1094
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1095
|
+
process.exit(1);
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
async push(options) {
|
|
1099
|
+
await requireAuth();
|
|
1100
|
+
const teamId = await getActiveTeamId();
|
|
1101
|
+
const spinner = ora("Pushing subagents...").start();
|
|
1102
|
+
try {
|
|
1103
|
+
const apiClient$1 = getApiClient();
|
|
1104
|
+
const localSubagents = await getSubagentFiles();
|
|
1105
|
+
if (localSubagents.length === 0) {
|
|
1106
|
+
spinner.info(chalk.yellow("No local subagents to push"));
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
let created = 0;
|
|
1110
|
+
let updated = 0;
|
|
1111
|
+
const toUpdate = localSubagents.filter((s) => s.jiveId && s.name !== "subagent-runner");
|
|
1112
|
+
let changeMessage = options.message;
|
|
1113
|
+
if (toUpdate.length > 0 && !changeMessage) {
|
|
1114
|
+
spinner.stop();
|
|
1115
|
+
changeMessage = (await prompts({
|
|
1116
|
+
type: "text",
|
|
1117
|
+
name: "changeMessage",
|
|
1118
|
+
message: "Change description (optional, press Enter to skip):"
|
|
1119
|
+
})).changeMessage;
|
|
1120
|
+
spinner.start("Pushing subagents...");
|
|
1121
|
+
}
|
|
1122
|
+
for (const subagent of localSubagents) {
|
|
1123
|
+
if (subagent.name === "subagent-runner") continue;
|
|
1124
|
+
if (subagent.jiveId) {
|
|
1125
|
+
await apiClient$1.updateSubagent(subagent.jiveId, {
|
|
1126
|
+
name: subagent.name,
|
|
1127
|
+
description: subagent.description,
|
|
1128
|
+
prompt: subagent.prompt,
|
|
1129
|
+
changeMessage: changeMessage || void 0
|
|
1130
|
+
});
|
|
1131
|
+
updated++;
|
|
1132
|
+
} else {
|
|
1133
|
+
const result = await apiClient$1.createSubagent(teamId, {
|
|
1134
|
+
name: subagent.name,
|
|
1135
|
+
description: subagent.description,
|
|
1136
|
+
prompt: subagent.prompt
|
|
1137
|
+
});
|
|
1138
|
+
await updateSubagentJiveId(subagent.name, result.id);
|
|
1139
|
+
created++;
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
spinner.succeed(chalk.green("Subagents pushed successfully!"));
|
|
1143
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
1144
|
+
if (created > 0) console.log(chalk.green(` ✓ Created ${created} subagent(s)`));
|
|
1145
|
+
if (updated > 0) {
|
|
1146
|
+
console.log(chalk.green(` ✓ Updated ${updated} subagent(s)`));
|
|
1147
|
+
if (changeMessage) console.log(chalk.gray(` Change: "${changeMessage}"`));
|
|
1148
|
+
}
|
|
1149
|
+
console.log();
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
spinner.fail(chalk.red("Failed to push subagents"));
|
|
1152
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1153
|
+
process.exit(1);
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
async create() {
|
|
1157
|
+
await requireAuth();
|
|
1158
|
+
const teamId = await getActiveTeamId();
|
|
1159
|
+
console.log(chalk.bold("\n✨ Create a New Subagent\n"));
|
|
1160
|
+
const response = await prompts([
|
|
1161
|
+
{
|
|
1162
|
+
type: "text",
|
|
1163
|
+
name: "name",
|
|
1164
|
+
message: "Subagent name:",
|
|
1165
|
+
validate: (value) => value ? true : "Name is required"
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
type: "text",
|
|
1169
|
+
name: "description",
|
|
1170
|
+
message: "Description:"
|
|
1171
|
+
},
|
|
1172
|
+
{
|
|
1173
|
+
type: "text",
|
|
1174
|
+
name: "prompt",
|
|
1175
|
+
message: "Prompt:",
|
|
1176
|
+
validate: (value) => value ? true : "Prompt is required"
|
|
1177
|
+
}
|
|
1178
|
+
]);
|
|
1179
|
+
if (!response.name || !response.prompt) {
|
|
1180
|
+
console.log(chalk.yellow("\n❌ Creation cancelled"));
|
|
1181
|
+
return;
|
|
1182
|
+
}
|
|
1183
|
+
const spinner = ora("Creating subagent...").start();
|
|
1184
|
+
try {
|
|
1185
|
+
const result = await getApiClient().createSubagent(teamId, {
|
|
1186
|
+
name: response.name,
|
|
1187
|
+
description: response.description || "",
|
|
1188
|
+
prompt: response.prompt
|
|
1189
|
+
});
|
|
1190
|
+
await saveSubagentFile({
|
|
1191
|
+
name: result.name,
|
|
1192
|
+
description: result.description,
|
|
1193
|
+
prompt: result.prompt,
|
|
1194
|
+
jiveId: result.id
|
|
1195
|
+
});
|
|
1196
|
+
spinner.succeed(chalk.green("Subagent created successfully!"));
|
|
1197
|
+
console.log(chalk.cyan(`\n✨ ${result.name}`));
|
|
1198
|
+
console.log(chalk.gray(` ID: ${result.id}`));
|
|
1199
|
+
console.log(chalk.white(`\n✅ Saved to .claude/agents/${result.name}.md\n`));
|
|
1200
|
+
} catch (error) {
|
|
1201
|
+
spinner.fail(chalk.red("Failed to create subagent"));
|
|
1202
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1203
|
+
process.exit(1);
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
async delete(nameOrId) {
|
|
1207
|
+
await requireAuth();
|
|
1208
|
+
const teamId = await getActiveTeamId();
|
|
1209
|
+
const spinner = ora("Finding subagent...").start();
|
|
1210
|
+
try {
|
|
1211
|
+
const apiClient$1 = getApiClient();
|
|
1212
|
+
const subagent = (await apiClient$1.getSubagents(teamId)).find((s) => s.name === nameOrId || s.id === nameOrId);
|
|
1213
|
+
if (!subagent) {
|
|
1214
|
+
spinner.fail(chalk.red("Subagent not found"));
|
|
1215
|
+
console.log(chalk.yellow(`\n⚠️ No subagent found with name or ID: ${nameOrId}\n`));
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
spinner.stop();
|
|
1219
|
+
if (!(await prompts({
|
|
1220
|
+
type: "confirm",
|
|
1221
|
+
name: "confirm",
|
|
1222
|
+
message: `Delete subagent "${subagent.name}"?`,
|
|
1223
|
+
initial: false
|
|
1224
|
+
})).confirm) {
|
|
1225
|
+
console.log(chalk.yellow("\n❌ Deletion cancelled"));
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const deleteSpinner = ora("Deleting subagent...").start();
|
|
1229
|
+
await apiClient$1.deleteSubagent(subagent.id);
|
|
1230
|
+
await deleteSubagentFile(subagent.name);
|
|
1231
|
+
deleteSpinner.succeed(chalk.green("Subagent deleted successfully!"));
|
|
1232
|
+
console.log();
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
spinner.fail(chalk.red("Failed to delete subagent"));
|
|
1235
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1236
|
+
process.exit(1);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
};
|
|
1240
|
+
|
|
1241
|
+
//#endregion
|
|
1242
|
+
//#region src/mcp/codegen.ts
|
|
1243
|
+
/**
|
|
1244
|
+
* Convert JSON Schema to TypeScript interface string
|
|
1245
|
+
*/
|
|
1246
|
+
function jsonSchemaToTypescript(schema, interfaceName, indent = "") {
|
|
1247
|
+
if (!schema || typeof schema !== "object") return "any";
|
|
1248
|
+
if (schema.type && !schema.properties) switch (schema.type) {
|
|
1249
|
+
case "string": return schema.enum ? schema.enum.map((e) => `"${e}"`).join(" | ") : "string";
|
|
1250
|
+
case "number":
|
|
1251
|
+
case "integer": return "number";
|
|
1252
|
+
case "boolean": return "boolean";
|
|
1253
|
+
case "null": return "null";
|
|
1254
|
+
case "array":
|
|
1255
|
+
if (schema.items) return `${jsonSchemaToTypescript(schema.items, "", indent)}[]`;
|
|
1256
|
+
return "any[]";
|
|
1257
|
+
case "object":
|
|
1258
|
+
if (!schema.properties) return "Record<string, any>";
|
|
1259
|
+
break;
|
|
1260
|
+
default: return "any";
|
|
1261
|
+
}
|
|
1262
|
+
if (schema.properties) return `{\n${Object.entries(schema.properties).map(([key, propSchema]) => {
|
|
1263
|
+
const optional = schema.required?.includes(key) ?? false ? "" : "?";
|
|
1264
|
+
const propType = jsonSchemaToTypescript(propSchema, "", indent + " ");
|
|
1265
|
+
return `${propSchema.description ? `${indent} /**\n${indent} * ${propSchema.description}\n${indent} */\n${indent} ` : `${indent} `}${key}${optional}: ${propType};`;
|
|
1266
|
+
}).join("\n")}\n${indent}}`;
|
|
1267
|
+
return "any";
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Generate interface definition for input schema
|
|
1271
|
+
*/
|
|
1272
|
+
function generateInputInterface(toolName, inputSchema) {
|
|
1273
|
+
if (!inputSchema || !inputSchema.properties || Object.keys(inputSchema.properties).length === 0) return "";
|
|
1274
|
+
const interfaceName = toPascalCase(toolName) + "Input";
|
|
1275
|
+
return `interface ${interfaceName} ${jsonSchemaToTypescript(inputSchema, interfaceName, "")}\n`;
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Generate interface definition for output schema
|
|
1279
|
+
*/
|
|
1280
|
+
function generateOutputInterface(toolName, outputSchema) {
|
|
1281
|
+
if (!outputSchema || !outputSchema.properties || Object.keys(outputSchema.properties).length === 0) return `interface ${toPascalCase(toolName)}Response {\n [key: string]: any;\n}\n`;
|
|
1282
|
+
const interfaceName = toPascalCase(toolName) + "Response";
|
|
1283
|
+
return `interface ${interfaceName} ${jsonSchemaToTypescript(outputSchema, interfaceName, "")}\n`;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Convert kebab-case or snake_case to PascalCase
|
|
1287
|
+
*/
|
|
1288
|
+
function toPascalCase(str) {
|
|
1289
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Convert kebab-case or snake_case to camelCase
|
|
1293
|
+
*/
|
|
1294
|
+
function toCamelCase(str) {
|
|
1295
|
+
const pascal = toPascalCase(str);
|
|
1296
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Generate complete TypeScript tool definition
|
|
1300
|
+
*
|
|
1301
|
+
* Example output:
|
|
1302
|
+
* ```ts
|
|
1303
|
+
* import { callMcpTool } from "./runtime";
|
|
1304
|
+
*
|
|
1305
|
+
* interface GetDocumentInput {
|
|
1306
|
+
* documentId: string;
|
|
1307
|
+
* }
|
|
1308
|
+
*
|
|
1309
|
+
* interface GetDocumentResponse {
|
|
1310
|
+
* content: string;
|
|
1311
|
+
* }
|
|
1312
|
+
*
|
|
1313
|
+
* // Read a document from Google Drive
|
|
1314
|
+
* export async function getDocument(input: GetDocumentInput): Promise<GetDocumentResponse> {
|
|
1315
|
+
* return callMcpTool<GetDocumentResponse>('google-drive', 'get_document', input);
|
|
1316
|
+
* }
|
|
1317
|
+
* ```
|
|
1318
|
+
*/
|
|
1319
|
+
function generateToolDefinition(serverName, tool) {
|
|
1320
|
+
const functionName = toCamelCase(tool.name);
|
|
1321
|
+
const hasInput = tool.inputSchema?.properties && Object.keys(tool.inputSchema.properties).length > 0;
|
|
1322
|
+
const inputTypeName = hasInput ? `${toPascalCase(tool.name)}Input` : "void";
|
|
1323
|
+
const outputTypeName = `${toPascalCase(tool.name)}Response`;
|
|
1324
|
+
const inputInterface = generateInputInterface(tool.name, tool.inputSchema);
|
|
1325
|
+
const outputInterface = generateOutputInterface(tool.name, tool.outputSchema);
|
|
1326
|
+
const inputParam = hasInput ? `input: ${inputTypeName}` : "";
|
|
1327
|
+
const callArgs = hasInput ? `input` : "{}";
|
|
1328
|
+
return `import { callMcpTool } from "./runtime";
|
|
1329
|
+
|
|
1330
|
+
${inputInterface}${outputInterface}
|
|
1331
|
+
/**
|
|
1332
|
+
* ${tool.description || `Execute ${tool.name} tool`}
|
|
1333
|
+
*/
|
|
1334
|
+
export async function ${functionName}(${inputParam}): Promise<${outputTypeName}> {
|
|
1335
|
+
return callMcpTool<${outputTypeName}>('${serverName}', '${tool.name}', ${callArgs});
|
|
1336
|
+
}
|
|
1337
|
+
`;
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Generate a unique tool ID for referencing
|
|
1341
|
+
*/
|
|
1342
|
+
function generateToolId(serverName, toolName) {
|
|
1343
|
+
return `${serverName}__${toolName}`;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region src/mcp/connection-manager.ts
|
|
1348
|
+
/**
|
|
1349
|
+
* MCP Connection Manager
|
|
1350
|
+
* Manages connections to multiple MCP servers, introspects tools, and provides search/execute functionality
|
|
1351
|
+
*/
|
|
1352
|
+
var McpConnectionManager = class {
|
|
1353
|
+
clients = /* @__PURE__ */ new Map();
|
|
1354
|
+
tools = /* @__PURE__ */ new Map();
|
|
1355
|
+
serverConfigs = /* @__PURE__ */ new Map();
|
|
1356
|
+
/**
|
|
1357
|
+
* Initialize connections to all MCP servers
|
|
1358
|
+
*/
|
|
1359
|
+
async initialize(configs) {
|
|
1360
|
+
console.error(`Initializing ${configs.length} MCP server(s)...`);
|
|
1361
|
+
for (const config of configs) try {
|
|
1362
|
+
await this.connectToServer(config);
|
|
1363
|
+
} catch (error) {
|
|
1364
|
+
console.error(`Failed to connect to ${config.name}:`, error);
|
|
1365
|
+
}
|
|
1366
|
+
console.error(`Connected to ${this.clients.size} MCP server(s)`);
|
|
1367
|
+
await this.introspectTools();
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Connect to a single MCP server
|
|
1371
|
+
*/
|
|
1372
|
+
async connectToServer(config) {
|
|
1373
|
+
console.error(`Connecting to ${config.name}...`);
|
|
1374
|
+
const cleanEnv = {};
|
|
1375
|
+
for (const [key, value] of Object.entries(process.env)) if (value !== void 0) cleanEnv[key] = value;
|
|
1376
|
+
const transport = new StdioClientTransport({
|
|
1377
|
+
command: config.config.command,
|
|
1378
|
+
args: config.config.args || [],
|
|
1379
|
+
env: {
|
|
1380
|
+
...cleanEnv,
|
|
1381
|
+
...config.config.env || {}
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1384
|
+
const client = new Client({
|
|
1385
|
+
name: "jive-mcp-client",
|
|
1386
|
+
version: "1.0.0"
|
|
1387
|
+
}, { capabilities: {} });
|
|
1388
|
+
await client.connect(transport);
|
|
1389
|
+
this.clients.set(config.name, client);
|
|
1390
|
+
this.serverConfigs.set(config.name, config);
|
|
1391
|
+
console.error(`✓ Connected to ${config.name}`);
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Introspect tools from all connected servers and generate TypeScript definitions
|
|
1395
|
+
*/
|
|
1396
|
+
async introspectTools() {
|
|
1397
|
+
console.error("Introspecting tools from connected servers...");
|
|
1398
|
+
for (const [serverName, client] of this.clients.entries()) try {
|
|
1399
|
+
const result = await client.listTools();
|
|
1400
|
+
console.error(`Found ${result.tools.length} tool(s) in ${serverName}`);
|
|
1401
|
+
for (const tool of result.tools) {
|
|
1402
|
+
const toolId = generateToolId(serverName, tool.name);
|
|
1403
|
+
const tsDefinition = generateToolDefinition(serverName, {
|
|
1404
|
+
name: tool.name,
|
|
1405
|
+
description: tool.description,
|
|
1406
|
+
inputSchema: tool.inputSchema
|
|
1407
|
+
});
|
|
1408
|
+
this.tools.set(toolId, {
|
|
1409
|
+
id: toolId,
|
|
1410
|
+
serverName,
|
|
1411
|
+
toolName: tool.name,
|
|
1412
|
+
description: tool.description || "",
|
|
1413
|
+
inputSchema: tool.inputSchema,
|
|
1414
|
+
outputSchema: void 0,
|
|
1415
|
+
tsDefinition
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
console.error(`Failed to introspect tools from ${serverName}:`, error);
|
|
1420
|
+
}
|
|
1421
|
+
console.error(`✓ Generated TypeScript for ${this.tools.size} tool(s)`);
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Search tools by query string (matches name or description)
|
|
1425
|
+
*/
|
|
1426
|
+
searchTools(query) {
|
|
1427
|
+
const lowerQuery = query.toLowerCase();
|
|
1428
|
+
const results = [];
|
|
1429
|
+
for (const tool of this.tools.values()) {
|
|
1430
|
+
const nameMatch = tool.toolName.toLowerCase().includes(lowerQuery);
|
|
1431
|
+
const descMatch = tool.description.toLowerCase().includes(lowerQuery);
|
|
1432
|
+
if (nameMatch || descMatch) results.push({
|
|
1433
|
+
id: tool.id,
|
|
1434
|
+
name: tool.toolName,
|
|
1435
|
+
description: tool.description
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
return results;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Get full tool definitions including TypeScript code
|
|
1442
|
+
*/
|
|
1443
|
+
getTools(toolIds) {
|
|
1444
|
+
const results = [];
|
|
1445
|
+
for (const toolId of toolIds) {
|
|
1446
|
+
const tool = this.tools.get(toolId);
|
|
1447
|
+
if (tool) results.push({
|
|
1448
|
+
id: tool.id,
|
|
1449
|
+
name: tool.toolName,
|
|
1450
|
+
description: tool.description,
|
|
1451
|
+
tsDefinition: tool.tsDefinition
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
return results;
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* Call an MCP tool
|
|
1458
|
+
*/
|
|
1459
|
+
async callTool(serverName, toolName, args) {
|
|
1460
|
+
const client = this.clients.get(serverName);
|
|
1461
|
+
if (!client) throw new Error(`MCP server '${serverName}' not connected`);
|
|
1462
|
+
return await client.callTool({
|
|
1463
|
+
name: toolName,
|
|
1464
|
+
arguments: args
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* Get all connected server names
|
|
1469
|
+
*/
|
|
1470
|
+
getConnectedServers() {
|
|
1471
|
+
return Array.from(this.clients.keys());
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* Get total tool count
|
|
1475
|
+
*/
|
|
1476
|
+
getToolCount() {
|
|
1477
|
+
return this.tools.size;
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Shutdown all connections
|
|
1481
|
+
*/
|
|
1482
|
+
async shutdown() {
|
|
1483
|
+
console.error("Shutting down MCP connections...");
|
|
1484
|
+
for (const [serverName, client] of this.clients.entries()) try {
|
|
1485
|
+
await client.close();
|
|
1486
|
+
console.error(`✓ Disconnected from ${serverName}`);
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
console.error(`Error disconnecting from ${serverName}:`, error);
|
|
1489
|
+
}
|
|
1490
|
+
this.clients.clear();
|
|
1491
|
+
this.tools.clear();
|
|
1492
|
+
this.serverConfigs.clear();
|
|
1493
|
+
}
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1496
|
+
//#endregion
|
|
1497
|
+
//#region src/mcp/index.ts
|
|
1498
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1499
|
+
const __dirname = path.dirname(__filename);
|
|
1500
|
+
const LOG_FILE = process.env.JIVE_LOG_FILE || path.join(__dirname, "../jive-mcp.log");
|
|
1501
|
+
const logStream = fs$1.createWriteStream(LOG_FILE, { flags: "a" });
|
|
1502
|
+
function log(...args) {
|
|
1503
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1504
|
+
const message = args.map((arg) => typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg)).join(" ");
|
|
1505
|
+
logStream.write(`[${timestamp}] ${message}\n`);
|
|
1506
|
+
}
|
|
1507
|
+
/**
|
|
1508
|
+
* Stdio MCP server that calls HTTP API endpoints
|
|
1509
|
+
* Requires JIVE_API_KEY environment variable
|
|
1510
|
+
* Optionally accepts JIVE_API_URL (defaults to http://localhost:5173)
|
|
1511
|
+
*/
|
|
1512
|
+
async function startMcpServer() {
|
|
1513
|
+
log("=== Jive Server Starting ===");
|
|
1514
|
+
const apiKey = process.env.JIVE_API_KEY;
|
|
1515
|
+
const apiUrl = process.env.JIVE_API_URL || "http://localhost:5173";
|
|
1516
|
+
if (!apiKey) {
|
|
1517
|
+
log("Error: JIVE_API_KEY environment variable is required");
|
|
1518
|
+
log("Usage: JIVE_API_KEY=jive_xxx npm run mcp");
|
|
1519
|
+
process.exit(1);
|
|
1520
|
+
}
|
|
1521
|
+
if (!apiKey.startsWith("jive_")) {
|
|
1522
|
+
log("Error: JIVE_API_KEY must start with \"jive_\"");
|
|
1523
|
+
process.exit(1);
|
|
1524
|
+
}
|
|
1525
|
+
log(`Jive server connecting to ${apiUrl}`);
|
|
1526
|
+
const connectionManager = new McpConnectionManager();
|
|
1527
|
+
try {
|
|
1528
|
+
const response = await fetch(`${apiUrl}/api/mcp-config`, {
|
|
1529
|
+
method: "GET",
|
|
1530
|
+
headers: { "X-API-Key": apiKey }
|
|
1531
|
+
});
|
|
1532
|
+
if (!response.ok) throw new Error(`Failed to fetch MCP configs: ${response.status} ${response.statusText}`);
|
|
1533
|
+
const { servers } = await response.json();
|
|
1534
|
+
log(`Found ${servers.length} MCP server config(s)`);
|
|
1535
|
+
if (servers.length > 0) await connectionManager.initialize(servers);
|
|
1536
|
+
else log("No MCP servers configured for this team");
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
log("Error fetching MCP configs:", error);
|
|
1539
|
+
log("Continuing without MCP tool connections...");
|
|
1540
|
+
}
|
|
1541
|
+
const server = new Server({
|
|
1542
|
+
name: "jive-mcp",
|
|
1543
|
+
version: "1.0.0"
|
|
1544
|
+
}, { capabilities: { tools: {} } });
|
|
1545
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1546
|
+
return { tools: [
|
|
1547
|
+
{
|
|
1548
|
+
name: "search_subagents",
|
|
1549
|
+
description: "Search for subagents by name or description",
|
|
1550
|
+
inputSchema: {
|
|
1551
|
+
type: "object",
|
|
1552
|
+
properties: { query: {
|
|
1553
|
+
type: "string",
|
|
1554
|
+
description: "Search query for subagents"
|
|
1555
|
+
} },
|
|
1556
|
+
required: ["query"]
|
|
1557
|
+
}
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
name: "call_subagent",
|
|
1561
|
+
description: "Get a subagent's prompt formatted with user input",
|
|
1562
|
+
inputSchema: {
|
|
1563
|
+
type: "object",
|
|
1564
|
+
properties: {
|
|
1565
|
+
id: {
|
|
1566
|
+
type: "number",
|
|
1567
|
+
description: "Subagent ID"
|
|
1568
|
+
},
|
|
1569
|
+
prompt: {
|
|
1570
|
+
type: "string",
|
|
1571
|
+
description: "Prompt to pass to the subagent"
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1574
|
+
required: ["id", "prompt"]
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
1577
|
+
{
|
|
1578
|
+
name: "search_tools",
|
|
1579
|
+
description: "Search for MCP tools by name or description. Returns lightweight list of tool names and descriptions.",
|
|
1580
|
+
inputSchema: {
|
|
1581
|
+
type: "object",
|
|
1582
|
+
properties: { query: {
|
|
1583
|
+
type: "string",
|
|
1584
|
+
description: "Search query for tools"
|
|
1585
|
+
} },
|
|
1586
|
+
required: ["query"]
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
name: "get_tools",
|
|
1591
|
+
description: "Get full TypeScript definitions for specific tools by their IDs",
|
|
1592
|
+
inputSchema: {
|
|
1593
|
+
type: "object",
|
|
1594
|
+
properties: { ids: {
|
|
1595
|
+
type: "array",
|
|
1596
|
+
items: { type: "string" },
|
|
1597
|
+
description: "Array of tool IDs to retrieve"
|
|
1598
|
+
} },
|
|
1599
|
+
required: ["ids"]
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
name: "call_tool",
|
|
1604
|
+
description: "Execute TypeScript code that calls MCP tools. Returns the result of execution.",
|
|
1605
|
+
inputSchema: {
|
|
1606
|
+
type: "object",
|
|
1607
|
+
properties: { code: {
|
|
1608
|
+
type: "string",
|
|
1609
|
+
description: "TypeScript code to execute"
|
|
1610
|
+
} },
|
|
1611
|
+
required: ["code"]
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
] };
|
|
1615
|
+
});
|
|
1616
|
+
async function apiRequest(endpoint, body) {
|
|
1617
|
+
const url = `${apiUrl}${endpoint}`;
|
|
1618
|
+
try {
|
|
1619
|
+
const response = await fetch(url, {
|
|
1620
|
+
method: "POST",
|
|
1621
|
+
headers: {
|
|
1622
|
+
"Content-Type": "application/json",
|
|
1623
|
+
"X-API-Key": apiKey
|
|
1624
|
+
},
|
|
1625
|
+
body: body ? JSON.stringify(body) : void 0
|
|
1626
|
+
});
|
|
1627
|
+
if (!response.ok) {
|
|
1628
|
+
const errorText = await response.text();
|
|
1629
|
+
let errorMessage;
|
|
1630
|
+
try {
|
|
1631
|
+
errorMessage = JSON.parse(errorText).error || errorText;
|
|
1632
|
+
} catch {
|
|
1633
|
+
errorMessage = errorText;
|
|
1634
|
+
}
|
|
1635
|
+
throw new Error(`API request failed (${response.status}): ${errorMessage}`);
|
|
1636
|
+
}
|
|
1637
|
+
return await response.json();
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
if (error instanceof Error) throw error;
|
|
1640
|
+
throw new Error(`API request failed: ${String(error)}`);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1644
|
+
const { name, arguments: args } = request.params;
|
|
1645
|
+
log(`Tool called: ${name}`, { args });
|
|
1646
|
+
try {
|
|
1647
|
+
if (!args) throw new Error("Missing arguments");
|
|
1648
|
+
switch (name) {
|
|
1649
|
+
case "search_subagents": {
|
|
1650
|
+
const results = await apiRequest("/api/subagents/search", { query: args.query });
|
|
1651
|
+
return { content: [{
|
|
1652
|
+
type: "text",
|
|
1653
|
+
text: JSON.stringify(results, null, 2)
|
|
1654
|
+
}] };
|
|
1655
|
+
}
|
|
1656
|
+
case "call_subagent": return { content: [{
|
|
1657
|
+
type: "text",
|
|
1658
|
+
text: (await apiRequest(`/api/subagents/${args.id}/call`, { prompt: args.prompt })).prompt
|
|
1659
|
+
}] };
|
|
1660
|
+
case "search_tools": {
|
|
1661
|
+
const results = connectionManager.searchTools(args.query);
|
|
1662
|
+
return { content: [{
|
|
1663
|
+
type: "text",
|
|
1664
|
+
text: JSON.stringify(results, null, 2)
|
|
1665
|
+
}] };
|
|
1666
|
+
}
|
|
1667
|
+
case "get_tools": return { content: [{
|
|
1668
|
+
type: "text",
|
|
1669
|
+
text: connectionManager.getTools(args.ids).map((tool) => {
|
|
1670
|
+
return `// Tool ID: ${tool.id}\n// ${tool.description}\n\n${tool.tsDefinition}`;
|
|
1671
|
+
}).join("\n\n---\n\n") || "No tools found"
|
|
1672
|
+
}] };
|
|
1673
|
+
case "call_tool": try {
|
|
1674
|
+
let code = args.code;
|
|
1675
|
+
code = code.replace(/import\s+.*?from\s+['"].*?['"];?\s*/g, "");
|
|
1676
|
+
const result = ts.transpileModule(code, { compilerOptions: {
|
|
1677
|
+
module: ts.ModuleKind.ESNext,
|
|
1678
|
+
target: ts.ScriptTarget.ES2020
|
|
1679
|
+
} });
|
|
1680
|
+
if (result.diagnostics && result.diagnostics.length > 0) return {
|
|
1681
|
+
content: [{
|
|
1682
|
+
type: "text",
|
|
1683
|
+
text: `TypeScript validation failed:\n${result.diagnostics.map((d) => ts.flattenDiagnosticMessageText(d.messageText, "\n")).join("\n")}`
|
|
1684
|
+
}],
|
|
1685
|
+
isError: true
|
|
1686
|
+
};
|
|
1687
|
+
const consoleOutput = [];
|
|
1688
|
+
let executionResult = null;
|
|
1689
|
+
const callMcpTool = async (serverName, toolName, args$1) => {
|
|
1690
|
+
log("Calling MCP tool:", {
|
|
1691
|
+
serverName,
|
|
1692
|
+
toolName,
|
|
1693
|
+
args: args$1
|
|
1694
|
+
});
|
|
1695
|
+
try {
|
|
1696
|
+
const result$1 = await connectionManager.callTool(serverName, toolName, args$1);
|
|
1697
|
+
log("MCP tool call result:", result$1);
|
|
1698
|
+
return result$1;
|
|
1699
|
+
} catch (error) {
|
|
1700
|
+
throw new Error(`MCP tool call failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
const done = (result$1) => {
|
|
1704
|
+
executionResult = result$1;
|
|
1705
|
+
};
|
|
1706
|
+
const customConsole = { log: (...args$1) => {
|
|
1707
|
+
consoleOutput.push(args$1.map(String).join(" "));
|
|
1708
|
+
log("User code log:", args$1.map(String).join(" "));
|
|
1709
|
+
} };
|
|
1710
|
+
const wrappedCode = `(async () => { ${result.outputText} })()`;
|
|
1711
|
+
const executeCode = new Function("callMcpTool", "done", "console", `return ${wrappedCode}`);
|
|
1712
|
+
log("Executing tool code");
|
|
1713
|
+
log("Wrapped code:", executeCode);
|
|
1714
|
+
const returnValue = await executeCode(callMcpTool, done, customConsole);
|
|
1715
|
+
log("Tool code execution result:", returnValue);
|
|
1716
|
+
const finalResult = executionResult !== null && executionResult !== void 0 ? executionResult : returnValue;
|
|
1717
|
+
if (finalResult !== null && finalResult !== void 0) return { content: [{
|
|
1718
|
+
type: "text",
|
|
1719
|
+
text: typeof finalResult === "string" ? finalResult : JSON.stringify(finalResult, null, 2)
|
|
1720
|
+
}] };
|
|
1721
|
+
return { content: [{
|
|
1722
|
+
type: "text",
|
|
1723
|
+
text: consoleOutput.length > 0 ? consoleOutput.join("\n") : "Code executed successfully (no output)"
|
|
1724
|
+
}] };
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
return {
|
|
1727
|
+
content: [{
|
|
1728
|
+
type: "text",
|
|
1729
|
+
text: `Execution error: ${error instanceof Error ? error.message : String(error)}`
|
|
1730
|
+
}],
|
|
1731
|
+
isError: true
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
1735
|
+
}
|
|
1736
|
+
} catch (error) {
|
|
1737
|
+
return {
|
|
1738
|
+
content: [{
|
|
1739
|
+
type: "text",
|
|
1740
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
1741
|
+
}],
|
|
1742
|
+
isError: true
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
const transport = new StdioServerTransport();
|
|
1747
|
+
await server.connect(transport);
|
|
1748
|
+
log("Jive server running on stdio");
|
|
1749
|
+
log(`Logging to: ${LOG_FILE}`);
|
|
1750
|
+
process.on("SIGINT", async () => {
|
|
1751
|
+
log("Shutting down via SIGINT...");
|
|
1752
|
+
await connectionManager.shutdown();
|
|
1753
|
+
logStream.end();
|
|
1754
|
+
process.exit(0);
|
|
1755
|
+
});
|
|
1756
|
+
process.on("SIGTERM", async () => {
|
|
1757
|
+
log("Shutting down via SIGTERM...");
|
|
1758
|
+
await connectionManager.shutdown();
|
|
1759
|
+
logStream.end();
|
|
1760
|
+
process.exit(0);
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
var mcp_default = startMcpServer;
|
|
1764
|
+
|
|
1765
|
+
//#endregion
|
|
1766
|
+
//#region src/commands/mcp.ts
|
|
1767
|
+
const mcpCommands = {
|
|
1768
|
+
async list() {
|
|
1769
|
+
await requireAuth();
|
|
1770
|
+
const teamId = await getActiveTeamId();
|
|
1771
|
+
const spinner = ora("Fetching MCP servers...").start();
|
|
1772
|
+
try {
|
|
1773
|
+
const servers = await getApiClient().getMcpServers(teamId);
|
|
1774
|
+
spinner.stop();
|
|
1775
|
+
if (servers.length === 0) {
|
|
1776
|
+
console.log(chalk.yellow("\n⚠️ No MCP servers found for this team"));
|
|
1777
|
+
console.log(chalk.white("Run"), chalk.cyan("jive mcp add"), chalk.white("to add one\n"));
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
console.log(chalk.bold(`\n🔧 Team MCP Servers (${servers.length}):\n`));
|
|
1781
|
+
for (const server of servers) {
|
|
1782
|
+
console.log(chalk.cyan(` ${server.name}`));
|
|
1783
|
+
console.log(chalk.gray(` ID: ${server.id}`));
|
|
1784
|
+
if (server.config?.command) console.log(chalk.white(` Command: ${server.config.command} ${server.config.args?.join(" ") || ""}`));
|
|
1785
|
+
console.log();
|
|
1786
|
+
}
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
spinner.fail(chalk.red("Failed to fetch MCP servers"));
|
|
1789
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1790
|
+
process.exit(1);
|
|
1791
|
+
}
|
|
1792
|
+
},
|
|
1793
|
+
async pull(options) {
|
|
1794
|
+
await requireAuth();
|
|
1795
|
+
const teamId = await getActiveTeamId();
|
|
1796
|
+
const spinner = ora("Pulling MCP servers...").start();
|
|
1797
|
+
try {
|
|
1798
|
+
const remoteServers = await getApiClient().getMcpServers(teamId);
|
|
1799
|
+
if (remoteServers.length === 0) {
|
|
1800
|
+
spinner.info(chalk.yellow("No MCP servers to pull"));
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
await backupMcpConfig();
|
|
1804
|
+
const localConfig = await getMcpConfig();
|
|
1805
|
+
const newServers = {};
|
|
1806
|
+
for (const server of remoteServers) newServers[server.name] = server.config;
|
|
1807
|
+
if (options.merge && localConfig?.mcpServers) await saveMcpConfig({ mcpServers: {
|
|
1808
|
+
...localConfig.mcpServers,
|
|
1809
|
+
...newServers
|
|
1810
|
+
} });
|
|
1811
|
+
else {
|
|
1812
|
+
const jiveServer = localConfig?.mcpServers?.["jive-mcp"];
|
|
1813
|
+
const servers = { ...newServers };
|
|
1814
|
+
if (jiveServer) servers["jive-mcp"] = jiveServer;
|
|
1815
|
+
await saveMcpConfig({ mcpServers: servers });
|
|
1816
|
+
}
|
|
1817
|
+
spinner.succeed(chalk.green("MCP servers pulled successfully!"));
|
|
1818
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
1819
|
+
console.log(chalk.green(` ✓ Pulled ${remoteServers.length} MCP server(s)`));
|
|
1820
|
+
if (options.merge) console.log(chalk.cyan(" ℹ Merged with local servers"));
|
|
1821
|
+
else console.log(chalk.cyan(" ℹ Replaced local servers"));
|
|
1822
|
+
console.log(chalk.gray(` ✓ Backup saved to .mcp.json.backup`));
|
|
1823
|
+
console.log();
|
|
1824
|
+
} catch (error) {
|
|
1825
|
+
spinner.fail(chalk.red("Failed to pull MCP servers"));
|
|
1826
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1827
|
+
process.exit(1);
|
|
1828
|
+
}
|
|
1829
|
+
},
|
|
1830
|
+
async push(options) {
|
|
1831
|
+
await requireAuth();
|
|
1832
|
+
const teamId = await getActiveTeamId();
|
|
1833
|
+
const spinner = ora("Pushing MCP servers...").start();
|
|
1834
|
+
try {
|
|
1835
|
+
const apiClient$1 = getApiClient();
|
|
1836
|
+
const localConfig = await getMcpConfig();
|
|
1837
|
+
if (!localConfig?.mcpServers) {
|
|
1838
|
+
spinner.info(chalk.yellow("No local MCP servers to push"));
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
let created = 0;
|
|
1842
|
+
let errors = 0;
|
|
1843
|
+
const serverNames = Object.keys(localConfig.mcpServers).filter((name) => name !== "jive-mcp");
|
|
1844
|
+
for (const name of serverNames) {
|
|
1845
|
+
const config = localConfig.mcpServers[name];
|
|
1846
|
+
try {
|
|
1847
|
+
await apiClient$1.createMcpServer(teamId, {
|
|
1848
|
+
name,
|
|
1849
|
+
description: `Pushed from local .mcp.json`,
|
|
1850
|
+
config
|
|
1851
|
+
});
|
|
1852
|
+
created++;
|
|
1853
|
+
} catch (error) {
|
|
1854
|
+
console.warn(chalk.gray(`\nWarning: Could not push "${name}": ${error.message}`));
|
|
1855
|
+
errors++;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
spinner.succeed(chalk.green("MCP servers pushed!"));
|
|
1859
|
+
console.log(chalk.bold("\n📦 Summary:\n"));
|
|
1860
|
+
console.log(chalk.green(` ✓ Pushed ${created} MCP server(s)`));
|
|
1861
|
+
if (errors > 0) console.log(chalk.yellow(` ⚠ ${errors} error(s) (servers may already exist)`));
|
|
1862
|
+
console.log();
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
spinner.fail(chalk.red("Failed to push MCP servers"));
|
|
1865
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1866
|
+
process.exit(1);
|
|
1867
|
+
}
|
|
1868
|
+
},
|
|
1869
|
+
async add(name) {
|
|
1870
|
+
await requireAuth();
|
|
1871
|
+
const teamId = await getActiveTeamId();
|
|
1872
|
+
console.log(chalk.bold(`\n➕ Add MCP Server: ${name}\n`));
|
|
1873
|
+
const response = await prompts([
|
|
1874
|
+
{
|
|
1875
|
+
type: "text",
|
|
1876
|
+
name: "command",
|
|
1877
|
+
message: "Command (e.g., npx, node, python):",
|
|
1878
|
+
validate: (value) => value ? true : "Command is required"
|
|
1879
|
+
},
|
|
1880
|
+
{
|
|
1881
|
+
type: "list",
|
|
1882
|
+
name: "args",
|
|
1883
|
+
message: "Arguments (comma-separated):",
|
|
1884
|
+
separator: ","
|
|
1885
|
+
},
|
|
1886
|
+
{
|
|
1887
|
+
type: "text",
|
|
1888
|
+
name: "env",
|
|
1889
|
+
message: "Environment variables (key=value, comma-separated):"
|
|
1890
|
+
}
|
|
1891
|
+
]);
|
|
1892
|
+
if (!response.command) {
|
|
1893
|
+
console.log(chalk.yellow("\n❌ Cancelled"));
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const config = { command: response.command };
|
|
1897
|
+
if (response.args && response.args.length > 0) config.args = response.args;
|
|
1898
|
+
if (response.env) {
|
|
1899
|
+
config.env = {};
|
|
1900
|
+
const envPairs = response.env.split(",");
|
|
1901
|
+
for (const pair of envPairs) {
|
|
1902
|
+
const [key, value] = pair.split("=");
|
|
1903
|
+
if (key && value) config.env[key.trim()] = value.trim();
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const spinner = ora("Adding MCP server...").start();
|
|
1907
|
+
try {
|
|
1908
|
+
await getApiClient().createMcpServer(teamId, {
|
|
1909
|
+
name,
|
|
1910
|
+
description: "",
|
|
1911
|
+
config
|
|
1912
|
+
});
|
|
1913
|
+
await addMcpServer(name, config);
|
|
1914
|
+
spinner.succeed(chalk.green("MCP server added successfully!"));
|
|
1915
|
+
console.log(chalk.cyan(`\n✨ ${name}`));
|
|
1916
|
+
console.log(chalk.white(` Command: ${config.command} ${config.args?.join(" ") || ""}`));
|
|
1917
|
+
console.log(chalk.white("\n✅ Added to team and local .mcp.json\n"));
|
|
1918
|
+
} catch (error) {
|
|
1919
|
+
spinner.fail(chalk.red("Failed to add MCP server"));
|
|
1920
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1921
|
+
process.exit(1);
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
async remove(nameOrId) {
|
|
1925
|
+
await requireAuth();
|
|
1926
|
+
const teamId = await getActiveTeamId();
|
|
1927
|
+
const spinner = ora("Finding MCP server...").start();
|
|
1928
|
+
try {
|
|
1929
|
+
const apiClient$1 = getApiClient();
|
|
1930
|
+
const server = (await apiClient$1.getMcpServers(teamId)).find((s) => s.name === nameOrId || s.id.toString() === nameOrId);
|
|
1931
|
+
if (!server) {
|
|
1932
|
+
spinner.fail(chalk.red("MCP server not found"));
|
|
1933
|
+
console.log(chalk.yellow(`\n⚠️ No MCP server found with name or ID: ${nameOrId}\n`));
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
spinner.stop();
|
|
1937
|
+
if (!(await prompts({
|
|
1938
|
+
type: "confirm",
|
|
1939
|
+
name: "confirm",
|
|
1940
|
+
message: `Remove MCP server "${server.name}"?`,
|
|
1941
|
+
initial: false
|
|
1942
|
+
})).confirm) {
|
|
1943
|
+
console.log(chalk.yellow("\n❌ Removal cancelled"));
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
const deleteSpinner = ora("Removing MCP server...").start();
|
|
1947
|
+
await apiClient$1.deleteMcpServer(server.id.toString());
|
|
1948
|
+
await removeMcpServer(server.name);
|
|
1949
|
+
deleteSpinner.succeed(chalk.green("MCP server removed successfully!"));
|
|
1950
|
+
console.log();
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
spinner.fail(chalk.red("Failed to remove MCP server"));
|
|
1953
|
+
console.error(chalk.red(`\n❌ ${error.message}`));
|
|
1954
|
+
process.exit(1);
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
async start() {
|
|
1958
|
+
await requireAuth();
|
|
1959
|
+
await getActiveTeamId();
|
|
1960
|
+
ora("Starting MCP server...").start();
|
|
1961
|
+
await mcp_default();
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
|
|
1965
|
+
//#endregion
|
|
1966
|
+
//#region src/commands/sync.ts
|
|
1967
|
+
async function syncCommand(options) {
|
|
1968
|
+
await requireAuth();
|
|
1969
|
+
await getActiveTeamId();
|
|
1970
|
+
if (options.dryRun) console.log(chalk.bold("\n🔍 Dry Run Mode (no changes will be made)\n"));
|
|
1971
|
+
else console.log(chalk.bold("\n🔄 Synchronizing Resources\n"));
|
|
1972
|
+
if (options.dryRun) {
|
|
1973
|
+
console.log(chalk.yellow("Dry run mode not yet implemented"));
|
|
1974
|
+
console.log(chalk.gray("For now, sync will proceed with actual changes\n"));
|
|
1975
|
+
}
|
|
1976
|
+
try {
|
|
1977
|
+
console.log(chalk.cyan("📥 Pulling subagents..."));
|
|
1978
|
+
await subagentCommands.pull({ overwrite: false });
|
|
1979
|
+
console.log(chalk.cyan("📥 Pulling MCP servers..."));
|
|
1980
|
+
await mcpCommands.pull({ merge: true });
|
|
1981
|
+
console.log(chalk.green.bold("\n✅ Sync completed successfully!\n"));
|
|
1982
|
+
} catch (error) {
|
|
1983
|
+
console.error(chalk.red("\n❌ Sync failed"));
|
|
1984
|
+
console.error(chalk.red(`${error.message}\n`));
|
|
1985
|
+
process.exit(1);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
async function statusCommand() {
|
|
1989
|
+
await requireAuth();
|
|
1990
|
+
const teamId = await getActiveTeamId();
|
|
1991
|
+
console.log(chalk.bold("\n📊 Sync Status\n"));
|
|
1992
|
+
const spinner = ora("Checking status...").start();
|
|
1993
|
+
try {
|
|
1994
|
+
spinner.succeed(chalk.green("Status check complete"));
|
|
1995
|
+
console.log(chalk.cyan(`Team ID: ${teamId}`));
|
|
1996
|
+
console.log(chalk.white("\nTo see detailed status:"));
|
|
1997
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive subagents list"), chalk.white("to view subagents"));
|
|
1998
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive mcp list"), chalk.white("to view MCP servers"));
|
|
1999
|
+
console.log(chalk.white(" • Run"), chalk.cyan("jive sync"), chalk.white("to synchronize\n"));
|
|
2000
|
+
} catch (error) {
|
|
2001
|
+
spinner.fail(chalk.red("Failed to check status"));
|
|
2002
|
+
console.error(chalk.red(`\n❌ ${error.message}\n`));
|
|
2003
|
+
process.exit(1);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
//#endregion
|
|
2008
|
+
//#region src/commands/doctor.ts
|
|
2009
|
+
async function doctorCommand() {
|
|
2010
|
+
console.log(chalk.bold("\n🩺 Jive Health Check\n"));
|
|
2011
|
+
const results = [];
|
|
2012
|
+
results.push({
|
|
2013
|
+
name: "Authentication",
|
|
2014
|
+
result: await checkAuthentication()
|
|
2015
|
+
});
|
|
2016
|
+
results.push({
|
|
2017
|
+
name: "Project Initialization",
|
|
2018
|
+
result: await checkProjectInit()
|
|
2019
|
+
});
|
|
2020
|
+
results.push({
|
|
2021
|
+
name: "MCP Server Config",
|
|
2022
|
+
result: await checkMcpConfig()
|
|
2023
|
+
});
|
|
2024
|
+
results.push({
|
|
2025
|
+
name: "Subagent Runner",
|
|
2026
|
+
result: await checkSubagentRunner()
|
|
2027
|
+
});
|
|
2028
|
+
results.push({
|
|
2029
|
+
name: "Telemetry Plugin",
|
|
2030
|
+
result: await checkPlugin()
|
|
2031
|
+
});
|
|
2032
|
+
results.push({
|
|
2033
|
+
name: "Plugin Config",
|
|
2034
|
+
result: await checkPluginConfig()
|
|
2035
|
+
});
|
|
2036
|
+
results.push({
|
|
2037
|
+
name: "API Connectivity",
|
|
2038
|
+
result: await checkApiConnectivity()
|
|
2039
|
+
});
|
|
2040
|
+
results.push({
|
|
2041
|
+
name: "Team Membership",
|
|
2042
|
+
result: await checkTeamMembership()
|
|
2043
|
+
});
|
|
2044
|
+
console.log(chalk.bold("Checks:\n"));
|
|
2045
|
+
let passCount = 0;
|
|
2046
|
+
let warnCount = 0;
|
|
2047
|
+
let failCount = 0;
|
|
2048
|
+
for (const { name, result } of results) {
|
|
2049
|
+
const icon = result.status === "pass" ? "✓" : result.status === "warn" ? "⚠" : "✗";
|
|
2050
|
+
const color = result.status === "pass" ? chalk.green : result.status === "warn" ? chalk.yellow : chalk.red;
|
|
2051
|
+
console.log(color(` ${icon} ${name}`));
|
|
2052
|
+
console.log(color(` ${result.message}`));
|
|
2053
|
+
if (result.details) console.log(chalk.gray(` ${result.details}`));
|
|
2054
|
+
if (result.fix) console.log(chalk.cyan(` Fix: ${result.fix}`));
|
|
2055
|
+
console.log("");
|
|
2056
|
+
if (result.status === "pass") passCount++;
|
|
2057
|
+
if (result.status === "warn") warnCount++;
|
|
2058
|
+
if (result.status === "fail") failCount++;
|
|
2059
|
+
}
|
|
2060
|
+
console.log(chalk.bold("Summary:\n"));
|
|
2061
|
+
console.log(chalk.green(` ${passCount} passed`));
|
|
2062
|
+
if (warnCount > 0) console.log(chalk.yellow(` ${warnCount} warnings`));
|
|
2063
|
+
if (failCount > 0) console.log(chalk.red(` ${failCount} failed`));
|
|
2064
|
+
if (failCount === 0 && warnCount === 0) console.log(chalk.bold.green("\n✨ All checks passed! Jive is healthy.\n"));
|
|
2065
|
+
else if (failCount === 0) console.log(chalk.bold.yellow("\n⚠️ Some warnings found, but system should work.\n"));
|
|
2066
|
+
else {
|
|
2067
|
+
console.log(chalk.bold.red("\n❌ Critical issues found. Please fix them to use Jive.\n"));
|
|
2068
|
+
console.log(chalk.white("Run"), chalk.cyan("jive init"), chalk.white("to initialize or repair your setup.\n"));
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
async function checkAuthentication() {
|
|
2072
|
+
const credentials = await getCredentials();
|
|
2073
|
+
if (!credentials) return {
|
|
2074
|
+
status: "fail",
|
|
2075
|
+
message: "Not authenticated",
|
|
2076
|
+
fix: "Run \"jive login\" to authenticate"
|
|
2077
|
+
};
|
|
2078
|
+
return {
|
|
2079
|
+
status: "pass",
|
|
2080
|
+
message: "Authenticated",
|
|
2081
|
+
details: `Logged in as ${credentials.email}`
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
async function checkProjectInit() {
|
|
2085
|
+
try {
|
|
2086
|
+
const config = await getProjectConfig();
|
|
2087
|
+
if (!config) return {
|
|
2088
|
+
status: "fail",
|
|
2089
|
+
message: "Project not initialized",
|
|
2090
|
+
fix: "Run \"jive init\" to initialize this project"
|
|
2091
|
+
};
|
|
2092
|
+
return {
|
|
2093
|
+
status: "pass",
|
|
2094
|
+
message: "Project initialized",
|
|
2095
|
+
details: `Team ID: ${config.teamId}`
|
|
2096
|
+
};
|
|
2097
|
+
} catch (error) {
|
|
2098
|
+
return {
|
|
2099
|
+
status: "fail",
|
|
2100
|
+
message: "Cannot read project config",
|
|
2101
|
+
details: error.message,
|
|
2102
|
+
fix: "Run \"jive init\" to recreate configuration"
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
async function checkMcpConfig() {
|
|
2107
|
+
const mcpConfig = await getMcpConfig();
|
|
2108
|
+
if (!mcpConfig) return {
|
|
2109
|
+
status: "fail",
|
|
2110
|
+
message: "No .mcp.json found",
|
|
2111
|
+
fix: "Run \"jive init\" to create .mcp.json"
|
|
2112
|
+
};
|
|
2113
|
+
if (!mcpConfig.mcpServers?.["jive-mcp"]) return {
|
|
2114
|
+
status: "warn",
|
|
2115
|
+
message: "Jive server not configured",
|
|
2116
|
+
details: "Found .mcp.json but jive-mcp server is missing",
|
|
2117
|
+
fix: "Run \"jive init\" to add jive-mcp server"
|
|
2118
|
+
};
|
|
2119
|
+
return {
|
|
2120
|
+
status: "pass",
|
|
2121
|
+
message: "MCP server configured",
|
|
2122
|
+
details: `${Object.keys(mcpConfig.mcpServers || {}).length} server(s) configured`
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
async function checkSubagentRunner() {
|
|
2126
|
+
const runnerPath = path.join(process.cwd(), ".claude", "agents", "subagent-runner.md");
|
|
2127
|
+
try {
|
|
2128
|
+
await fs.access(runnerPath);
|
|
2129
|
+
if (!(await fs.readFile(runnerPath, "utf-8")).includes("subagent-runner")) return {
|
|
2130
|
+
status: "warn",
|
|
2131
|
+
message: "Subagent runner file corrupted",
|
|
2132
|
+
details: "File exists but content is invalid",
|
|
2133
|
+
fix: "Run \"jive init\" to recreate subagent-runner.md"
|
|
2134
|
+
};
|
|
2135
|
+
return {
|
|
2136
|
+
status: "pass",
|
|
2137
|
+
message: "Subagent runner exists",
|
|
2138
|
+
details: runnerPath
|
|
2139
|
+
};
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
if (error.code === "ENOENT") return {
|
|
2142
|
+
status: "fail",
|
|
2143
|
+
message: "Subagent runner not found",
|
|
2144
|
+
fix: "Run \"jive init\" to create .claude/agents/subagent-runner.md"
|
|
2145
|
+
};
|
|
2146
|
+
return {
|
|
2147
|
+
status: "fail",
|
|
2148
|
+
message: "Cannot read subagent runner",
|
|
2149
|
+
details: error.message
|
|
2150
|
+
};
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
async function checkPlugin() {
|
|
2154
|
+
const pluginStatus = await getPluginStatus();
|
|
2155
|
+
if (!pluginStatus.installed) return {
|
|
2156
|
+
status: "warn",
|
|
2157
|
+
message: "Telemetry plugin not installed",
|
|
2158
|
+
details: "Subagent execution tracking will not work",
|
|
2159
|
+
fix: "Run \"jive init\" to install the plugin, or see SUBAGENT_VISIBILITY.md"
|
|
2160
|
+
};
|
|
2161
|
+
try {
|
|
2162
|
+
const pluginJsonPath = path.join(pluginStatus.pluginPath, ".claude-plugin", "plugin.json");
|
|
2163
|
+
await fs.access(pluginJsonPath);
|
|
2164
|
+
return {
|
|
2165
|
+
status: "pass",
|
|
2166
|
+
message: "Telemetry plugin installed",
|
|
2167
|
+
details: pluginStatus.pluginPath
|
|
2168
|
+
};
|
|
2169
|
+
} catch {
|
|
2170
|
+
return {
|
|
2171
|
+
status: "warn",
|
|
2172
|
+
message: "Plugin installation incomplete",
|
|
2173
|
+
details: "Plugin directory exists but files are missing",
|
|
2174
|
+
fix: "Run \"jive init\" to reinstall the plugin"
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
async function checkPluginConfig() {
|
|
2179
|
+
const pluginStatus = await getPluginStatus();
|
|
2180
|
+
if (!pluginStatus.configured) return {
|
|
2181
|
+
status: "warn",
|
|
2182
|
+
message: "Plugin not configured",
|
|
2183
|
+
details: "Missing .jive/config.json with telemetry settings",
|
|
2184
|
+
fix: "Run \"jive init\" to create plugin configuration"
|
|
2185
|
+
};
|
|
2186
|
+
try {
|
|
2187
|
+
const configContent = await fs.readFile(pluginStatus.configPath, "utf-8");
|
|
2188
|
+
const config = JSON.parse(configContent);
|
|
2189
|
+
if (!config.apiUrl || !config.apiKey) return {
|
|
2190
|
+
status: "warn",
|
|
2191
|
+
message: "Plugin config incomplete",
|
|
2192
|
+
details: "Missing apiUrl or apiKey",
|
|
2193
|
+
fix: "Run \"jive init\" to recreate plugin configuration"
|
|
2194
|
+
};
|
|
2195
|
+
return {
|
|
2196
|
+
status: "pass",
|
|
2197
|
+
message: "Plugin configured",
|
|
2198
|
+
details: `API: ${config.apiUrl}`
|
|
2199
|
+
};
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
return {
|
|
2202
|
+
status: "warn",
|
|
2203
|
+
message: "Invalid plugin config",
|
|
2204
|
+
details: error.message,
|
|
2205
|
+
fix: "Run \"jive init\" to recreate plugin configuration"
|
|
2206
|
+
};
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
async function checkApiConnectivity() {
|
|
2210
|
+
if (!await getCredentials()) return {
|
|
2211
|
+
status: "fail",
|
|
2212
|
+
message: "Cannot check API (not authenticated)",
|
|
2213
|
+
fix: "Run \"jive login\" first"
|
|
2214
|
+
};
|
|
2215
|
+
try {
|
|
2216
|
+
await getApiClient().getTeams();
|
|
2217
|
+
return {
|
|
2218
|
+
status: "pass",
|
|
2219
|
+
message: "API reachable",
|
|
2220
|
+
details: `Successfully connected to Jive API`
|
|
2221
|
+
};
|
|
2222
|
+
} catch (error) {
|
|
2223
|
+
return {
|
|
2224
|
+
status: "fail",
|
|
2225
|
+
message: "Cannot reach API",
|
|
2226
|
+
details: error.message,
|
|
2227
|
+
fix: "Check your internet connection and JIVE_API_URL environment variable"
|
|
2228
|
+
};
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
async function checkTeamMembership() {
|
|
2232
|
+
if (!await getCredentials()) return {
|
|
2233
|
+
status: "fail",
|
|
2234
|
+
message: "Cannot check teams (not authenticated)",
|
|
2235
|
+
fix: "Run \"jive login\" first"
|
|
2236
|
+
};
|
|
2237
|
+
try {
|
|
2238
|
+
const result = await getApiClient().getTeams();
|
|
2239
|
+
if (result.teams.length === 0) return {
|
|
2240
|
+
status: "warn",
|
|
2241
|
+
message: "Not a member of any teams",
|
|
2242
|
+
fix: "Run \"jive team create\" to create a team"
|
|
2243
|
+
};
|
|
2244
|
+
const teamNames = result.teams.map((t) => t.name).join(", ");
|
|
2245
|
+
return {
|
|
2246
|
+
status: "pass",
|
|
2247
|
+
message: `Member of ${result.teams.length} team(s)`,
|
|
2248
|
+
details: teamNames
|
|
2249
|
+
};
|
|
2250
|
+
} catch (error) {
|
|
2251
|
+
return {
|
|
2252
|
+
status: "fail",
|
|
2253
|
+
message: "Cannot fetch teams",
|
|
2254
|
+
details: error.message
|
|
2255
|
+
};
|
|
2256
|
+
}
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
//#endregion
|
|
2260
|
+
//#region src/index.ts
|
|
2261
|
+
const program = new Command();
|
|
2262
|
+
program.name("jive").description("CLI tool for managing MCP servers, tools, and subagents across teams").version("0.1.0");
|
|
2263
|
+
program.command("login").description("Authenticate with the Jive platform").action(loginCommand);
|
|
2264
|
+
program.command("signup").description("Create a new account").action(signupCommand);
|
|
2265
|
+
program.command("logout").description("Clear authentication credentials").action(logoutCommand);
|
|
2266
|
+
const team = program.command("team").description("Manage teams");
|
|
2267
|
+
team.command("create").description("Create a new team").action(teamCommands.create);
|
|
2268
|
+
team.command("list").description("List all teams you're a member of").action(teamCommands.list);
|
|
2269
|
+
team.command("switch").description("Switch active team context").action(teamCommands.switch);
|
|
2270
|
+
team.command("invite").description("Invite user to current team").argument("<email>", "Email address of user to invite").action(teamCommands.invite);
|
|
2271
|
+
program.command("init").description("Initialize Jive in current project").action(initCommand);
|
|
2272
|
+
program.command("detach").description("Remove Jive integrations from current project").action(detachCommand);
|
|
2273
|
+
program.command("doctor").description("Check Jive installation and configuration").action(doctorCommand);
|
|
2274
|
+
const subagents = program.command("subagents").description("Manage subagents");
|
|
2275
|
+
subagents.command("list").description("List all subagents for active team").action(subagentCommands.list);
|
|
2276
|
+
subagents.command("pull").description("Download team subagents to local .claude/agents/ directory").option("--overwrite", "Overwrite existing files").action(subagentCommands.pull);
|
|
2277
|
+
subagents.command("push").description("Upload local subagents to team").option("--force", "Force push without conflict checks").option("-m, --message <text>", "Change description for version history").action(subagentCommands.push);
|
|
2278
|
+
subagents.command("create").description("Create a new subagent interactively").action(subagentCommands.create);
|
|
2279
|
+
subagents.command("delete").description("Delete a subagent").argument("<name-or-id>", "Name or ID of subagent to delete").action(subagentCommands.delete);
|
|
2280
|
+
const mcp = program.command("mcp").description("Manage MCP servers");
|
|
2281
|
+
mcp.command("list").description("List all MCP servers for active team").action(mcpCommands.list);
|
|
2282
|
+
mcp.command("pull").description("Download team MCP servers to local .mcp.json").option("--merge", "Merge with local servers instead of replacing").action(mcpCommands.pull);
|
|
2283
|
+
mcp.command("push").description("Upload local MCP servers to team").option("--force", "Force push without conflict checks").action(mcpCommands.push);
|
|
2284
|
+
mcp.command("add").description("Add a new MCP server to team").argument("<name>", "Name of the MCP server").action(mcpCommands.add);
|
|
2285
|
+
mcp.command("remove").description("Remove an MCP server from team").argument("<name-or-id>", "Name or ID of MCP server to remove").action(mcpCommands.remove);
|
|
2286
|
+
mcp.command("start").description("Start the MCP server").action(mcpCommands.start);
|
|
2287
|
+
program.command("sync").description("Synchronize all resources between local and team").option("--dry-run", "Show what would be changed without making changes").action(syncCommand);
|
|
2288
|
+
program.command("status").description("Show sync status of local resources").action(statusCommand);
|
|
2289
|
+
program.parse();
|
|
2290
|
+
|
|
2291
|
+
//#endregion
|
|
2292
|
+
export { isProjectInitialized as a, saveCredentials as c, getProjectConfig as i, saveProjectConfig as l, getActiveTeamId as n, requireAuth as o, getCredentials as r, requireProjectConfig as s, clearCredentials as t, updateProjectConfig as u };
|