@indexnetwork/cli 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -0
- package/bin/index.cjs +110 -0
- package/dist/index.js +2040 -0
- package/package.json +30 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2040 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/main.ts
|
|
4
|
+
import { execFile } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
// src/args.parser.ts
|
|
7
|
+
var KNOWN_COMMANDS = new Set(["login", "logout", "profile", "intent", "opportunity", "network", "conversation", "help", "version"]);
|
|
8
|
+
var OPPORTUNITY_SUBCOMMANDS = new Set(["list", "show", "accept", "reject"]);
|
|
9
|
+
var NETWORK_SUBCOMMANDS = new Set(["list", "create", "show", "join", "leave", "invite"]);
|
|
10
|
+
var CONVERSATION_SUBCOMMANDS = new Set(["list", "with", "show", "send", "stream", "sessions", "help"]);
|
|
11
|
+
function parseArgs(args) {
|
|
12
|
+
const result = {
|
|
13
|
+
command: "help",
|
|
14
|
+
list: false
|
|
15
|
+
};
|
|
16
|
+
if (args.length === 0) {
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
const first = args[0];
|
|
20
|
+
if (first === "--help" || first === "-h") {
|
|
21
|
+
result.command = "help";
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
if (first === "--version" || first === "-v") {
|
|
25
|
+
result.command = "version";
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
if (!KNOWN_COMMANDS.has(first)) {
|
|
29
|
+
result.command = "unknown";
|
|
30
|
+
result.unknown = first;
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
result.command = first;
|
|
34
|
+
let i = 1;
|
|
35
|
+
const positionals = [];
|
|
36
|
+
while (i < args.length) {
|
|
37
|
+
const arg = args[i];
|
|
38
|
+
if (arg === "--list" || arg === "-l") {
|
|
39
|
+
result.list = true;
|
|
40
|
+
i++;
|
|
41
|
+
} else if (arg === "--session" || arg === "-s") {
|
|
42
|
+
result.sessionId = args[i + 1];
|
|
43
|
+
i += 2;
|
|
44
|
+
} else if (arg === "--api-url") {
|
|
45
|
+
result.apiUrl = args[i + 1];
|
|
46
|
+
i += 2;
|
|
47
|
+
} else if (arg === "--app-url") {
|
|
48
|
+
result.appUrl = args[i + 1];
|
|
49
|
+
i += 2;
|
|
50
|
+
} else if (arg === "--token" || arg === "-t") {
|
|
51
|
+
result.token = args[i + 1];
|
|
52
|
+
i += 2;
|
|
53
|
+
} else if (arg === "--archived") {
|
|
54
|
+
result.archived = true;
|
|
55
|
+
i++;
|
|
56
|
+
} else if (arg === "--status") {
|
|
57
|
+
result.status = args[i + 1];
|
|
58
|
+
i += 2;
|
|
59
|
+
} else if (arg === "--limit") {
|
|
60
|
+
result.limit = parseInt(args[i + 1], 10);
|
|
61
|
+
i += 2;
|
|
62
|
+
} else if (arg === "--prompt" || arg === "-p") {
|
|
63
|
+
result.prompt = args[i + 1];
|
|
64
|
+
i += 2;
|
|
65
|
+
} else if (arg.startsWith("--")) {
|
|
66
|
+
i++;
|
|
67
|
+
} else {
|
|
68
|
+
positionals.push(arg);
|
|
69
|
+
i++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (result.command === "opportunity") {
|
|
73
|
+
const sub = positionals[0];
|
|
74
|
+
if (sub && OPPORTUNITY_SUBCOMMANDS.has(sub)) {
|
|
75
|
+
result.subcommand = sub;
|
|
76
|
+
if (positionals[1]) {
|
|
77
|
+
result.targetId = positionals[1];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
if (result.command === "profile" && positionals.length > 0) {
|
|
83
|
+
const sub = positionals[0];
|
|
84
|
+
if (sub === "show") {
|
|
85
|
+
result.subcommand = "show";
|
|
86
|
+
if (positionals[1]) {
|
|
87
|
+
result.userId = positionals[1];
|
|
88
|
+
}
|
|
89
|
+
} else if (sub === "sync") {
|
|
90
|
+
result.subcommand = "sync";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (result.command === "intent") {
|
|
94
|
+
parseIntentArgs(positionals, result);
|
|
95
|
+
}
|
|
96
|
+
if (result.command === "network") {
|
|
97
|
+
if (positionals.length > 0 && NETWORK_SUBCOMMANDS.has(positionals[0])) {
|
|
98
|
+
result.subcommand = positionals[0];
|
|
99
|
+
result.positionals = positionals.slice(1);
|
|
100
|
+
} else if (positionals.length > 0) {
|
|
101
|
+
result.positionals = positionals;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (result.command === "conversation") {
|
|
105
|
+
if (positionals.length > 0 && CONVERSATION_SUBCOMMANDS.has(positionals[0])) {
|
|
106
|
+
result.subcommand = positionals[0];
|
|
107
|
+
result.positionals = positionals.slice(1);
|
|
108
|
+
} else if (positionals.length > 0) {
|
|
109
|
+
result.message = positionals.join(" ");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
var INTENT_SUBCOMMANDS = new Set(["list", "show", "create", "archive"]);
|
|
115
|
+
function parseIntentArgs(positionals, result) {
|
|
116
|
+
if (positionals.length === 0)
|
|
117
|
+
return;
|
|
118
|
+
const sub = positionals[0];
|
|
119
|
+
if (!INTENT_SUBCOMMANDS.has(sub))
|
|
120
|
+
return;
|
|
121
|
+
result.subcommand = sub;
|
|
122
|
+
const rest = positionals.slice(1);
|
|
123
|
+
switch (result.subcommand) {
|
|
124
|
+
case "show":
|
|
125
|
+
case "archive":
|
|
126
|
+
result.intentId = rest[0];
|
|
127
|
+
break;
|
|
128
|
+
case "create":
|
|
129
|
+
if (rest.length > 0) {
|
|
130
|
+
result.intentContent = rest.join(" ");
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/auth.store.ts
|
|
137
|
+
import { join } from "node:path";
|
|
138
|
+
import { homedir } from "node:os";
|
|
139
|
+
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises";
|
|
140
|
+
var CREDENTIALS_FILE = "credentials.json";
|
|
141
|
+
|
|
142
|
+
class CredentialStore {
|
|
143
|
+
dir;
|
|
144
|
+
filePath;
|
|
145
|
+
constructor(dir) {
|
|
146
|
+
this.dir = dir ?? join(homedir(), ".index");
|
|
147
|
+
this.filePath = join(this.dir, CREDENTIALS_FILE);
|
|
148
|
+
}
|
|
149
|
+
async load() {
|
|
150
|
+
try {
|
|
151
|
+
const raw = await readFile(this.filePath, "utf-8");
|
|
152
|
+
const parsed = JSON.parse(raw);
|
|
153
|
+
if (parsed.token && parsed.apiUrl) {
|
|
154
|
+
return parsed;
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async save(credentials) {
|
|
162
|
+
await mkdir(this.dir, { recursive: true });
|
|
163
|
+
await writeFile(this.filePath, JSON.stringify(credentials, null, 2), {
|
|
164
|
+
mode: 384
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async clear() {
|
|
168
|
+
try {
|
|
169
|
+
await unlink(this.filePath);
|
|
170
|
+
} catch {}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/api.client.ts
|
|
175
|
+
class ApiClient {
|
|
176
|
+
baseUrl;
|
|
177
|
+
token;
|
|
178
|
+
constructor(baseUrl, token) {
|
|
179
|
+
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
180
|
+
this.token = token;
|
|
181
|
+
}
|
|
182
|
+
async listSessions() {
|
|
183
|
+
const res = await this.get("/api/chat/sessions");
|
|
184
|
+
const body = await res.json();
|
|
185
|
+
return body.sessions;
|
|
186
|
+
}
|
|
187
|
+
async getMe() {
|
|
188
|
+
const res = await this.get("/api/auth/me");
|
|
189
|
+
const body = await res.json();
|
|
190
|
+
return body.user;
|
|
191
|
+
}
|
|
192
|
+
async getUser(userId) {
|
|
193
|
+
const res = await this.get(`/api/users/${userId}`);
|
|
194
|
+
const body = await res.json();
|
|
195
|
+
return body.user;
|
|
196
|
+
}
|
|
197
|
+
async syncProfile() {
|
|
198
|
+
const res = await this.post("/api/profiles/sync");
|
|
199
|
+
const body = await res.json();
|
|
200
|
+
return body;
|
|
201
|
+
}
|
|
202
|
+
async listOpportunities(opts) {
|
|
203
|
+
const params = new URLSearchParams;
|
|
204
|
+
if (opts?.status)
|
|
205
|
+
params.set("status", opts.status);
|
|
206
|
+
if (opts?.limit)
|
|
207
|
+
params.set("limit", String(opts.limit));
|
|
208
|
+
const qs = params.toString();
|
|
209
|
+
const path = qs ? `/api/opportunities?${qs}` : "/api/opportunities";
|
|
210
|
+
const res = await this.get(path);
|
|
211
|
+
const body = await res.json();
|
|
212
|
+
return body.opportunities;
|
|
213
|
+
}
|
|
214
|
+
async getOpportunity(id) {
|
|
215
|
+
const res = await this.get(`/api/opportunities/${id}`);
|
|
216
|
+
return await res.json();
|
|
217
|
+
}
|
|
218
|
+
async updateOpportunityStatus(id, status) {
|
|
219
|
+
const res = await this.patch(`/api/opportunities/${id}/status`, { status });
|
|
220
|
+
return await res.json();
|
|
221
|
+
}
|
|
222
|
+
async streamChat(params) {
|
|
223
|
+
const res = await fetch(`${this.baseUrl}/api/chat/stream`, {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
Authorization: `Bearer ${this.token}`
|
|
228
|
+
},
|
|
229
|
+
body: JSON.stringify({
|
|
230
|
+
message: params.message,
|
|
231
|
+
...params.sessionId ? { sessionId: params.sessionId } : {}
|
|
232
|
+
})
|
|
233
|
+
});
|
|
234
|
+
if (!res.ok) {
|
|
235
|
+
await this.handleError(res);
|
|
236
|
+
}
|
|
237
|
+
return res;
|
|
238
|
+
}
|
|
239
|
+
async listIntents(options = {}) {
|
|
240
|
+
const body = {};
|
|
241
|
+
if (options.limit !== undefined)
|
|
242
|
+
body.limit = options.limit;
|
|
243
|
+
if (options.archived !== undefined)
|
|
244
|
+
body.archived = options.archived;
|
|
245
|
+
if (options.sourceType !== undefined)
|
|
246
|
+
body.sourceType = options.sourceType;
|
|
247
|
+
if (options.page !== undefined)
|
|
248
|
+
body.page = options.page;
|
|
249
|
+
const res = await this.post("/api/intents/list", body);
|
|
250
|
+
return await res.json();
|
|
251
|
+
}
|
|
252
|
+
async getIntent(id) {
|
|
253
|
+
const res = await this.get(`/api/intents/${id}`);
|
|
254
|
+
const body = await res.json();
|
|
255
|
+
return body.intent;
|
|
256
|
+
}
|
|
257
|
+
async processIntent(content) {
|
|
258
|
+
const res = await this.post("/api/intents/process", { content });
|
|
259
|
+
return await res.json();
|
|
260
|
+
}
|
|
261
|
+
async archiveIntent(id) {
|
|
262
|
+
const res = await this.patch(`/api/intents/${id}/archive`);
|
|
263
|
+
return await res.json();
|
|
264
|
+
}
|
|
265
|
+
async listNetworks() {
|
|
266
|
+
const res = await this.get("/api/indexes");
|
|
267
|
+
const body = await res.json();
|
|
268
|
+
return body.indexes;
|
|
269
|
+
}
|
|
270
|
+
async createNetwork(title, prompt) {
|
|
271
|
+
const res = await this.post("/api/indexes", {
|
|
272
|
+
title,
|
|
273
|
+
...prompt ? { prompt } : {}
|
|
274
|
+
});
|
|
275
|
+
const body = await res.json();
|
|
276
|
+
return body.index;
|
|
277
|
+
}
|
|
278
|
+
async getNetwork(id) {
|
|
279
|
+
const res = await this.get(`/api/indexes/${id}`);
|
|
280
|
+
const body = await res.json();
|
|
281
|
+
return body.index;
|
|
282
|
+
}
|
|
283
|
+
async getNetworkMembers(id) {
|
|
284
|
+
const res = await this.get(`/api/indexes/${id}/members`);
|
|
285
|
+
const body = await res.json();
|
|
286
|
+
return body.members;
|
|
287
|
+
}
|
|
288
|
+
async joinNetwork(id) {
|
|
289
|
+
const res = await this.post(`/api/indexes/${id}/join`, {});
|
|
290
|
+
const body = await res.json();
|
|
291
|
+
return body.index;
|
|
292
|
+
}
|
|
293
|
+
async leaveNetwork(id) {
|
|
294
|
+
await this.post(`/api/indexes/${id}/leave`, {});
|
|
295
|
+
}
|
|
296
|
+
async searchUsers(query, indexId) {
|
|
297
|
+
const params = new URLSearchParams({ q: query });
|
|
298
|
+
if (indexId)
|
|
299
|
+
params.set("indexId", indexId);
|
|
300
|
+
const res = await this.get(`/api/indexes/search-users?${params.toString()}`);
|
|
301
|
+
const body = await res.json();
|
|
302
|
+
return body.users;
|
|
303
|
+
}
|
|
304
|
+
async addNetworkMember(networkId, userId) {
|
|
305
|
+
const res = await this.post(`/api/indexes/${networkId}/members`, { userId });
|
|
306
|
+
const body = await res.json();
|
|
307
|
+
return body;
|
|
308
|
+
}
|
|
309
|
+
async listConversations() {
|
|
310
|
+
const res = await this.get("/api/conversations");
|
|
311
|
+
const body = await res.json();
|
|
312
|
+
return body.conversations;
|
|
313
|
+
}
|
|
314
|
+
async getOrCreateDM(peerUserId) {
|
|
315
|
+
const res = await this.post("/api/conversations/dm", { peerUserId });
|
|
316
|
+
const body = await res.json();
|
|
317
|
+
return body.conversation;
|
|
318
|
+
}
|
|
319
|
+
async getMessages(conversationId, opts) {
|
|
320
|
+
const params = new URLSearchParams;
|
|
321
|
+
if (opts?.limit)
|
|
322
|
+
params.set("limit", String(opts.limit));
|
|
323
|
+
if (opts?.before)
|
|
324
|
+
params.set("before", opts.before);
|
|
325
|
+
const qs = params.toString();
|
|
326
|
+
const path = qs ? `/api/conversations/${conversationId}/messages?${qs}` : `/api/conversations/${conversationId}/messages`;
|
|
327
|
+
const res = await this.get(path);
|
|
328
|
+
const body = await res.json();
|
|
329
|
+
return body.messages;
|
|
330
|
+
}
|
|
331
|
+
async sendMessage(conversationId, text) {
|
|
332
|
+
const res = await this.post(`/api/conversations/${conversationId}/messages`, {
|
|
333
|
+
parts: [{ type: "text", text }]
|
|
334
|
+
});
|
|
335
|
+
const body = await res.json();
|
|
336
|
+
return body.message;
|
|
337
|
+
}
|
|
338
|
+
async hideConversation(conversationId) {
|
|
339
|
+
await this.del(`/api/conversations/${conversationId}`);
|
|
340
|
+
}
|
|
341
|
+
async streamConversationEvents() {
|
|
342
|
+
const res = await fetch(`${this.baseUrl}/api/conversations/stream`, {
|
|
343
|
+
headers: {
|
|
344
|
+
Authorization: `Bearer ${this.token}`,
|
|
345
|
+
Accept: "text/event-stream"
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) {
|
|
349
|
+
await this.handleError(res);
|
|
350
|
+
}
|
|
351
|
+
return res;
|
|
352
|
+
}
|
|
353
|
+
async get(path) {
|
|
354
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
355
|
+
headers: {
|
|
356
|
+
Authorization: `Bearer ${this.token}`
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
if (!res.ok) {
|
|
360
|
+
await this.handleError(res);
|
|
361
|
+
}
|
|
362
|
+
return res;
|
|
363
|
+
}
|
|
364
|
+
async post(path, body) {
|
|
365
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
366
|
+
method: "POST",
|
|
367
|
+
headers: {
|
|
368
|
+
"Content-Type": "application/json",
|
|
369
|
+
Authorization: `Bearer ${this.token}`
|
|
370
|
+
},
|
|
371
|
+
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
372
|
+
});
|
|
373
|
+
if (!res.ok) {
|
|
374
|
+
await this.handleError(res);
|
|
375
|
+
}
|
|
376
|
+
return res;
|
|
377
|
+
}
|
|
378
|
+
async patch(path, body) {
|
|
379
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
380
|
+
method: "PATCH",
|
|
381
|
+
headers: {
|
|
382
|
+
"Content-Type": "application/json",
|
|
383
|
+
Authorization: `Bearer ${this.token}`
|
|
384
|
+
},
|
|
385
|
+
...body !== undefined ? { body: JSON.stringify(body) } : {}
|
|
386
|
+
});
|
|
387
|
+
if (!res.ok) {
|
|
388
|
+
await this.handleError(res);
|
|
389
|
+
}
|
|
390
|
+
return res;
|
|
391
|
+
}
|
|
392
|
+
async del(path) {
|
|
393
|
+
const res = await fetch(`${this.baseUrl}${path}`, {
|
|
394
|
+
method: "DELETE",
|
|
395
|
+
headers: {
|
|
396
|
+
Authorization: `Bearer ${this.token}`
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
if (!res.ok) {
|
|
400
|
+
await this.handleError(res);
|
|
401
|
+
}
|
|
402
|
+
return res;
|
|
403
|
+
}
|
|
404
|
+
async handleError(res) {
|
|
405
|
+
if (res.status === 401) {
|
|
406
|
+
throw new Error("Session expired or invalid. Run `index login` to re-authenticate.");
|
|
407
|
+
}
|
|
408
|
+
let message = `HTTP ${res.status}`;
|
|
409
|
+
try {
|
|
410
|
+
const body = await res.json();
|
|
411
|
+
if (body.error)
|
|
412
|
+
message = body.error;
|
|
413
|
+
} catch {}
|
|
414
|
+
throw new Error(message);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/login.command.ts
|
|
419
|
+
import { createServer } from "node:http";
|
|
420
|
+
function closeServer(server) {
|
|
421
|
+
return new Promise((resolve) => {
|
|
422
|
+
server.close(() => resolve());
|
|
423
|
+
server.closeAllConnections();
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
async function handleLogin(apiUrl, appUrl, store, options = {}) {
|
|
427
|
+
const timeoutMs = options.timeoutMs ?? 120000;
|
|
428
|
+
const baseUrl = apiUrl.replace(/\/$/, "");
|
|
429
|
+
const baseAppUrl = appUrl.replace(/\/$/, "");
|
|
430
|
+
let resolveCallback;
|
|
431
|
+
const callbackPromise = new Promise((resolve) => {
|
|
432
|
+
resolveCallback = resolve;
|
|
433
|
+
});
|
|
434
|
+
const server = createServer(async (req, res) => {
|
|
435
|
+
const url = new URL(req.url ?? "/", `http://localhost`);
|
|
436
|
+
if (url.pathname === "/callback") {
|
|
437
|
+
const sessionToken = url.searchParams.get("session_token");
|
|
438
|
+
if (sessionToken) {
|
|
439
|
+
await store.save({ token: sessionToken, apiUrl: baseUrl });
|
|
440
|
+
resolveCallback({ success: true });
|
|
441
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
442
|
+
res.end(callbackHtml("CLI authorized", "You can close this window and return to the terminal."));
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
resolveCallback({
|
|
446
|
+
success: false,
|
|
447
|
+
error: "No session token received in callback."
|
|
448
|
+
});
|
|
449
|
+
res.writeHead(400, { "Content-Type": "text/html" });
|
|
450
|
+
res.end(callbackHtml("Authorization failed", "No session token received. Please try again."));
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
454
|
+
res.end("Not Found");
|
|
455
|
+
});
|
|
456
|
+
await new Promise((resolve) => {
|
|
457
|
+
server.listen(0, () => resolve());
|
|
458
|
+
});
|
|
459
|
+
const addr = server.address();
|
|
460
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
461
|
+
const callbackUrl = `http://localhost:${port}/callback`;
|
|
462
|
+
const authUrl = `${baseAppUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
463
|
+
const timeout = setTimeout(() => {
|
|
464
|
+
resolveCallback({
|
|
465
|
+
success: false,
|
|
466
|
+
error: "Login timed out. No callback received."
|
|
467
|
+
});
|
|
468
|
+
closeServer(server);
|
|
469
|
+
}, timeoutMs);
|
|
470
|
+
if (options.signal) {
|
|
471
|
+
options.signal.addEventListener("abort", () => {
|
|
472
|
+
clearTimeout(timeout);
|
|
473
|
+
closeServer(server);
|
|
474
|
+
resolveCallback({
|
|
475
|
+
success: false,
|
|
476
|
+
error: "Login cancelled."
|
|
477
|
+
});
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const wrappedPromise = callbackPromise.then(async (result) => {
|
|
481
|
+
clearTimeout(timeout);
|
|
482
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
483
|
+
await closeServer(server);
|
|
484
|
+
return result;
|
|
485
|
+
});
|
|
486
|
+
return {
|
|
487
|
+
authUrl,
|
|
488
|
+
port,
|
|
489
|
+
callbackPromise: wrappedPromise
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
function callbackHtml(title, message) {
|
|
493
|
+
return `<!DOCTYPE html>
|
|
494
|
+
<html lang="en">
|
|
495
|
+
<head>
|
|
496
|
+
<meta charset="utf-8" />
|
|
497
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
498
|
+
<title>${title} — Index</title>
|
|
499
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
500
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
501
|
+
<link href="https://fonts.googleapis.com/css2?family=Public+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
|
|
502
|
+
<style>
|
|
503
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
504
|
+
body {
|
|
505
|
+
font-family: 'Public Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
506
|
+
background: #FDFDFD;
|
|
507
|
+
color: #111;
|
|
508
|
+
display: flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
justify-content: center;
|
|
511
|
+
min-height: 100vh;
|
|
512
|
+
}
|
|
513
|
+
.card {
|
|
514
|
+
text-align: center;
|
|
515
|
+
max-width: 400px;
|
|
516
|
+
padding: 2rem;
|
|
517
|
+
}
|
|
518
|
+
h1 {
|
|
519
|
+
font-size: 1.25rem;
|
|
520
|
+
font-weight: 600;
|
|
521
|
+
margin-bottom: 0.5rem;
|
|
522
|
+
}
|
|
523
|
+
p {
|
|
524
|
+
font-size: 0.875rem;
|
|
525
|
+
color: #666;
|
|
526
|
+
line-height: 1.5;
|
|
527
|
+
}
|
|
528
|
+
</style>
|
|
529
|
+
</head>
|
|
530
|
+
<body>
|
|
531
|
+
<div class="card">
|
|
532
|
+
<h1>${title}</h1>
|
|
533
|
+
<p>${message}</p>
|
|
534
|
+
</div>
|
|
535
|
+
</body>
|
|
536
|
+
</html>`;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// src/output/base.ts
|
|
540
|
+
var RESET = "\x1B[0m";
|
|
541
|
+
var BOLD = "\x1B[1m";
|
|
542
|
+
var DIM = "\x1B[2m";
|
|
543
|
+
var ITALIC = "\x1B[3m";
|
|
544
|
+
var RED = "\x1B[31m";
|
|
545
|
+
var GREEN = "\x1B[32m";
|
|
546
|
+
var YELLOW = "\x1B[33m";
|
|
547
|
+
var BLUE = "\x1B[34m";
|
|
548
|
+
var MAGENTA = "\x1B[35m";
|
|
549
|
+
var CYAN = "\x1B[36m";
|
|
550
|
+
var WHITE = "\x1B[37m";
|
|
551
|
+
var GRAY = "\x1B[90m";
|
|
552
|
+
var ORANGE = "\x1B[38;5;208m";
|
|
553
|
+
var AGENT_TEXT = "\x1B[38;5;252m";
|
|
554
|
+
var USER_PROMPT = CYAN;
|
|
555
|
+
function error(message, exitCode) {
|
|
556
|
+
console.error(`${RED}${BOLD}error${RESET}${RED}: ${message}${RESET}`);
|
|
557
|
+
if (exitCode !== undefined) {
|
|
558
|
+
process.exit(exitCode);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function success(message) {
|
|
562
|
+
console.log(`${GREEN}${BOLD}+${RESET} ${message}`);
|
|
563
|
+
}
|
|
564
|
+
function info(message) {
|
|
565
|
+
console.log(`${CYAN}${message}${RESET}`);
|
|
566
|
+
}
|
|
567
|
+
function dim(message) {
|
|
568
|
+
console.log(`${GRAY}${message}${RESET}`);
|
|
569
|
+
}
|
|
570
|
+
function heading(message) {
|
|
571
|
+
console.log(`
|
|
572
|
+
${BOLD}${message}${RESET}`);
|
|
573
|
+
}
|
|
574
|
+
function chatHeader() {
|
|
575
|
+
console.log();
|
|
576
|
+
console.log(` ${BOLD}${CYAN}I N D E X${RESET} ${GRAY}chat${RESET}`);
|
|
577
|
+
console.log(` ${GRAY}─────────────────────────────────────${RESET}`);
|
|
578
|
+
console.log(` ${GRAY}Type your message. "exit" or Ctrl+C to quit.${RESET}`);
|
|
579
|
+
console.log();
|
|
580
|
+
}
|
|
581
|
+
var PROMPT_STR = `${USER_PROMPT}${BOLD}> ${RESET}`;
|
|
582
|
+
function status(message) {
|
|
583
|
+
process.stderr.write(`\r ${GRAY}${message}${RESET}\x1B[K`);
|
|
584
|
+
}
|
|
585
|
+
function clearStatus() {
|
|
586
|
+
process.stderr.write("\r\x1B[K");
|
|
587
|
+
}
|
|
588
|
+
function toolActivity(description) {
|
|
589
|
+
process.stderr.write(`\r\x1B[K${ORANGE}> ${description}${RESET}
|
|
590
|
+
`);
|
|
591
|
+
}
|
|
592
|
+
var TOOL_DESCRIPTIONS = {
|
|
593
|
+
read_user_profiles: "Reading your profile...",
|
|
594
|
+
create_user_profile: "Creating your profile...",
|
|
595
|
+
update_user_profile: "Updating your profile...",
|
|
596
|
+
read_intents: "Fetching your active signals...",
|
|
597
|
+
create_intent: "Creating a new signal...",
|
|
598
|
+
update_intent: "Updating signal...",
|
|
599
|
+
delete_intent: "Removing signal...",
|
|
600
|
+
create_intent_index: "Saving signal to index...",
|
|
601
|
+
read_intent_indexes: "Fetching signals in index...",
|
|
602
|
+
delete_intent_index: "Removing signal from index...",
|
|
603
|
+
read_indexes: "Checking your indexes...",
|
|
604
|
+
create_index: "Creating a new index...",
|
|
605
|
+
update_index: "Updating index...",
|
|
606
|
+
delete_index: "Deleting index...",
|
|
607
|
+
create_index_membership: "Adding member to index...",
|
|
608
|
+
read_index_memberships: "Fetching index memberships...",
|
|
609
|
+
create_opportunities: "Searching for relevant connections...",
|
|
610
|
+
list_my_opportunities: "Listing your opportunities...",
|
|
611
|
+
update_opportunity: "Updating opportunity status...",
|
|
612
|
+
scrape_url: "Reading content from URL...",
|
|
613
|
+
read_docs: "Looking up documentation...",
|
|
614
|
+
import_gmail_contacts: "Importing Gmail contacts...",
|
|
615
|
+
import_contacts: "Importing contacts...",
|
|
616
|
+
list_contacts: "Listing your contacts...",
|
|
617
|
+
add_contact: "Adding contact...",
|
|
618
|
+
remove_contact: "Removing contact...",
|
|
619
|
+
send_opportunity: "Sending opportunity..."
|
|
620
|
+
};
|
|
621
|
+
function humanizeToolName(name) {
|
|
622
|
+
return TOOL_DESCRIPTIONS[name] ?? name.replace(/_/g, " ") + "...";
|
|
623
|
+
}
|
|
624
|
+
function wordWrap(text, maxWidth) {
|
|
625
|
+
const lines = [];
|
|
626
|
+
const words = text.split(/\s+/);
|
|
627
|
+
let current = "";
|
|
628
|
+
for (const word of words) {
|
|
629
|
+
if (current.length + word.length + 1 > maxWidth && current.length > 0) {
|
|
630
|
+
lines.push(current);
|
|
631
|
+
current = word;
|
|
632
|
+
} else {
|
|
633
|
+
current = current ? `${current} ${word}` : word;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (current)
|
|
637
|
+
lines.push(current);
|
|
638
|
+
return lines;
|
|
639
|
+
}
|
|
640
|
+
function confidenceBar(confidence) {
|
|
641
|
+
const pct = Math.round(confidence);
|
|
642
|
+
const filled = Math.round(pct / 10);
|
|
643
|
+
const empty = 10 - filled;
|
|
644
|
+
return `${GREEN}${"#".repeat(filled)}${GRAY}${"-".repeat(empty)}${RESET} ${GRAY}${pct}%`;
|
|
645
|
+
}
|
|
646
|
+
function padTo(cellWidth, plainText) {
|
|
647
|
+
const remaining = Math.max(0, cellWidth - plainText.length);
|
|
648
|
+
return " ".repeat(remaining);
|
|
649
|
+
}
|
|
650
|
+
function stripAnsi(str) {
|
|
651
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
652
|
+
}
|
|
653
|
+
// src/output/markdown.ts
|
|
654
|
+
class MarkdownRenderer {
|
|
655
|
+
buffer = "";
|
|
656
|
+
inCodeBlock = false;
|
|
657
|
+
codeBlockLang = "";
|
|
658
|
+
codeBlockContent = "";
|
|
659
|
+
pristine = true;
|
|
660
|
+
write(text) {
|
|
661
|
+
this.buffer += text;
|
|
662
|
+
this.flush();
|
|
663
|
+
}
|
|
664
|
+
flush() {
|
|
665
|
+
while (this.buffer.length > 0) {
|
|
666
|
+
if (this.inCodeBlock) {
|
|
667
|
+
const endIdx = this.buffer.indexOf("```");
|
|
668
|
+
if (endIdx !== -1) {
|
|
669
|
+
this.codeBlockContent += this.buffer.slice(0, endIdx);
|
|
670
|
+
this.buffer = this.buffer.slice(endIdx + 3);
|
|
671
|
+
if (this.buffer.startsWith(`
|
|
672
|
+
`)) {
|
|
673
|
+
this.buffer = this.buffer.slice(1);
|
|
674
|
+
}
|
|
675
|
+
this.renderCodeBlock();
|
|
676
|
+
this.inCodeBlock = false;
|
|
677
|
+
this.codeBlockLang = "";
|
|
678
|
+
this.codeBlockContent = "";
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
this.codeBlockContent += this.buffer;
|
|
682
|
+
this.buffer = "";
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
const codeBlockMatch = this.buffer.match(/^```([^\n]*)\n/);
|
|
686
|
+
if (codeBlockMatch) {
|
|
687
|
+
this.codeBlockLang = codeBlockMatch[1].trim();
|
|
688
|
+
this.buffer = this.buffer.slice(codeBlockMatch[0].length);
|
|
689
|
+
this.inCodeBlock = true;
|
|
690
|
+
this.codeBlockContent = "";
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (this.buffer.startsWith("```")) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
const nlIdx = this.buffer.indexOf(`
|
|
697
|
+
`);
|
|
698
|
+
if (nlIdx === -1) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
const line = this.buffer.slice(0, nlIdx);
|
|
702
|
+
this.buffer = this.buffer.slice(nlIdx + 1);
|
|
703
|
+
this.emitLine(line);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
emitLine(line) {
|
|
707
|
+
if (this.pristine) {
|
|
708
|
+
process.stdout.write(`
|
|
709
|
+
`);
|
|
710
|
+
this.pristine = false;
|
|
711
|
+
}
|
|
712
|
+
const rendered = this.renderLine(line);
|
|
713
|
+
process.stdout.write(rendered + `
|
|
714
|
+
`);
|
|
715
|
+
}
|
|
716
|
+
renderLine(line) {
|
|
717
|
+
const bulletMatch = line.match(/^(\s*)([-*])\s+(.*)$/);
|
|
718
|
+
if (bulletMatch) {
|
|
719
|
+
const [, indent, , content] = bulletMatch;
|
|
720
|
+
return `${indent} ${CYAN}*${RESET} ${this.renderInline(content)}`;
|
|
721
|
+
}
|
|
722
|
+
const numMatch = line.match(/^(\s*)(\d+)\.\s+(.*)$/);
|
|
723
|
+
if (numMatch) {
|
|
724
|
+
const [, indent, num, content] = numMatch;
|
|
725
|
+
return `${indent} ${CYAN}${num}.${RESET} ${this.renderInline(content)}`;
|
|
726
|
+
}
|
|
727
|
+
if (line.startsWith("### ")) {
|
|
728
|
+
return `${BOLD}${WHITE}${line.slice(4)}${RESET}`;
|
|
729
|
+
}
|
|
730
|
+
if (line.startsWith("## ")) {
|
|
731
|
+
return `${BOLD}${WHITE}${line.slice(3)}${RESET}`;
|
|
732
|
+
}
|
|
733
|
+
if (line.startsWith("# ")) {
|
|
734
|
+
return `${BOLD}${WHITE}${line.slice(2)}${RESET}`;
|
|
735
|
+
}
|
|
736
|
+
return this.renderInline(line);
|
|
737
|
+
}
|
|
738
|
+
renderInline(text) {
|
|
739
|
+
text = text.replace(/\*\*\*(.+?)\*\*\*/g, `${BOLD}${ITALIC}${WHITE}$1${RESET}${AGENT_TEXT}`);
|
|
740
|
+
text = text.replace(/\*\*(.+?)\*\*/g, `${BOLD}${WHITE}$1${RESET}${AGENT_TEXT}`);
|
|
741
|
+
text = text.replace(/(?<!\*)\*([^*]+?)\*(?!\*)/g, `${ITALIC}$1${RESET}${AGENT_TEXT}`);
|
|
742
|
+
text = text.replace(/`([^`]+)`/g, `${CYAN}$1${RESET}${AGENT_TEXT}`);
|
|
743
|
+
return `${AGENT_TEXT}${text}${RESET}`;
|
|
744
|
+
}
|
|
745
|
+
renderCodeBlock() {
|
|
746
|
+
const lang = this.codeBlockLang;
|
|
747
|
+
const content = this.codeBlockContent.trimEnd();
|
|
748
|
+
if (!this.pristine) {
|
|
749
|
+
process.stdout.write(`
|
|
750
|
+
`);
|
|
751
|
+
}
|
|
752
|
+
this.pristine = false;
|
|
753
|
+
if (lang === "intent_proposal") {
|
|
754
|
+
this.renderIntentProposal(content);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (lang === "opportunity") {
|
|
758
|
+
this.renderOpportunity(content);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
const border = `${GRAY}|${RESET}`;
|
|
762
|
+
if (lang) {
|
|
763
|
+
process.stdout.write(` ${GRAY}--- ${lang} ${"─".repeat(Math.max(0, 40 - lang.length))}${RESET}
|
|
764
|
+
`);
|
|
765
|
+
} else {
|
|
766
|
+
process.stdout.write(` ${GRAY}${"─".repeat(46)}${RESET}
|
|
767
|
+
`);
|
|
768
|
+
}
|
|
769
|
+
for (const line of content.split(`
|
|
770
|
+
`)) {
|
|
771
|
+
process.stdout.write(` ${border} ${CYAN}${line}${RESET}
|
|
772
|
+
`);
|
|
773
|
+
}
|
|
774
|
+
process.stdout.write(` ${GRAY}${"─".repeat(46)}${RESET}
|
|
775
|
+
|
|
776
|
+
`);
|
|
777
|
+
}
|
|
778
|
+
renderIntentProposal(content) {
|
|
779
|
+
try {
|
|
780
|
+
const data = JSON.parse(content);
|
|
781
|
+
const desc = data.description ?? content;
|
|
782
|
+
const confidence = data.confidence;
|
|
783
|
+
process.stdout.write(` ${MAGENTA}+${"─".repeat(56)}+${RESET}
|
|
784
|
+
`);
|
|
785
|
+
process.stdout.write(` ${MAGENTA}|${RESET} ${BOLD}${MAGENTA}Signal Proposal${RESET}${" ".repeat(40)}${MAGENTA}|${RESET}
|
|
786
|
+
`);
|
|
787
|
+
process.stdout.write(` ${MAGENTA}|${RESET}${" ".repeat(56)}${MAGENTA}|${RESET}
|
|
788
|
+
`);
|
|
789
|
+
const wrapped = wordWrap(desc, 52);
|
|
790
|
+
for (const line of wrapped) {
|
|
791
|
+
const pad = Math.max(0, 54 - line.length);
|
|
792
|
+
process.stdout.write(` ${MAGENTA}|${RESET} ${AGENT_TEXT}${line}${RESET}${" ".repeat(pad)}${MAGENTA}|${RESET}
|
|
793
|
+
`);
|
|
794
|
+
}
|
|
795
|
+
if (confidence !== undefined) {
|
|
796
|
+
process.stdout.write(` ${MAGENTA}|${RESET}${" ".repeat(56)}${MAGENTA}|${RESET}
|
|
797
|
+
`);
|
|
798
|
+
const bar = confidenceBar(confidence);
|
|
799
|
+
process.stdout.write(` ${MAGENTA}|${RESET} ${GRAY}Confidence: ${bar}${RESET}${" ".repeat(24)}${MAGENTA}|${RESET}
|
|
800
|
+
`);
|
|
801
|
+
}
|
|
802
|
+
process.stdout.write(` ${MAGENTA}+${"─".repeat(56)}+${RESET}
|
|
803
|
+
|
|
804
|
+
`);
|
|
805
|
+
} catch {
|
|
806
|
+
process.stdout.write(` ${GRAY}${content}${RESET}
|
|
807
|
+
|
|
808
|
+
`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
renderOpportunity(content) {
|
|
812
|
+
try {
|
|
813
|
+
const data = JSON.parse(content);
|
|
814
|
+
process.stdout.write(` ${BLUE}+${"─".repeat(56)}+${RESET}
|
|
815
|
+
`);
|
|
816
|
+
process.stdout.write(` ${BLUE}|${RESET} ${BOLD}${BLUE}Opportunity${RESET}${" ".repeat(44)}${BLUE}|${RESET}
|
|
817
|
+
`);
|
|
818
|
+
if (data.title) {
|
|
819
|
+
const pad = Math.max(0, 54 - data.title.length);
|
|
820
|
+
process.stdout.write(` ${BLUE}|${RESET} ${BOLD}${data.title}${RESET}${" ".repeat(pad)}${BLUE}|${RESET}
|
|
821
|
+
`);
|
|
822
|
+
}
|
|
823
|
+
if (data.description) {
|
|
824
|
+
process.stdout.write(` ${BLUE}|${RESET}${" ".repeat(56)}${BLUE}|${RESET}
|
|
825
|
+
`);
|
|
826
|
+
const wrapped = wordWrap(data.description, 52);
|
|
827
|
+
for (const line of wrapped) {
|
|
828
|
+
const pad = Math.max(0, 54 - line.length);
|
|
829
|
+
process.stdout.write(` ${BLUE}|${RESET} ${AGENT_TEXT}${line}${RESET}${" ".repeat(pad)}${BLUE}|${RESET}
|
|
830
|
+
`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
process.stdout.write(` ${BLUE}+${"─".repeat(56)}+${RESET}
|
|
834
|
+
|
|
835
|
+
`);
|
|
836
|
+
} catch {
|
|
837
|
+
process.stdout.write(` ${GRAY}${content}${RESET}
|
|
838
|
+
|
|
839
|
+
`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
reset(reason) {
|
|
843
|
+
if (this.buffer.length > 0 || this.inCodeBlock) {
|
|
844
|
+
this.buffer = "";
|
|
845
|
+
this.inCodeBlock = false;
|
|
846
|
+
this.codeBlockContent = "";
|
|
847
|
+
this.codeBlockLang = "";
|
|
848
|
+
}
|
|
849
|
+
if (!this.pristine) {
|
|
850
|
+
process.stdout.write(`
|
|
851
|
+
${GRAY}--- retrying${reason ? `: ${reason}` : ""} ---${RESET}
|
|
852
|
+
`);
|
|
853
|
+
}
|
|
854
|
+
this.pristine = true;
|
|
855
|
+
}
|
|
856
|
+
finalize() {
|
|
857
|
+
if (this.inCodeBlock) {
|
|
858
|
+
this.renderCodeBlock();
|
|
859
|
+
this.inCodeBlock = false;
|
|
860
|
+
}
|
|
861
|
+
if (this.buffer.length > 0) {
|
|
862
|
+
this.emitLine(this.buffer);
|
|
863
|
+
this.buffer = "";
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
// src/output/formatters.ts
|
|
868
|
+
function profileCard(data) {
|
|
869
|
+
const lines = [];
|
|
870
|
+
const W = 56;
|
|
871
|
+
const border = (ch) => `${CYAN}${ch}${RESET}`;
|
|
872
|
+
const hline = ` ${border("+")}${CYAN}${"─".repeat(W)}${RESET}${border("+")}`;
|
|
873
|
+
lines.push("");
|
|
874
|
+
lines.push(hline);
|
|
875
|
+
const displayName = data.name ?? "(unnamed)";
|
|
876
|
+
const ghostTag = data.isGhost ? ` ${YELLOW}[ghost]${RESET}` : "";
|
|
877
|
+
const nameContent = `${BOLD}${WHITE}${displayName}${RESET}${ghostTag}`;
|
|
878
|
+
lines.push(` ${border("|")} ${nameContent}${padTo(W - 2, stripAnsi(displayName + (data.isGhost ? " [ghost]" : "")))}${border("|")}`);
|
|
879
|
+
if (data.intro) {
|
|
880
|
+
lines.push(` ${border("|")}${" ".repeat(W)}${border("|")}`);
|
|
881
|
+
const wrapped = wordWrap(data.intro, W - 4);
|
|
882
|
+
for (const line of wrapped) {
|
|
883
|
+
lines.push(` ${border("|")} ${AGENT_TEXT}${line}${RESET}${padTo(W - 2, line)}${border("|")}`);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (data.location) {
|
|
887
|
+
lines.push(` ${border("|")}${" ".repeat(W)}${border("|")}`);
|
|
888
|
+
const locLine = `Location: ${data.location}`;
|
|
889
|
+
lines.push(` ${border("|")} ${GRAY}${locLine}${RESET}${padTo(W - 2, locLine)}${border("|")}`);
|
|
890
|
+
}
|
|
891
|
+
if (data.socials && Object.keys(data.socials).length > 0) {
|
|
892
|
+
lines.push(` ${border("|")}${" ".repeat(W)}${border("|")}`);
|
|
893
|
+
for (const [platform, url] of Object.entries(data.socials)) {
|
|
894
|
+
const socialLine = `${platform}: ${url}`;
|
|
895
|
+
lines.push(` ${border("|")} ${BLUE}${socialLine}${RESET}${padTo(W - 2, socialLine)}${border("|")}`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const since = new Date(data.createdAt).toLocaleDateString("en-US", {
|
|
899
|
+
year: "numeric",
|
|
900
|
+
month: "short",
|
|
901
|
+
day: "numeric"
|
|
902
|
+
});
|
|
903
|
+
const sinceLine = `Member since ${since}`;
|
|
904
|
+
lines.push(` ${border("|")}${" ".repeat(W)}${border("|")}`);
|
|
905
|
+
lines.push(` ${border("|")} ${DIM}${sinceLine}${RESET}${padTo(W - 2, sinceLine)}${border("|")}`);
|
|
906
|
+
lines.push(hline);
|
|
907
|
+
lines.push("");
|
|
908
|
+
const output = lines.join(`
|
|
909
|
+
`);
|
|
910
|
+
console.log(output);
|
|
911
|
+
return output;
|
|
912
|
+
}
|
|
913
|
+
function sessionTable(sessions) {
|
|
914
|
+
if (sessions.length === 0) {
|
|
915
|
+
dim(" No chat sessions found.");
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const idWidth = 36;
|
|
919
|
+
const titleWidth = 40;
|
|
920
|
+
const dateWidth = 20;
|
|
921
|
+
console.log(` ${BOLD}${"ID".padEnd(idWidth)} ${"Title".padEnd(titleWidth)} ${"Created".padEnd(dateWidth)}${RESET}`);
|
|
922
|
+
console.log(` ${GRAY}${"-".repeat(idWidth)} ${"-".repeat(titleWidth)} ${"-".repeat(dateWidth)}${RESET}`);
|
|
923
|
+
for (const s of sessions) {
|
|
924
|
+
const title = (s.title ?? "(untitled)").slice(0, titleWidth);
|
|
925
|
+
const date = new Date(s.createdAt).toLocaleDateString("en-US", {
|
|
926
|
+
year: "numeric",
|
|
927
|
+
month: "short",
|
|
928
|
+
day: "numeric",
|
|
929
|
+
hour: "2-digit",
|
|
930
|
+
minute: "2-digit"
|
|
931
|
+
});
|
|
932
|
+
console.log(` ${GRAY}${s.id.padEnd(idWidth)}${RESET} ${title.padEnd(titleWidth)} ${GRAY}${date}${RESET}`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
function intentTable(intents) {
|
|
936
|
+
if (intents.length === 0) {
|
|
937
|
+
dim(" No signals found.");
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
const descWidth = 50;
|
|
941
|
+
const statusWidth = 10;
|
|
942
|
+
const sourceWidth = 16;
|
|
943
|
+
const dateWidth = 20;
|
|
944
|
+
console.log(` ${BOLD}${"Signal".padEnd(descWidth)} ${"Status".padEnd(statusWidth)} ${"Source".padEnd(sourceWidth)} ${"Created".padEnd(dateWidth)}${RESET}`);
|
|
945
|
+
console.log(` ${GRAY}${"-".repeat(descWidth)} ${"-".repeat(statusWidth)} ${"-".repeat(sourceWidth)} ${"-".repeat(dateWidth)}${RESET}`);
|
|
946
|
+
for (const intent of intents) {
|
|
947
|
+
const desc = (intent.summary ?? intent.payload).slice(0, descWidth);
|
|
948
|
+
const status2 = (intent.status ?? "").padEnd(statusWidth);
|
|
949
|
+
const source = (intent.sourceType ?? "-").padEnd(sourceWidth);
|
|
950
|
+
const date = new Date(intent.createdAt).toLocaleDateString("en-US", {
|
|
951
|
+
year: "numeric",
|
|
952
|
+
month: "short",
|
|
953
|
+
day: "numeric"
|
|
954
|
+
});
|
|
955
|
+
const sColor = intent.status === "ACTIVE" ? GREEN : GRAY;
|
|
956
|
+
console.log(` ${desc.padEnd(descWidth)} ${sColor}${status2}${RESET} ${GRAY}${source}${RESET} ${GRAY}${date}${RESET}`);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
function intentCard(intent) {
|
|
960
|
+
console.log();
|
|
961
|
+
console.log(` ${BOLD}${CYAN}Signal Details${RESET}`);
|
|
962
|
+
console.log(` ${GRAY}${"─".repeat(50)}${RESET}`);
|
|
963
|
+
console.log(` ${BOLD}ID${RESET} ${GRAY}${intent.id}${RESET}`);
|
|
964
|
+
console.log(` ${BOLD}Status${RESET} ${intent.status === "ACTIVE" ? GREEN : GRAY}${intent.status}${RESET}`);
|
|
965
|
+
if (intent.summary) {
|
|
966
|
+
console.log(` ${BOLD}Summary${RESET} ${intent.summary}`);
|
|
967
|
+
}
|
|
968
|
+
console.log();
|
|
969
|
+
console.log(` ${BOLD}Description${RESET}`);
|
|
970
|
+
console.log(` ${intent.payload}`);
|
|
971
|
+
if (intent.speechActType) {
|
|
972
|
+
console.log();
|
|
973
|
+
console.log(` ${BOLD}Speech Act${RESET} ${intent.speechActType}`);
|
|
974
|
+
}
|
|
975
|
+
if (intent.intentMode) {
|
|
976
|
+
console.log(` ${BOLD}Mode${RESET} ${intent.intentMode}`);
|
|
977
|
+
}
|
|
978
|
+
if (intent.sourceType) {
|
|
979
|
+
console.log(` ${BOLD}Source${RESET} ${intent.sourceType}`);
|
|
980
|
+
}
|
|
981
|
+
if (intent.confidence !== undefined) {
|
|
982
|
+
console.log(` ${BOLD}Confidence${RESET} ${confidenceBar(intent.confidence)}`);
|
|
983
|
+
}
|
|
984
|
+
if (intent.semanticEntropy !== undefined) {
|
|
985
|
+
console.log(` ${BOLD}Entropy${RESET} ${intent.semanticEntropy.toFixed(2)}`);
|
|
986
|
+
}
|
|
987
|
+
if (intent.isIncognito) {
|
|
988
|
+
console.log(` ${BOLD}Incognito${RESET} ${YELLOW}Yes${RESET}`);
|
|
989
|
+
}
|
|
990
|
+
console.log();
|
|
991
|
+
console.log(` ${BOLD}Created${RESET} ${GRAY}${new Date(intent.createdAt).toLocaleString()}${RESET}`);
|
|
992
|
+
console.log(` ${BOLD}Updated${RESET} ${GRAY}${new Date(intent.updatedAt).toLocaleString()}${RESET}`);
|
|
993
|
+
if (intent.archivedAt) {
|
|
994
|
+
console.log(` ${BOLD}Archived${RESET} ${GRAY}${new Date(intent.archivedAt).toLocaleString()}${RESET}`);
|
|
995
|
+
}
|
|
996
|
+
if (intent.indexes && intent.indexes.length > 0) {
|
|
997
|
+
console.log();
|
|
998
|
+
console.log(` ${BOLD}Index Assignments${RESET}`);
|
|
999
|
+
for (const idx of intent.indexes) {
|
|
1000
|
+
const score = idx.relevancyScore !== undefined ? ` (${idx.relevancyScore.toFixed(2)})` : "";
|
|
1001
|
+
console.log(` ${CYAN}*${RESET} ${idx.title}${GRAY}${score}${RESET}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
console.log(` ${GRAY}${"─".repeat(50)}${RESET}`);
|
|
1005
|
+
console.log();
|
|
1006
|
+
}
|
|
1007
|
+
var ROLE_LABELS = {
|
|
1008
|
+
agent: { label: "Helper", color: GREEN },
|
|
1009
|
+
patient: { label: "Seeker", color: YELLOW },
|
|
1010
|
+
peer: { label: "Peer", color: CYAN }
|
|
1011
|
+
};
|
|
1012
|
+
function roleLabel(role) {
|
|
1013
|
+
const entry = role ? ROLE_LABELS[role] : undefined;
|
|
1014
|
+
if (!entry)
|
|
1015
|
+
return `${GRAY}Unknown${RESET}`;
|
|
1016
|
+
return `${entry.color}${entry.label}${RESET}`;
|
|
1017
|
+
}
|
|
1018
|
+
function opportunityTable(opportunities) {
|
|
1019
|
+
if (opportunities.length === 0) {
|
|
1020
|
+
dim(" No opportunities found.");
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const nameW = 24;
|
|
1024
|
+
const catW = 20;
|
|
1025
|
+
const statusW = 10;
|
|
1026
|
+
const confW = 8;
|
|
1027
|
+
const dateW = 20;
|
|
1028
|
+
process.stdout.write(` ${BOLD}${"Counterparty".padEnd(nameW)} ${"Category".padEnd(catW)} ${"Status".padEnd(statusW)} ${"Conf".padEnd(confW)} ${"Created".padEnd(dateW)}${RESET}
|
|
1029
|
+
`);
|
|
1030
|
+
process.stdout.write(` ${GRAY}${"-".repeat(nameW)} ${"-".repeat(catW)} ${"-".repeat(statusW)} ${"-".repeat(confW)} ${"-".repeat(dateW)}${RESET}
|
|
1031
|
+
`);
|
|
1032
|
+
for (const opp of opportunities) {
|
|
1033
|
+
const name = (opp.counterpartName ?? "Unknown").slice(0, nameW);
|
|
1034
|
+
const category = (opp.interpretation?.category ?? "-").slice(0, catW);
|
|
1035
|
+
const st = opp.status.slice(0, statusW);
|
|
1036
|
+
const conf = opp.interpretation?.confidence != null ? `${opp.interpretation.confidence}%` : "-";
|
|
1037
|
+
const date = opp.createdAt ? new Date(opp.createdAt).toLocaleDateString("en-US", {
|
|
1038
|
+
year: "numeric",
|
|
1039
|
+
month: "short",
|
|
1040
|
+
day: "numeric"
|
|
1041
|
+
}) : "-";
|
|
1042
|
+
process.stdout.write(` ${name.padEnd(nameW)} ${GRAY}${category.padEnd(catW)}${RESET} ${statusColor(st)}${st.padEnd(statusW)}${RESET} ${GRAY}${conf.padEnd(confW)}${RESET} ${GRAY}${date.padEnd(dateW)}${RESET}
|
|
1043
|
+
`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function opportunityCard(opp) {
|
|
1047
|
+
const width = 58;
|
|
1048
|
+
const innerWidth = width - 2;
|
|
1049
|
+
process.stdout.write(`
|
|
1050
|
+
${BLUE}+${"─".repeat(width)}+${RESET}
|
|
1051
|
+
`);
|
|
1052
|
+
process.stdout.write(` ${BLUE}|${RESET} ${BOLD}${BLUE}Opportunity${RESET}${" ".repeat(innerWidth - 12)}${BLUE}|${RESET}
|
|
1053
|
+
`);
|
|
1054
|
+
process.stdout.write(` ${BLUE}+${"─".repeat(width)}+${RESET}
|
|
1055
|
+
`);
|
|
1056
|
+
const st = opp.status ?? "unknown";
|
|
1057
|
+
const category = opp.interpretation?.category ?? "Uncategorized";
|
|
1058
|
+
cardLine(`${BOLD}Status:${RESET} ${statusColor(st)}${st}${RESET}`, innerWidth);
|
|
1059
|
+
cardLine(`${BOLD}Category:${RESET} ${category}`, innerWidth);
|
|
1060
|
+
if (opp.interpretation?.confidence != null) {
|
|
1061
|
+
const bar = confidenceBar(opp.interpretation.confidence);
|
|
1062
|
+
cardLine(`${BOLD}Confidence:${RESET} ${bar}`, innerWidth);
|
|
1063
|
+
}
|
|
1064
|
+
if (opp.actors && opp.actors.length > 0) {
|
|
1065
|
+
process.stdout.write(` ${BLUE}|${RESET}${" ".repeat(innerWidth)}${BLUE}|${RESET}
|
|
1066
|
+
`);
|
|
1067
|
+
cardLine(`${BOLD}Parties:${RESET}`, innerWidth);
|
|
1068
|
+
for (const actor of opp.actors) {
|
|
1069
|
+
const name = actor.name ?? actor.userId;
|
|
1070
|
+
const role = roleLabel(actor.role);
|
|
1071
|
+
cardLine(` ${name} ${role}`, innerWidth);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (opp.interpretation?.reasoning) {
|
|
1075
|
+
process.stdout.write(` ${BLUE}|${RESET}${" ".repeat(innerWidth)}${BLUE}|${RESET}
|
|
1076
|
+
`);
|
|
1077
|
+
cardLine(`${BOLD}Reasoning:${RESET}`, innerWidth);
|
|
1078
|
+
const wrapped = wordWrap(opp.interpretation.reasoning, innerWidth - 4);
|
|
1079
|
+
for (const line of wrapped) {
|
|
1080
|
+
cardLine(` ${AGENT_TEXT}${line}${RESET}`, innerWidth);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (opp.presentation) {
|
|
1084
|
+
process.stdout.write(` ${BLUE}|${RESET}${" ".repeat(innerWidth)}${BLUE}|${RESET}
|
|
1085
|
+
`);
|
|
1086
|
+
cardLine(`${BOLD}Presentation:${RESET}`, innerWidth);
|
|
1087
|
+
const wrapped = wordWrap(opp.presentation, innerWidth - 4);
|
|
1088
|
+
for (const line of wrapped) {
|
|
1089
|
+
cardLine(` ${AGENT_TEXT}${line}${RESET}`, innerWidth);
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (opp.createdAt) {
|
|
1093
|
+
process.stdout.write(` ${BLUE}|${RESET}${" ".repeat(innerWidth)}${BLUE}|${RESET}
|
|
1094
|
+
`);
|
|
1095
|
+
const created = new Date(opp.createdAt).toLocaleString("en-US", {
|
|
1096
|
+
year: "numeric",
|
|
1097
|
+
month: "short",
|
|
1098
|
+
day: "numeric",
|
|
1099
|
+
hour: "2-digit",
|
|
1100
|
+
minute: "2-digit"
|
|
1101
|
+
});
|
|
1102
|
+
cardLine(`${GRAY}Created: ${created}${RESET}`, innerWidth);
|
|
1103
|
+
}
|
|
1104
|
+
process.stdout.write(` ${BLUE}+${"─".repeat(width)}+${RESET}
|
|
1105
|
+
|
|
1106
|
+
`);
|
|
1107
|
+
}
|
|
1108
|
+
function cardLine(content, _innerWidth) {
|
|
1109
|
+
process.stdout.write(` ${BLUE}|${RESET} ${content}
|
|
1110
|
+
`);
|
|
1111
|
+
}
|
|
1112
|
+
function statusColor(st) {
|
|
1113
|
+
switch (st) {
|
|
1114
|
+
case "accepted":
|
|
1115
|
+
return GREEN;
|
|
1116
|
+
case "rejected":
|
|
1117
|
+
return RED;
|
|
1118
|
+
case "pending":
|
|
1119
|
+
return YELLOW;
|
|
1120
|
+
case "expired":
|
|
1121
|
+
return GRAY;
|
|
1122
|
+
default:
|
|
1123
|
+
return "";
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function networkTable(networks) {
|
|
1127
|
+
if (networks.length === 0) {
|
|
1128
|
+
dim(" No networks found.");
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const titleW = 30;
|
|
1132
|
+
const membersW = 8;
|
|
1133
|
+
const roleW = 10;
|
|
1134
|
+
const policyW = 12;
|
|
1135
|
+
const dateW = 18;
|
|
1136
|
+
console.log(` ${BOLD}${"Title".padEnd(titleW)} ${"Members".padEnd(membersW)} ${"Role".padEnd(roleW)} ${"Join Policy".padEnd(policyW)} ${"Created".padEnd(dateW)}${RESET}`);
|
|
1137
|
+
console.log(` ${GRAY}${"-".repeat(titleW)} ${"-".repeat(membersW)} ${"-".repeat(roleW)} ${"-".repeat(policyW)} ${"-".repeat(dateW)}${RESET}`);
|
|
1138
|
+
for (const n of networks) {
|
|
1139
|
+
const title = n.title.slice(0, titleW);
|
|
1140
|
+
const members = String(n.memberCount ?? "-");
|
|
1141
|
+
const role = n.role ?? "member";
|
|
1142
|
+
const policy = (n.joinPolicy ?? "invite_only").replace("_", " ");
|
|
1143
|
+
const date = n.createdAt ? new Date(n.createdAt).toLocaleDateString("en-US", {
|
|
1144
|
+
year: "numeric",
|
|
1145
|
+
month: "short",
|
|
1146
|
+
day: "numeric"
|
|
1147
|
+
}) : "-";
|
|
1148
|
+
console.log(` ${title.padEnd(titleW)} ${members.padEnd(membersW)} ${role.padEnd(roleW)} ${policy.padEnd(policyW)} ${GRAY}${date}${RESET}`);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
function networkCard(network) {
|
|
1152
|
+
console.log();
|
|
1153
|
+
console.log(` ${BOLD}${network.title}${RESET}`);
|
|
1154
|
+
console.log(` ${GRAY}${"─".repeat(40)}${RESET}`);
|
|
1155
|
+
console.log(` ${GRAY}ID:${RESET} ${network.id}`);
|
|
1156
|
+
if (network.prompt) {
|
|
1157
|
+
console.log(` ${GRAY}Prompt:${RESET} ${network.prompt}`);
|
|
1158
|
+
}
|
|
1159
|
+
console.log(` ${GRAY}Join Policy:${RESET} ${(network.joinPolicy ?? "invite_only").replace("_", " ")}`);
|
|
1160
|
+
console.log(` ${GRAY}Members:${RESET} ${network.memberCount ?? "-"}`);
|
|
1161
|
+
if (network.owner) {
|
|
1162
|
+
console.log(` ${GRAY}Owner:${RESET} ${network.owner.name} (${network.owner.email})`);
|
|
1163
|
+
}
|
|
1164
|
+
console.log();
|
|
1165
|
+
}
|
|
1166
|
+
function memberTable(members) {
|
|
1167
|
+
if (members.length === 0) {
|
|
1168
|
+
dim(" No members found.");
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const nameW = 24;
|
|
1172
|
+
const emailW = 30;
|
|
1173
|
+
const roleW = 10;
|
|
1174
|
+
const dateW = 18;
|
|
1175
|
+
console.log(` ${BOLD}${"Name".padEnd(nameW)} ${"Email".padEnd(emailW)} ${"Role".padEnd(roleW)} ${"Joined".padEnd(dateW)}${RESET}`);
|
|
1176
|
+
console.log(` ${GRAY}${"-".repeat(nameW)} ${"-".repeat(emailW)} ${"-".repeat(roleW)} ${"-".repeat(dateW)}${RESET}`);
|
|
1177
|
+
for (const m of members) {
|
|
1178
|
+
const name = m.user.name.slice(0, nameW);
|
|
1179
|
+
const email = m.user.email.slice(0, emailW);
|
|
1180
|
+
const role = m.permissions.includes("owner") ? "owner" : m.permissions.includes("admin") ? "admin" : "member";
|
|
1181
|
+
const date = m.createdAt ? new Date(m.createdAt).toLocaleDateString("en-US", {
|
|
1182
|
+
year: "numeric",
|
|
1183
|
+
month: "short",
|
|
1184
|
+
day: "numeric"
|
|
1185
|
+
}) : "-";
|
|
1186
|
+
console.log(` ${name.padEnd(nameW)} ${email.padEnd(emailW)} ${role.padEnd(roleW)} ${GRAY}${date}${RESET}`);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
function conversationTable(conversations) {
|
|
1190
|
+
if (conversations.length === 0) {
|
|
1191
|
+
dim(" No conversations found.");
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const idWidth = 36;
|
|
1195
|
+
const participantsWidth = 40;
|
|
1196
|
+
const dateWidth = 20;
|
|
1197
|
+
console.log(` ${BOLD}${"ID".padEnd(idWidth)} ${"Participants".padEnd(participantsWidth)} ${"Created".padEnd(dateWidth)}${RESET}`);
|
|
1198
|
+
console.log(` ${GRAY}${"-".repeat(idWidth)} ${"-".repeat(participantsWidth)} ${"-".repeat(dateWidth)}${RESET}`);
|
|
1199
|
+
for (const c of conversations) {
|
|
1200
|
+
const names = c.participants.map((p) => p.user?.name ?? p.participantId).join(", ").slice(0, participantsWidth);
|
|
1201
|
+
const date = new Date(c.createdAt).toLocaleDateString("en-US", {
|
|
1202
|
+
year: "numeric",
|
|
1203
|
+
month: "short",
|
|
1204
|
+
day: "numeric"
|
|
1205
|
+
});
|
|
1206
|
+
console.log(` ${GRAY}${c.id.padEnd(idWidth)}${RESET} ${names.padEnd(participantsWidth)} ${GRAY}${date}${RESET}`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
function conversationCard(conversation) {
|
|
1210
|
+
console.log();
|
|
1211
|
+
console.log(` ${BOLD}${CYAN}Conversation${RESET}`);
|
|
1212
|
+
console.log(` ${GRAY}${"─".repeat(40)}${RESET}`);
|
|
1213
|
+
console.log(` ${BOLD}ID${RESET} ${GRAY}${conversation.id}${RESET}`);
|
|
1214
|
+
if (conversation.participants.length > 0) {
|
|
1215
|
+
const names = conversation.participants.map((p) => p.user?.name ?? p.participantId).join(", ");
|
|
1216
|
+
console.log(` ${BOLD}Participants${RESET} ${names}`);
|
|
1217
|
+
}
|
|
1218
|
+
const date = new Date(conversation.createdAt).toLocaleDateString("en-US", {
|
|
1219
|
+
year: "numeric",
|
|
1220
|
+
month: "short",
|
|
1221
|
+
day: "numeric"
|
|
1222
|
+
});
|
|
1223
|
+
console.log(` ${BOLD}Created${RESET} ${GRAY}${date}${RESET}`);
|
|
1224
|
+
console.log(` ${GRAY}${"─".repeat(40)}${RESET}`);
|
|
1225
|
+
console.log();
|
|
1226
|
+
}
|
|
1227
|
+
function messageList(messages) {
|
|
1228
|
+
if (messages.length === 0) {
|
|
1229
|
+
dim(" No messages found.");
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
for (const msg of messages) {
|
|
1233
|
+
const sender = msg.senderId ?? msg.role;
|
|
1234
|
+
const time = new Date(msg.createdAt).toLocaleTimeString("en-US", {
|
|
1235
|
+
hour: "2-digit",
|
|
1236
|
+
minute: "2-digit"
|
|
1237
|
+
});
|
|
1238
|
+
const date = new Date(msg.createdAt).toLocaleDateString("en-US", {
|
|
1239
|
+
month: "short",
|
|
1240
|
+
day: "numeric"
|
|
1241
|
+
});
|
|
1242
|
+
const textParts = msg.parts.filter((p) => p.type === "text" && p.text).map((p) => p.text).join("");
|
|
1243
|
+
console.log(` ${CYAN}${sender}${RESET} ${GRAY}${date} ${time}${RESET}`);
|
|
1244
|
+
console.log(` ${textParts}`);
|
|
1245
|
+
console.log();
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
// src/profile.command.ts
|
|
1249
|
+
async function handleProfile(client, subcommand, positionals) {
|
|
1250
|
+
if (subcommand === "sync") {
|
|
1251
|
+
await profileSync(client);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1254
|
+
if (subcommand === "show") {
|
|
1255
|
+
const userId = positionals[0];
|
|
1256
|
+
if (!userId) {
|
|
1257
|
+
error("Usage: index profile show <user-id>", 1);
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
await profileShow(client, userId);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
await profileMe(client);
|
|
1264
|
+
}
|
|
1265
|
+
async function profileMe(client) {
|
|
1266
|
+
info("Loading your profile...");
|
|
1267
|
+
const me = await client.getMe();
|
|
1268
|
+
const user = await client.getUser(me.id);
|
|
1269
|
+
profileCard(user);
|
|
1270
|
+
}
|
|
1271
|
+
async function profileShow(client, userId) {
|
|
1272
|
+
info("Loading profile...");
|
|
1273
|
+
const user = await client.getUser(userId);
|
|
1274
|
+
profileCard(user);
|
|
1275
|
+
}
|
|
1276
|
+
async function profileSync(client) {
|
|
1277
|
+
info("Regenerating profile...");
|
|
1278
|
+
await client.syncProfile();
|
|
1279
|
+
success("Profile regeneration triggered. It may take a moment to complete.");
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/intent.command.ts
|
|
1283
|
+
var INTENT_HELP = `
|
|
1284
|
+
Usage:
|
|
1285
|
+
index intent list [--archived] [--limit <n>] List your signals
|
|
1286
|
+
index intent show <id> Show signal details
|
|
1287
|
+
index intent create <content> Create a signal from text
|
|
1288
|
+
index intent archive <id> Archive a signal
|
|
1289
|
+
`;
|
|
1290
|
+
async function handleIntent(client, subcommand, options) {
|
|
1291
|
+
if (!subcommand) {
|
|
1292
|
+
console.log(INTENT_HELP);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
switch (subcommand) {
|
|
1296
|
+
case "list": {
|
|
1297
|
+
const result = await client.listIntents({
|
|
1298
|
+
archived: options.archived,
|
|
1299
|
+
limit: options.limit
|
|
1300
|
+
});
|
|
1301
|
+
heading("Signals");
|
|
1302
|
+
intentTable(result.intents);
|
|
1303
|
+
if (result.pagination.total > 0) {
|
|
1304
|
+
dim(`
|
|
1305
|
+
Page ${result.pagination.page} of ${result.pagination.totalPages} (${result.pagination.total} total)`);
|
|
1306
|
+
}
|
|
1307
|
+
console.log();
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
case "show": {
|
|
1311
|
+
if (!options.intentId) {
|
|
1312
|
+
error("Missing signal ID. Usage: index intent show <id>", 1);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const intent = await client.getIntent(options.intentId);
|
|
1316
|
+
intentCard(intent);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
case "create": {
|
|
1320
|
+
if (!options.intentContent) {
|
|
1321
|
+
error("Missing content. Usage: index intent create <content>", 1);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
info("Processing signal...");
|
|
1325
|
+
const result = await client.processIntent(options.intentContent);
|
|
1326
|
+
success("Signal processed successfully.");
|
|
1327
|
+
if (result.message) {
|
|
1328
|
+
dim(` ${result.message}`);
|
|
1329
|
+
}
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
case "archive": {
|
|
1333
|
+
if (!options.intentId) {
|
|
1334
|
+
error("Missing signal ID. Usage: index intent archive <id>", 1);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
await client.archiveIntent(options.intentId);
|
|
1338
|
+
success(`Signal ${options.intentId} archived.`);
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/opportunity.command.ts
|
|
1345
|
+
var OPPORTUNITY_HELP = `
|
|
1346
|
+
Usage:
|
|
1347
|
+
index opportunity list List your opportunities
|
|
1348
|
+
index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
|
|
1349
|
+
index opportunity list --limit <n> Limit results
|
|
1350
|
+
index opportunity show <id> Show full opportunity details
|
|
1351
|
+
index opportunity accept <id> Accept an opportunity
|
|
1352
|
+
index opportunity reject <id> Reject an opportunity
|
|
1353
|
+
`;
|
|
1354
|
+
async function handleOpportunity(client, subcommand, options) {
|
|
1355
|
+
if (!subcommand) {
|
|
1356
|
+
console.log(OPPORTUNITY_HELP);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
switch (subcommand) {
|
|
1360
|
+
case "list":
|
|
1361
|
+
await opportunityList(client, options.status, options.limit);
|
|
1362
|
+
return;
|
|
1363
|
+
case "show":
|
|
1364
|
+
if (!options.targetId) {
|
|
1365
|
+
error("Missing opportunity ID. Usage: index opportunity show <id>", 1);
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
await opportunityShow(client, options.targetId);
|
|
1369
|
+
return;
|
|
1370
|
+
case "accept":
|
|
1371
|
+
if (!options.targetId) {
|
|
1372
|
+
error("Missing opportunity ID. Usage: index opportunity accept <id>", 1);
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
await opportunityStatusUpdate(client, options.targetId, "accepted");
|
|
1376
|
+
return;
|
|
1377
|
+
case "reject":
|
|
1378
|
+
if (!options.targetId) {
|
|
1379
|
+
error("Missing opportunity ID. Usage: index opportunity reject <id>", 1);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
await opportunityStatusUpdate(client, options.targetId, "rejected");
|
|
1383
|
+
return;
|
|
1384
|
+
default:
|
|
1385
|
+
error(`Unknown subcommand: ${subcommand}`, 1);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
async function opportunityList(client, status2, limit) {
|
|
1390
|
+
const opportunities = await client.listOpportunities({ status: status2, limit });
|
|
1391
|
+
heading("Opportunities");
|
|
1392
|
+
opportunityTable(opportunities);
|
|
1393
|
+
console.log();
|
|
1394
|
+
}
|
|
1395
|
+
async function opportunityShow(client, id) {
|
|
1396
|
+
const opportunity = await client.getOpportunity(id);
|
|
1397
|
+
opportunityCard(opportunity);
|
|
1398
|
+
}
|
|
1399
|
+
async function opportunityStatusUpdate(client, id, status2) {
|
|
1400
|
+
await client.updateOpportunityStatus(id, status2);
|
|
1401
|
+
const label = status2 === "accepted" ? "accepted" : "rejected";
|
|
1402
|
+
success(`Opportunity ${label}.`);
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/network.command.ts
|
|
1406
|
+
var NETWORK_HELP = `
|
|
1407
|
+
Network Commands:
|
|
1408
|
+
index network list List your networks
|
|
1409
|
+
index network create <name> Create a new network
|
|
1410
|
+
index network create <name> --prompt Create with a description
|
|
1411
|
+
index network show <id> Show network details and members
|
|
1412
|
+
index network join <id> Join a public network
|
|
1413
|
+
index network leave <id> Leave a network
|
|
1414
|
+
index network invite <id> <email> Invite a user by email
|
|
1415
|
+
`;
|
|
1416
|
+
async function handleNetwork(client, subcommand, positionals, options) {
|
|
1417
|
+
if (!subcommand) {
|
|
1418
|
+
console.log(NETWORK_HELP);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
switch (subcommand) {
|
|
1422
|
+
case "list":
|
|
1423
|
+
await networkList(client);
|
|
1424
|
+
return;
|
|
1425
|
+
case "create":
|
|
1426
|
+
await networkCreate(client, positionals[0], options.prompt);
|
|
1427
|
+
return;
|
|
1428
|
+
case "show":
|
|
1429
|
+
await networkShow(client, positionals[0]);
|
|
1430
|
+
return;
|
|
1431
|
+
case "join":
|
|
1432
|
+
await networkJoin(client, positionals[0]);
|
|
1433
|
+
return;
|
|
1434
|
+
case "leave":
|
|
1435
|
+
await networkLeave(client, positionals[0]);
|
|
1436
|
+
return;
|
|
1437
|
+
case "invite":
|
|
1438
|
+
await networkInvite(client, positionals[0], positionals[1]);
|
|
1439
|
+
return;
|
|
1440
|
+
default:
|
|
1441
|
+
error(`Unknown network subcommand: ${subcommand}`, 1);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
async function networkList(client) {
|
|
1445
|
+
const networks = await client.listNetworks();
|
|
1446
|
+
const filtered = networks.filter((n) => !n.isPersonal);
|
|
1447
|
+
heading("Networks");
|
|
1448
|
+
networkTable(filtered);
|
|
1449
|
+
console.log();
|
|
1450
|
+
}
|
|
1451
|
+
async function networkCreate(client, name, prompt) {
|
|
1452
|
+
if (!name) {
|
|
1453
|
+
error("Usage: index network create <name>", 1);
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
const network = await client.createNetwork(name, prompt);
|
|
1457
|
+
success(`Network created: ${network.title}`);
|
|
1458
|
+
dim(` ID: ${network.id}`);
|
|
1459
|
+
dim(` Join Policy: ${(network.joinPolicy ?? "invite_only").replace("_", " ")}`);
|
|
1460
|
+
}
|
|
1461
|
+
async function networkShow(client, id) {
|
|
1462
|
+
if (!id) {
|
|
1463
|
+
error("Usage: index network show <id>", 1);
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
const network = await client.getNetwork(id);
|
|
1467
|
+
networkCard(network);
|
|
1468
|
+
const members = await client.getNetworkMembers(id);
|
|
1469
|
+
heading("Members");
|
|
1470
|
+
memberTable(members);
|
|
1471
|
+
console.log();
|
|
1472
|
+
}
|
|
1473
|
+
async function networkJoin(client, id) {
|
|
1474
|
+
if (!id) {
|
|
1475
|
+
error("Usage: index network join <id>", 1);
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
const network = await client.joinNetwork(id);
|
|
1479
|
+
success(`Joined network: ${network.title}`);
|
|
1480
|
+
}
|
|
1481
|
+
async function networkLeave(client, id) {
|
|
1482
|
+
if (!id) {
|
|
1483
|
+
error("Usage: index network leave <id>", 1);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
await client.leaveNetwork(id);
|
|
1487
|
+
success("Left network.");
|
|
1488
|
+
}
|
|
1489
|
+
async function networkInvite(client, id, email) {
|
|
1490
|
+
if (!id || !email) {
|
|
1491
|
+
error("Usage: index network invite <id> <email>", 1);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
const users = await client.searchUsers(email, id);
|
|
1495
|
+
if (users.length === 0) {
|
|
1496
|
+
error("User not found.");
|
|
1497
|
+
return;
|
|
1498
|
+
}
|
|
1499
|
+
const user = users[0];
|
|
1500
|
+
const result = await client.addNetworkMember(id, user.id);
|
|
1501
|
+
success(`Invited ${user.name} (${user.email}) to the network.`);
|
|
1502
|
+
if (result.message) {
|
|
1503
|
+
dim(` ${result.message}`);
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// src/conversation.command.ts
|
|
1508
|
+
import { createInterface } from "node:readline/promises";
|
|
1509
|
+
async function renderSSEStream(response, callbacks) {
|
|
1510
|
+
const { onToken, onStatus, onToolActivity, onResponseReset } = callbacks;
|
|
1511
|
+
const result = {};
|
|
1512
|
+
if (!response.body) {
|
|
1513
|
+
result.error = "No response body";
|
|
1514
|
+
return result;
|
|
1515
|
+
}
|
|
1516
|
+
const reader = response.body.getReader();
|
|
1517
|
+
const decoder = new TextDecoder;
|
|
1518
|
+
let buffer = "";
|
|
1519
|
+
let insideToolCall = false;
|
|
1520
|
+
const toolDescriptions = new Map;
|
|
1521
|
+
try {
|
|
1522
|
+
while (true) {
|
|
1523
|
+
const { done, value } = await reader.read();
|
|
1524
|
+
if (done)
|
|
1525
|
+
break;
|
|
1526
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1527
|
+
while (buffer.includes(`
|
|
1528
|
+
|
|
1529
|
+
`)) {
|
|
1530
|
+
const idx = buffer.indexOf(`
|
|
1531
|
+
|
|
1532
|
+
`);
|
|
1533
|
+
const raw2 = buffer.slice(0, idx);
|
|
1534
|
+
buffer = buffer.slice(idx + 2);
|
|
1535
|
+
for (const line of raw2.split(`
|
|
1536
|
+
`)) {
|
|
1537
|
+
if (!line.startsWith("data:"))
|
|
1538
|
+
continue;
|
|
1539
|
+
const jsonStr = line.slice("data:".length).trim();
|
|
1540
|
+
if (!jsonStr)
|
|
1541
|
+
continue;
|
|
1542
|
+
try {
|
|
1543
|
+
const event = JSON.parse(jsonStr);
|
|
1544
|
+
const type = event.type;
|
|
1545
|
+
switch (type) {
|
|
1546
|
+
case "token": {
|
|
1547
|
+
if (typeof event.content !== "string")
|
|
1548
|
+
break;
|
|
1549
|
+
if (insideToolCall)
|
|
1550
|
+
break;
|
|
1551
|
+
onToken(event.content);
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
case "status":
|
|
1555
|
+
if (onStatus && typeof event.message === "string") {
|
|
1556
|
+
onStatus(event.message);
|
|
1557
|
+
}
|
|
1558
|
+
break;
|
|
1559
|
+
case "tool_activity": {
|
|
1560
|
+
const phase = event.phase;
|
|
1561
|
+
const toolName = typeof event.toolName === "string" ? event.toolName : "";
|
|
1562
|
+
const desc = typeof event.description === "string" ? event.description : undefined;
|
|
1563
|
+
if (phase === "start") {
|
|
1564
|
+
insideToolCall = true;
|
|
1565
|
+
if (desc && toolName) {
|
|
1566
|
+
toolDescriptions.set(toolName, desc);
|
|
1567
|
+
}
|
|
1568
|
+
if (onToolActivity && desc) {
|
|
1569
|
+
onToolActivity(desc, "start");
|
|
1570
|
+
}
|
|
1571
|
+
} else if (phase === "end") {
|
|
1572
|
+
insideToolCall = false;
|
|
1573
|
+
const startDesc = toolName ? toolDescriptions.get(toolName) : undefined;
|
|
1574
|
+
if (onToolActivity) {
|
|
1575
|
+
onToolActivity(startDesc ?? desc ?? toolName, "end", event.success !== false);
|
|
1576
|
+
}
|
|
1577
|
+
if (toolName)
|
|
1578
|
+
toolDescriptions.delete(toolName);
|
|
1579
|
+
}
|
|
1580
|
+
break;
|
|
1581
|
+
}
|
|
1582
|
+
case "tool_start":
|
|
1583
|
+
insideToolCall = true;
|
|
1584
|
+
break;
|
|
1585
|
+
case "tool_end":
|
|
1586
|
+
insideToolCall = false;
|
|
1587
|
+
break;
|
|
1588
|
+
case "llm_start":
|
|
1589
|
+
insideToolCall = false;
|
|
1590
|
+
break;
|
|
1591
|
+
case "response_reset":
|
|
1592
|
+
result.response = undefined;
|
|
1593
|
+
if (onResponseReset) {
|
|
1594
|
+
onResponseReset(typeof event.reason === "string" ? event.reason : undefined);
|
|
1595
|
+
}
|
|
1596
|
+
break;
|
|
1597
|
+
case "done":
|
|
1598
|
+
result.sessionId = typeof event.sessionId === "string" ? event.sessionId : undefined;
|
|
1599
|
+
result.response = typeof event.response === "string" ? event.response : undefined;
|
|
1600
|
+
result.title = typeof event.title === "string" ? event.title : undefined;
|
|
1601
|
+
break;
|
|
1602
|
+
case "error":
|
|
1603
|
+
result.error = typeof event.message === "string" ? event.message : "Unknown stream error";
|
|
1604
|
+
break;
|
|
1605
|
+
default:
|
|
1606
|
+
break;
|
|
1607
|
+
}
|
|
1608
|
+
} catch {}
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
} finally {
|
|
1613
|
+
reader.releaseLock();
|
|
1614
|
+
}
|
|
1615
|
+
return result;
|
|
1616
|
+
}
|
|
1617
|
+
var CONVERSATION_HELP = `
|
|
1618
|
+
Conversation Commands:
|
|
1619
|
+
index conversation Start an interactive AI chat (REPL)
|
|
1620
|
+
index conversation "message" One-shot message to the AI agent
|
|
1621
|
+
index conversation --session <id> Resume a specific chat session
|
|
1622
|
+
index conversation sessions List AI chat sessions
|
|
1623
|
+
index conversation list List all conversations (H2A + H2H)
|
|
1624
|
+
index conversation with <user-id> Open or resume a DM with a user
|
|
1625
|
+
index conversation show <id> Show messages in a conversation
|
|
1626
|
+
index conversation show <id> --limit <n> Limit number of messages
|
|
1627
|
+
index conversation send <id> <message> Send a message
|
|
1628
|
+
index conversation stream Listen for real-time events (SSE)
|
|
1629
|
+
`;
|
|
1630
|
+
async function handleConversation(client, subcommand, positionals, options) {
|
|
1631
|
+
if (!subcommand) {
|
|
1632
|
+
if (options?.message) {
|
|
1633
|
+
await chatOneShot(client, options.message, options.sessionId);
|
|
1634
|
+
} else {
|
|
1635
|
+
await chatRepl(client, options?.sessionId);
|
|
1636
|
+
}
|
|
1637
|
+
return;
|
|
1638
|
+
}
|
|
1639
|
+
switch (subcommand) {
|
|
1640
|
+
case "sessions":
|
|
1641
|
+
await chatSessionsList(client);
|
|
1642
|
+
return;
|
|
1643
|
+
case "list":
|
|
1644
|
+
await conversationList(client);
|
|
1645
|
+
return;
|
|
1646
|
+
case "with":
|
|
1647
|
+
await conversationWith(client, positionals[0]);
|
|
1648
|
+
return;
|
|
1649
|
+
case "show":
|
|
1650
|
+
await conversationShow(client, positionals[0], options?.limit);
|
|
1651
|
+
return;
|
|
1652
|
+
case "send":
|
|
1653
|
+
await conversationSend(client, positionals[0], positionals.slice(1));
|
|
1654
|
+
return;
|
|
1655
|
+
case "stream":
|
|
1656
|
+
await conversationStream(client);
|
|
1657
|
+
return;
|
|
1658
|
+
case "help":
|
|
1659
|
+
console.log(CONVERSATION_HELP);
|
|
1660
|
+
return;
|
|
1661
|
+
default:
|
|
1662
|
+
error(`Unknown conversation subcommand: ${subcommand}`, 1);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function conversationList(client) {
|
|
1666
|
+
const conversations = await client.listConversations();
|
|
1667
|
+
heading("Conversations");
|
|
1668
|
+
conversationTable(conversations);
|
|
1669
|
+
console.log();
|
|
1670
|
+
}
|
|
1671
|
+
async function conversationWith(client, userId) {
|
|
1672
|
+
if (!userId) {
|
|
1673
|
+
error("Usage: index conversation with <user-id>", 1);
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
const conversation = await client.getOrCreateDM(userId);
|
|
1677
|
+
conversationCard(conversation);
|
|
1678
|
+
}
|
|
1679
|
+
async function conversationShow(client, id, limit) {
|
|
1680
|
+
if (!id) {
|
|
1681
|
+
error("Usage: index conversation show <id>", 1);
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const messages = await client.getMessages(id, { limit: limit ?? 20 });
|
|
1685
|
+
heading("Messages");
|
|
1686
|
+
messageList(messages);
|
|
1687
|
+
}
|
|
1688
|
+
async function conversationSend(client, id, messageParts) {
|
|
1689
|
+
if (!id) {
|
|
1690
|
+
error("Usage: index conversation send <id> <message>", 1);
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (messageParts.length === 0) {
|
|
1694
|
+
error("Missing message. Usage: index conversation send <id> <message>", 1);
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
const text = messageParts.join(" ");
|
|
1698
|
+
const msg = await client.sendMessage(id, text);
|
|
1699
|
+
success(`Message sent (${msg.id})`);
|
|
1700
|
+
}
|
|
1701
|
+
async function conversationStream(client) {
|
|
1702
|
+
info("Connecting to conversation stream...");
|
|
1703
|
+
dim(`Press Ctrl+C to stop.
|
|
1704
|
+
`);
|
|
1705
|
+
const response = await client.streamConversationEvents();
|
|
1706
|
+
if (!response.body) {
|
|
1707
|
+
error("No response body from stream endpoint.", 1);
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const reader = response.body.getReader();
|
|
1711
|
+
const decoder = new TextDecoder;
|
|
1712
|
+
let buffer = "";
|
|
1713
|
+
try {
|
|
1714
|
+
while (true) {
|
|
1715
|
+
const { done, value } = await reader.read();
|
|
1716
|
+
if (done)
|
|
1717
|
+
break;
|
|
1718
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1719
|
+
while (buffer.includes(`
|
|
1720
|
+
|
|
1721
|
+
`)) {
|
|
1722
|
+
const idx = buffer.indexOf(`
|
|
1723
|
+
|
|
1724
|
+
`);
|
|
1725
|
+
const raw2 = buffer.slice(0, idx);
|
|
1726
|
+
buffer = buffer.slice(idx + 2);
|
|
1727
|
+
for (const line of raw2.split(`
|
|
1728
|
+
`)) {
|
|
1729
|
+
if (line.startsWith(":"))
|
|
1730
|
+
continue;
|
|
1731
|
+
if (!line.startsWith("data:"))
|
|
1732
|
+
continue;
|
|
1733
|
+
const jsonStr = line.slice("data:".length).trim();
|
|
1734
|
+
if (!jsonStr)
|
|
1735
|
+
continue;
|
|
1736
|
+
try {
|
|
1737
|
+
const event = JSON.parse(jsonStr);
|
|
1738
|
+
const type = event.type;
|
|
1739
|
+
if (type === "connected") {
|
|
1740
|
+
success("Connected to conversation stream.");
|
|
1741
|
+
} else {
|
|
1742
|
+
dim(`[${type}] ${JSON.stringify(event)}`);
|
|
1743
|
+
}
|
|
1744
|
+
} catch {}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
} finally {
|
|
1749
|
+
reader.releaseLock();
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
async function chatSessionsList(client) {
|
|
1753
|
+
const sessions = await client.listSessions();
|
|
1754
|
+
heading("Chat Sessions");
|
|
1755
|
+
sessionTable(sessions);
|
|
1756
|
+
console.log();
|
|
1757
|
+
}
|
|
1758
|
+
async function chatOneShot(client, message, sessionId) {
|
|
1759
|
+
const response = await client.streamChat({ message, sessionId });
|
|
1760
|
+
if (!response.ok) {
|
|
1761
|
+
handleStreamError(response);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
const result = await streamToTerminal(response);
|
|
1765
|
+
if (result.error) {
|
|
1766
|
+
error(result.error, 1);
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
if (result.sessionId) {
|
|
1770
|
+
dim(`
|
|
1771
|
+
Session: ${result.sessionId}`);
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async function chatRepl(client, sessionId) {
|
|
1775
|
+
let currentSessionId = sessionId;
|
|
1776
|
+
chatHeader();
|
|
1777
|
+
const PROMPT_STR2 = PROMPT_STR;
|
|
1778
|
+
const rl = createInterface({
|
|
1779
|
+
input: process.stdin,
|
|
1780
|
+
output: process.stderr,
|
|
1781
|
+
terminal: true
|
|
1782
|
+
});
|
|
1783
|
+
rl.setPrompt(PROMPT_STR2);
|
|
1784
|
+
rl.prompt();
|
|
1785
|
+
try {
|
|
1786
|
+
for await (const line of rl) {
|
|
1787
|
+
const input = line.trim();
|
|
1788
|
+
if (!input) {
|
|
1789
|
+
rl.prompt();
|
|
1790
|
+
continue;
|
|
1791
|
+
}
|
|
1792
|
+
if (input === "exit" || input === "quit")
|
|
1793
|
+
break;
|
|
1794
|
+
const response = await client.streamChat({
|
|
1795
|
+
message: input,
|
|
1796
|
+
sessionId: currentSessionId
|
|
1797
|
+
});
|
|
1798
|
+
if (!response.ok) {
|
|
1799
|
+
if (response.status === 401) {
|
|
1800
|
+
error("Session expired. Run `index login` to re-authenticate.", 1);
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
const body = await response.json().catch(() => ({}));
|
|
1804
|
+
error(body.error ?? `HTTP ${response.status}`);
|
|
1805
|
+
rl.prompt();
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
const result = await streamToTerminal(response);
|
|
1809
|
+
if (result.error) {
|
|
1810
|
+
error(result.error);
|
|
1811
|
+
}
|
|
1812
|
+
if (result.sessionId) {
|
|
1813
|
+
currentSessionId = result.sessionId;
|
|
1814
|
+
}
|
|
1815
|
+
process.stderr.write(`
|
|
1816
|
+
`);
|
|
1817
|
+
rl.prompt();
|
|
1818
|
+
}
|
|
1819
|
+
} finally {
|
|
1820
|
+
rl.close();
|
|
1821
|
+
}
|
|
1822
|
+
process.stderr.write(`
|
|
1823
|
+
`);
|
|
1824
|
+
dim("Goodbye!");
|
|
1825
|
+
}
|
|
1826
|
+
async function streamToTerminal(response) {
|
|
1827
|
+
let hasTokens = false;
|
|
1828
|
+
const md = new MarkdownRenderer;
|
|
1829
|
+
let lastToolDesc = "";
|
|
1830
|
+
const result = await renderSSEStream(response, {
|
|
1831
|
+
onToken(text) {
|
|
1832
|
+
if (!hasTokens) {
|
|
1833
|
+
clearStatus();
|
|
1834
|
+
hasTokens = true;
|
|
1835
|
+
}
|
|
1836
|
+
md.write(text);
|
|
1837
|
+
lastToolDesc = "";
|
|
1838
|
+
},
|
|
1839
|
+
onStatus(msg) {
|
|
1840
|
+
if (!hasTokens) {
|
|
1841
|
+
status(msg);
|
|
1842
|
+
}
|
|
1843
|
+
},
|
|
1844
|
+
onToolActivity(description, phase) {
|
|
1845
|
+
if (phase === "start") {
|
|
1846
|
+
const friendly = humanizeToolName(description);
|
|
1847
|
+
if (friendly === lastToolDesc)
|
|
1848
|
+
return;
|
|
1849
|
+
lastToolDesc = friendly;
|
|
1850
|
+
md.finalize();
|
|
1851
|
+
hasTokens = false;
|
|
1852
|
+
toolActivity(friendly);
|
|
1853
|
+
}
|
|
1854
|
+
},
|
|
1855
|
+
onResponseReset(reason) {
|
|
1856
|
+
md.reset(reason);
|
|
1857
|
+
hasTokens = false;
|
|
1858
|
+
}
|
|
1859
|
+
});
|
|
1860
|
+
md.finalize();
|
|
1861
|
+
clearStatus();
|
|
1862
|
+
if (hasTokens) {
|
|
1863
|
+
console.log();
|
|
1864
|
+
}
|
|
1865
|
+
return result;
|
|
1866
|
+
}
|
|
1867
|
+
async function handleStreamError(response) {
|
|
1868
|
+
if (response.status === 401) {
|
|
1869
|
+
error("Session expired or invalid. Run `index login` to re-authenticate.", 1);
|
|
1870
|
+
}
|
|
1871
|
+
const body = await response.json().catch(() => ({}));
|
|
1872
|
+
error(body.error ?? `HTTP ${response.status}`, 1);
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/main.ts
|
|
1876
|
+
var DEFAULT_API_URL = "https://protocol.index.network";
|
|
1877
|
+
var DEFAULT_APP_URL = "https://index.network";
|
|
1878
|
+
var VERSION = "0.7.0";
|
|
1879
|
+
var HELP_TEXT = `
|
|
1880
|
+
Index CLI v${VERSION}
|
|
1881
|
+
|
|
1882
|
+
Usage:
|
|
1883
|
+
index login Authenticate via browser (uses existing session or OAuth)
|
|
1884
|
+
index login --token <token> Authenticate with a manually provided token
|
|
1885
|
+
index logout Clear stored session
|
|
1886
|
+
index conversation Chat with the AI agent (interactive REPL)
|
|
1887
|
+
index conversation "message" One-shot message to the AI agent
|
|
1888
|
+
index conversation --session <id> Resume a specific chat session
|
|
1889
|
+
index conversation sessions List AI chat sessions
|
|
1890
|
+
index conversation list List all conversations (H2A + H2H)
|
|
1891
|
+
index conversation with <user-id> Open or resume a DM with a user
|
|
1892
|
+
index conversation show <id> Show messages in a conversation
|
|
1893
|
+
index conversation send <id> <msg> Send a message
|
|
1894
|
+
index conversation stream Listen for real-time events (SSE)
|
|
1895
|
+
index profile Show your profile
|
|
1896
|
+
index profile show <user-id> Show another user's profile
|
|
1897
|
+
index profile sync Regenerate your profile
|
|
1898
|
+
index intent list [--archived] [--limit <n>] List your signals
|
|
1899
|
+
index intent show <id> Show signal details
|
|
1900
|
+
index intent create <content> Create a signal from natural language
|
|
1901
|
+
index intent archive <id> Archive a signal
|
|
1902
|
+
index opportunity list List your opportunities
|
|
1903
|
+
index opportunity list --status <s> Filter by status (pending|accepted|rejected|expired)
|
|
1904
|
+
index opportunity list --limit <n> Limit results
|
|
1905
|
+
index opportunity show <id> Show full opportunity details
|
|
1906
|
+
index opportunity accept <id> Accept an opportunity
|
|
1907
|
+
index opportunity reject <id> Reject an opportunity
|
|
1908
|
+
index network list List your networks
|
|
1909
|
+
index network create <name> Create a new network
|
|
1910
|
+
index network show <id> Show network details and members
|
|
1911
|
+
index network join <id> Join a public network
|
|
1912
|
+
index network leave <id> Leave a network
|
|
1913
|
+
index network invite <id> <email> Invite a user by email
|
|
1914
|
+
index --help Show this help message
|
|
1915
|
+
index --version Show version
|
|
1916
|
+
|
|
1917
|
+
Options:
|
|
1918
|
+
--api-url <url> Override the API server URL (default: ${DEFAULT_API_URL})
|
|
1919
|
+
--app-url <url> Override the app URL for login (default: ${DEFAULT_APP_URL})
|
|
1920
|
+
--token <token>, -t Provide a bearer token directly (skips browser flow)
|
|
1921
|
+
--session <id>, -s Resume a specific chat session
|
|
1922
|
+
--archived Include archived signals (intent list)
|
|
1923
|
+
--status <status> Filter opportunities by status
|
|
1924
|
+
--limit <n> Limit number of results
|
|
1925
|
+
`;
|
|
1926
|
+
async function requireAuth(apiUrlOverride) {
|
|
1927
|
+
const store = new CredentialStore;
|
|
1928
|
+
const creds = await store.load();
|
|
1929
|
+
if (!creds) {
|
|
1930
|
+
error("Not logged in. Run `index login` first.", 1);
|
|
1931
|
+
process.exit(1);
|
|
1932
|
+
}
|
|
1933
|
+
const apiUrl = apiUrlOverride ?? creds.apiUrl;
|
|
1934
|
+
return new ApiClient(apiUrl, creds.token);
|
|
1935
|
+
}
|
|
1936
|
+
async function runLogin(apiUrlOverride, appUrlOverride, manualToken) {
|
|
1937
|
+
const store = new CredentialStore;
|
|
1938
|
+
const apiUrl = apiUrlOverride ?? DEFAULT_API_URL;
|
|
1939
|
+
const appUrl = appUrlOverride ?? DEFAULT_APP_URL;
|
|
1940
|
+
if (manualToken) {
|
|
1941
|
+
await store.save({ token: manualToken, apiUrl });
|
|
1942
|
+
try {
|
|
1943
|
+
const client = new ApiClient(apiUrl, manualToken);
|
|
1944
|
+
const user = await client.getMe();
|
|
1945
|
+
success(`Logged in as ${user.name} (${user.email})`);
|
|
1946
|
+
} catch {
|
|
1947
|
+
success("Token stored. Could not verify — check with `index conversation`.");
|
|
1948
|
+
}
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
info(`Authenticating with ${apiUrl}...`);
|
|
1952
|
+
const { authUrl, callbackPromise } = await handleLogin(apiUrl, appUrl, store);
|
|
1953
|
+
info("Opening browser for authentication...");
|
|
1954
|
+
dim(`If the browser does not open, visit:
|
|
1955
|
+
${authUrl}
|
|
1956
|
+
`);
|
|
1957
|
+
try {
|
|
1958
|
+
const opener = process.platform === "darwin" ? "open" : process.platform === "linux" ? "xdg-open" : null;
|
|
1959
|
+
if (opener) {
|
|
1960
|
+
execFile(opener, [authUrl], { stdio: "ignore" });
|
|
1961
|
+
}
|
|
1962
|
+
} catch {}
|
|
1963
|
+
dim("Waiting for authentication callback...");
|
|
1964
|
+
const result = await callbackPromise;
|
|
1965
|
+
if (result.success) {
|
|
1966
|
+
try {
|
|
1967
|
+
const creds = await store.load();
|
|
1968
|
+
if (creds) {
|
|
1969
|
+
const client = new ApiClient(creds.apiUrl, creds.token);
|
|
1970
|
+
const user = await client.getMe();
|
|
1971
|
+
success(`Logged in as ${user.name} (${user.email})`);
|
|
1972
|
+
}
|
|
1973
|
+
} catch {
|
|
1974
|
+
success("Login successful! Token stored.");
|
|
1975
|
+
}
|
|
1976
|
+
} else {
|
|
1977
|
+
error(result.error ?? "Login failed.", 1);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
async function runLogout() {
|
|
1981
|
+
const store = new CredentialStore;
|
|
1982
|
+
await store.clear();
|
|
1983
|
+
success("Logged out. Session cleared.");
|
|
1984
|
+
}
|
|
1985
|
+
async function main() {
|
|
1986
|
+
const args = parseArgs(process.argv.slice(2));
|
|
1987
|
+
switch (args.command) {
|
|
1988
|
+
case "help":
|
|
1989
|
+
console.log(HELP_TEXT);
|
|
1990
|
+
return;
|
|
1991
|
+
case "version":
|
|
1992
|
+
console.log(VERSION);
|
|
1993
|
+
return;
|
|
1994
|
+
case "unknown":
|
|
1995
|
+
error(`Unknown command: ${args.unknown}`, 1);
|
|
1996
|
+
return;
|
|
1997
|
+
case "login":
|
|
1998
|
+
await runLogin(args.apiUrl, args.appUrl, args.token);
|
|
1999
|
+
return;
|
|
2000
|
+
case "logout":
|
|
2001
|
+
await runLogout();
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
const client = await requireAuth(args.apiUrl);
|
|
2005
|
+
switch (args.command) {
|
|
2006
|
+
case "profile":
|
|
2007
|
+
await handleProfile(client, args.subcommand, args.userId ? [args.userId] : []);
|
|
2008
|
+
return;
|
|
2009
|
+
case "intent":
|
|
2010
|
+
await handleIntent(client, args.subcommand, {
|
|
2011
|
+
intentId: args.intentId,
|
|
2012
|
+
intentContent: args.intentContent,
|
|
2013
|
+
archived: args.archived,
|
|
2014
|
+
limit: args.limit
|
|
2015
|
+
});
|
|
2016
|
+
return;
|
|
2017
|
+
case "opportunity":
|
|
2018
|
+
await handleOpportunity(client, args.subcommand, {
|
|
2019
|
+
targetId: args.targetId,
|
|
2020
|
+
status: args.status,
|
|
2021
|
+
limit: args.limit
|
|
2022
|
+
});
|
|
2023
|
+
return;
|
|
2024
|
+
case "network":
|
|
2025
|
+
await handleNetwork(client, args.subcommand, args.positionals ?? [], {
|
|
2026
|
+
prompt: args.prompt
|
|
2027
|
+
});
|
|
2028
|
+
return;
|
|
2029
|
+
case "conversation":
|
|
2030
|
+
await handleConversation(client, args.subcommand, args.positionals ?? [], {
|
|
2031
|
+
limit: args.limit,
|
|
2032
|
+
sessionId: args.sessionId,
|
|
2033
|
+
message: args.message
|
|
2034
|
+
});
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
main().catch((err) => {
|
|
2039
|
+
error(err instanceof Error ? err.message : String(err), 1);
|
|
2040
|
+
});
|