indigo-cli 0.1.0 → 0.1.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/README.md +65 -207
- package/package.json +16 -12
- package/main.js +0 -4557
package/main.js
DELETED
|
@@ -1,4557 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __copyProps = (to, from, except, desc) => {
|
|
9
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
-
for (let key of __getOwnPropNames(from))
|
|
11
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
12
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
13
|
-
}
|
|
14
|
-
return to;
|
|
15
|
-
};
|
|
16
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
17
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
18
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
19
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
20
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
21
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
22
|
-
mod
|
|
23
|
-
));
|
|
24
|
-
|
|
25
|
-
// apps/cli/src/main.ts
|
|
26
|
-
var import_commander7 = require("commander");
|
|
27
|
-
|
|
28
|
-
// apps/cli/src/commands/auth/index.ts
|
|
29
|
-
var import_commander = require("commander");
|
|
30
|
-
|
|
31
|
-
// apps/cli/src/commands/auth/login.ts
|
|
32
|
-
var http = __toESM(require("http"));
|
|
33
|
-
var crypto2 = __toESM(require("crypto"));
|
|
34
|
-
var import_child_process = require("child_process");
|
|
35
|
-
|
|
36
|
-
// apps/cli/src/config/api.ts
|
|
37
|
-
var envs = {
|
|
38
|
-
development: "http://localhost:8080/api",
|
|
39
|
-
test: "http://localhost:8080/api",
|
|
40
|
-
stage: "https://api.stage.getindigo.ai/api",
|
|
41
|
-
production: "https://api.getindigo.ai/api"
|
|
42
|
-
};
|
|
43
|
-
var webappEnvs = {
|
|
44
|
-
development: "http://localhost:4200",
|
|
45
|
-
test: "http://localhost:4200",
|
|
46
|
-
stage: "https://app.stage.getindigo.ai",
|
|
47
|
-
production: "https://app.getindigo.ai"
|
|
48
|
-
};
|
|
49
|
-
var webauthEnvs = {
|
|
50
|
-
development: "https://auth.getindigo.ai",
|
|
51
|
-
test: "http://localhost:3002",
|
|
52
|
-
stage: "https://auth.stage.getindigo.ai",
|
|
53
|
-
production: "https://auth.getindigo.ai"
|
|
54
|
-
};
|
|
55
|
-
var getEnvironment = () => {
|
|
56
|
-
const buildEnv = typeof __CLI_BUILD_ENV__ !== "undefined" ? __CLI_BUILD_ENV__ : void 0;
|
|
57
|
-
if (buildEnv && buildEnv !== "auto") {
|
|
58
|
-
if (buildEnv === "staging" || buildEnv === "stage")
|
|
59
|
-
return "stage";
|
|
60
|
-
if (buildEnv === "test")
|
|
61
|
-
return "test";
|
|
62
|
-
if (buildEnv === "development" || buildEnv === "dev")
|
|
63
|
-
return "development";
|
|
64
|
-
if (buildEnv === "production" || buildEnv === "prod")
|
|
65
|
-
return "production";
|
|
66
|
-
}
|
|
67
|
-
const env = process.env.INDIGO_ENV || process.env.NODE_ENV;
|
|
68
|
-
if (env === "staging" || env === "stage")
|
|
69
|
-
return "stage";
|
|
70
|
-
if (env === "test")
|
|
71
|
-
return "test";
|
|
72
|
-
if (env === "production")
|
|
73
|
-
return "production";
|
|
74
|
-
return "development";
|
|
75
|
-
};
|
|
76
|
-
var environment = getEnvironment();
|
|
77
|
-
var apiUrl = process.env.INDIGO_API_URL || envs[environment];
|
|
78
|
-
var webappUrl = webappEnvs[environment];
|
|
79
|
-
var webauthUrl = process.env.INDIGO_AUTH_URL || webauthEnvs[environment];
|
|
80
|
-
var configuredUrls = {
|
|
81
|
-
environment,
|
|
82
|
-
apiUrl,
|
|
83
|
-
webappUrl,
|
|
84
|
-
webauthUrl
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
// apps/cli/src/services/credential-store.ts
|
|
88
|
-
var fs2 = __toESM(require("fs"));
|
|
89
|
-
var path2 = __toESM(require("path"));
|
|
90
|
-
var os2 = __toESM(require("os"));
|
|
91
|
-
var crypto = __toESM(require("crypto"));
|
|
92
|
-
|
|
93
|
-
// apps/cli/src/services/electron-credentials.ts
|
|
94
|
-
var fs = __toESM(require("fs"));
|
|
95
|
-
var path = __toESM(require("path"));
|
|
96
|
-
var os = __toESM(require("os"));
|
|
97
|
-
function getElectronAppDataPath() {
|
|
98
|
-
const platform = process.platform;
|
|
99
|
-
const home = os.homedir();
|
|
100
|
-
if (platform === "darwin") {
|
|
101
|
-
return path.join(home, "Library", "Application Support");
|
|
102
|
-
} else if (platform === "win32") {
|
|
103
|
-
return process.env.APPDATA || path.join(home, "AppData", "Roaming");
|
|
104
|
-
} else {
|
|
105
|
-
return process.env.XDG_CONFIG_HOME || path.join(home, ".config");
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function getElectronSettingsPaths() {
|
|
109
|
-
const appDataPath = getElectronAppDataPath();
|
|
110
|
-
return [
|
|
111
|
-
path.join(appDataPath, "indigo", "settings.json"),
|
|
112
|
-
path.join(appDataPath, "indigo-staging", "settings-staging.json"),
|
|
113
|
-
path.join(appDataPath, "indigo-dev", "settings-dev.json")
|
|
114
|
-
];
|
|
115
|
-
}
|
|
116
|
-
function decodeElectronToken(encoded) {
|
|
117
|
-
if (!encoded)
|
|
118
|
-
return null;
|
|
119
|
-
if (encoded.startsWith("plain:")) {
|
|
120
|
-
try {
|
|
121
|
-
const base64 = encoded.slice("plain:".length);
|
|
122
|
-
return Buffer.from(base64, "base64").toString("utf8");
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (encoded.startsWith("enc:")) {
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
return encoded;
|
|
131
|
-
}
|
|
132
|
-
function readSettingsFile(filePath) {
|
|
133
|
-
try {
|
|
134
|
-
if (!fs.existsSync(filePath)) {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
138
|
-
return JSON.parse(content);
|
|
139
|
-
} catch {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
function loadElectronCredentials() {
|
|
144
|
-
const settingsPaths = getElectronSettingsPaths();
|
|
145
|
-
for (const settingsPath of settingsPaths) {
|
|
146
|
-
const settings = readSettingsFile(settingsPath);
|
|
147
|
-
if (!settings)
|
|
148
|
-
continue;
|
|
149
|
-
const authState = settings["auth.multiUserState"];
|
|
150
|
-
if (authState && authState.version === 1 && authState.currentUserId) {
|
|
151
|
-
const currentUser = authState.users?.find((u) => u.id === authState.currentUserId);
|
|
152
|
-
if (currentUser?.jwtTokenEnc) {
|
|
153
|
-
const token = decodeElectronToken(currentUser.jwtTokenEnc);
|
|
154
|
-
if (token) {
|
|
155
|
-
const fullName = [currentUser.firstName, currentUser.lastName].filter(Boolean).join(" ") || currentUser.name;
|
|
156
|
-
return {
|
|
157
|
-
token,
|
|
158
|
-
userId: currentUser.id,
|
|
159
|
-
email: currentUser.email,
|
|
160
|
-
name: fullName
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
const legacyState = settings["multiUserState"];
|
|
166
|
-
if (legacyState && legacyState.currentUserId) {
|
|
167
|
-
const currentUser = legacyState.users?.find((u) => u.id === legacyState.currentUserId);
|
|
168
|
-
if (currentUser?.jwtToken) {
|
|
169
|
-
const fullName = [currentUser.firstName, currentUser.lastName].filter(Boolean).join(" ") || currentUser.name;
|
|
170
|
-
return {
|
|
171
|
-
token: currentUser.jwtToken,
|
|
172
|
-
userId: currentUser.id,
|
|
173
|
-
email: currentUser.email,
|
|
174
|
-
name: fullName
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return null;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// apps/cli/src/services/credential-store.ts
|
|
183
|
-
var SERVICE_NAME = "indigo-cli";
|
|
184
|
-
var ACCOUNT_NAME = "default";
|
|
185
|
-
var keytar = null;
|
|
186
|
-
var keytarLoaded = false;
|
|
187
|
-
function loadKeytarSync() {
|
|
188
|
-
if (keytarLoaded)
|
|
189
|
-
return keytar;
|
|
190
|
-
keytarLoaded = true;
|
|
191
|
-
if (process.env.INDIGO_DISABLE_KEYTAR === "true") {
|
|
192
|
-
keytar = null;
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
try {
|
|
196
|
-
require.resolve("keytar");
|
|
197
|
-
const dynamicRequire = new Function("moduleName", "return require(moduleName)");
|
|
198
|
-
keytar = dynamicRequire("keytar");
|
|
199
|
-
return keytar;
|
|
200
|
-
} catch {
|
|
201
|
-
keytar = null;
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
async function loadKeytar() {
|
|
206
|
-
return loadKeytarSync();
|
|
207
|
-
}
|
|
208
|
-
function getConfigDir() {
|
|
209
|
-
const customConfigDir = process.env.INDIGO_CONFIG_DIR;
|
|
210
|
-
if (customConfigDir) {
|
|
211
|
-
if (!fs2.existsSync(customConfigDir)) {
|
|
212
|
-
fs2.mkdirSync(customConfigDir, { recursive: true, mode: 448 });
|
|
213
|
-
}
|
|
214
|
-
return customConfigDir;
|
|
215
|
-
}
|
|
216
|
-
const home = os2.homedir();
|
|
217
|
-
const configDir = path2.join(home, ".indigo");
|
|
218
|
-
if (!fs2.existsSync(configDir)) {
|
|
219
|
-
fs2.mkdirSync(configDir, { mode: 448 });
|
|
220
|
-
}
|
|
221
|
-
return configDir;
|
|
222
|
-
}
|
|
223
|
-
function getConfigFilePath() {
|
|
224
|
-
return path2.join(getConfigDir(), "credentials.json");
|
|
225
|
-
}
|
|
226
|
-
function getEncryptionKey() {
|
|
227
|
-
const machineId = `${os2.hostname()}-${os2.userInfo().username}-indigo-cli`;
|
|
228
|
-
return crypto.createHash("sha256").update(machineId).digest();
|
|
229
|
-
}
|
|
230
|
-
function encrypt(text) {
|
|
231
|
-
const key = getEncryptionKey();
|
|
232
|
-
const iv = crypto.randomBytes(12);
|
|
233
|
-
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
234
|
-
const encrypted = Buffer.concat([
|
|
235
|
-
cipher.update(text, "utf8"),
|
|
236
|
-
cipher.final()
|
|
237
|
-
]);
|
|
238
|
-
const authTag = cipher.getAuthTag();
|
|
239
|
-
return `enc:${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted.toString("base64")}`;
|
|
240
|
-
}
|
|
241
|
-
function decrypt(encryptedText) {
|
|
242
|
-
if (!encryptedText.startsWith("enc:")) {
|
|
243
|
-
return encryptedText;
|
|
244
|
-
}
|
|
245
|
-
try {
|
|
246
|
-
const parts = encryptedText.split(":");
|
|
247
|
-
if (parts.length !== 4)
|
|
248
|
-
return null;
|
|
249
|
-
const iv = Buffer.from(parts[1], "base64");
|
|
250
|
-
const authTag = Buffer.from(parts[2], "base64");
|
|
251
|
-
const encrypted = Buffer.from(parts[3], "base64");
|
|
252
|
-
const key = getEncryptionKey();
|
|
253
|
-
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
254
|
-
decipher.setAuthTag(authTag);
|
|
255
|
-
const decrypted = Buffer.concat([
|
|
256
|
-
decipher.update(encrypted),
|
|
257
|
-
decipher.final()
|
|
258
|
-
]);
|
|
259
|
-
return decrypted.toString("utf8");
|
|
260
|
-
} catch {
|
|
261
|
-
return null;
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
function readConfigFile() {
|
|
265
|
-
const filePath = getConfigFilePath();
|
|
266
|
-
try {
|
|
267
|
-
if (fs2.existsSync(filePath)) {
|
|
268
|
-
const content = fs2.readFileSync(filePath, "utf8");
|
|
269
|
-
return JSON.parse(content);
|
|
270
|
-
}
|
|
271
|
-
} catch {
|
|
272
|
-
}
|
|
273
|
-
return {};
|
|
274
|
-
}
|
|
275
|
-
function writeConfigFile(data) {
|
|
276
|
-
const filePath = getConfigFilePath();
|
|
277
|
-
fs2.writeFileSync(filePath, JSON.stringify(data, null, 2), {
|
|
278
|
-
mode: 384
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
async function saveCredentials(credentials) {
|
|
282
|
-
const kt = await loadKeytar();
|
|
283
|
-
const credsWithSource = { ...credentials, source: "cli" };
|
|
284
|
-
const jsonData = JSON.stringify(credsWithSource);
|
|
285
|
-
if (kt) {
|
|
286
|
-
try {
|
|
287
|
-
await kt.setPassword(SERVICE_NAME, ACCOUNT_NAME, jsonData);
|
|
288
|
-
return { method: "keytar" };
|
|
289
|
-
} catch {
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
const encrypted = encrypt(jsonData);
|
|
293
|
-
const configData = readConfigFile();
|
|
294
|
-
configData.credentials = { ...credsWithSource, token: encrypted };
|
|
295
|
-
writeConfigFile(configData);
|
|
296
|
-
return { method: "file" };
|
|
297
|
-
}
|
|
298
|
-
async function loadCliCredentials() {
|
|
299
|
-
const kt = await loadKeytar();
|
|
300
|
-
if (kt) {
|
|
301
|
-
try {
|
|
302
|
-
const stored = await kt.getPassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
303
|
-
if (stored) {
|
|
304
|
-
const creds = JSON.parse(stored);
|
|
305
|
-
return { ...creds, source: "cli" };
|
|
306
|
-
}
|
|
307
|
-
} catch {
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
const configData = readConfigFile();
|
|
311
|
-
if (configData.credentials) {
|
|
312
|
-
const decrypted = decrypt(configData.credentials.token);
|
|
313
|
-
if (decrypted) {
|
|
314
|
-
try {
|
|
315
|
-
const parsed = JSON.parse(decrypted);
|
|
316
|
-
return { ...parsed, source: "cli" };
|
|
317
|
-
} catch {
|
|
318
|
-
return {
|
|
319
|
-
...configData.credentials,
|
|
320
|
-
token: decrypted,
|
|
321
|
-
source: "cli"
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
return null;
|
|
327
|
-
}
|
|
328
|
-
async function loadCredentials() {
|
|
329
|
-
const cliCreds = await loadCliCredentials();
|
|
330
|
-
if (cliCreds) {
|
|
331
|
-
return cliCreds;
|
|
332
|
-
}
|
|
333
|
-
if (process.env.INDIGO_DISABLE_ELECTRON_CREDS !== "true") {
|
|
334
|
-
const electronCreds = loadElectronCredentials();
|
|
335
|
-
if (electronCreds) {
|
|
336
|
-
return {
|
|
337
|
-
token: electronCreds.token,
|
|
338
|
-
userId: electronCreds.userId,
|
|
339
|
-
email: electronCreds.email,
|
|
340
|
-
name: electronCreds.name,
|
|
341
|
-
storedAt: "",
|
|
342
|
-
// Unknown when Electron stored it
|
|
343
|
-
source: "electron"
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
async function deleteCredentials() {
|
|
350
|
-
const kt = await loadKeytar();
|
|
351
|
-
if (kt) {
|
|
352
|
-
try {
|
|
353
|
-
await kt.deletePassword(SERVICE_NAME, ACCOUNT_NAME);
|
|
354
|
-
} catch {
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
const configData = readConfigFile();
|
|
358
|
-
delete configData.credentials;
|
|
359
|
-
writeConfigFile(configData);
|
|
360
|
-
}
|
|
361
|
-
function getStorageMethod() {
|
|
362
|
-
try {
|
|
363
|
-
require.resolve("keytar");
|
|
364
|
-
return "keytar";
|
|
365
|
-
} catch {
|
|
366
|
-
return "file";
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// apps/cli/src/services/auth.ts
|
|
371
|
-
function decodeJWT(token) {
|
|
372
|
-
try {
|
|
373
|
-
const parts = token.split(".");
|
|
374
|
-
if (parts.length !== 3)
|
|
375
|
-
return null;
|
|
376
|
-
const payload = Buffer.from(parts[1], "base64").toString("utf8");
|
|
377
|
-
return JSON.parse(payload);
|
|
378
|
-
} catch {
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
function validateToken(token) {
|
|
383
|
-
if (!token || typeof token !== "string")
|
|
384
|
-
return false;
|
|
385
|
-
try {
|
|
386
|
-
const decoded = decodeJWT(token);
|
|
387
|
-
if (!decoded?.exp)
|
|
388
|
-
return false;
|
|
389
|
-
const now = Date.now() / 1e3;
|
|
390
|
-
return decoded.exp > now;
|
|
391
|
-
} catch {
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
async function fetchUserProfile(token) {
|
|
396
|
-
try {
|
|
397
|
-
const response = await fetch(`${apiUrl}/users`, {
|
|
398
|
-
method: "GET",
|
|
399
|
-
headers: {
|
|
400
|
-
"Content-Type": "application/json",
|
|
401
|
-
Authorization: `Bearer ${token}`
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
if (!response.ok) {
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
const user = await response.json();
|
|
408
|
-
const firstName = user.firstName || user.first_name || "";
|
|
409
|
-
const lastName = user.lastName || user.last_name || "";
|
|
410
|
-
const fullName = user.fullName || [firstName, lastName].filter(Boolean).join(" ");
|
|
411
|
-
return {
|
|
412
|
-
id: user.id || user.clerkId,
|
|
413
|
-
email: user.email,
|
|
414
|
-
name: fullName || void 0,
|
|
415
|
-
firstName: firstName || void 0,
|
|
416
|
-
lastName: lastName || void 0,
|
|
417
|
-
avatarUrl: user.avatar || void 0,
|
|
418
|
-
company: user.company
|
|
419
|
-
};
|
|
420
|
-
} catch {
|
|
421
|
-
return null;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
async function refreshToken(currentToken) {
|
|
425
|
-
try {
|
|
426
|
-
const response = await fetch(`${apiUrl}/users/refresh-jwt-token`, {
|
|
427
|
-
method: "GET",
|
|
428
|
-
headers: {
|
|
429
|
-
"Content-Type": "application/json",
|
|
430
|
-
Authorization: `Bearer ${currentToken}`
|
|
431
|
-
}
|
|
432
|
-
});
|
|
433
|
-
if (!response.ok) {
|
|
434
|
-
return null;
|
|
435
|
-
}
|
|
436
|
-
const data = await response.json();
|
|
437
|
-
return data.jwt || null;
|
|
438
|
-
} catch {
|
|
439
|
-
return null;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
async function authenticate(token) {
|
|
443
|
-
if (!validateToken(token)) {
|
|
444
|
-
return { success: false, error: "Invalid or expired token" };
|
|
445
|
-
}
|
|
446
|
-
const decoded = decodeJWT(token);
|
|
447
|
-
if (!decoded) {
|
|
448
|
-
return { success: false, error: "Could not decode token" };
|
|
449
|
-
}
|
|
450
|
-
const user = await fetchUserProfile(token);
|
|
451
|
-
const { method } = await saveCredentials({
|
|
452
|
-
token,
|
|
453
|
-
userId: decoded.sub,
|
|
454
|
-
email: user?.email,
|
|
455
|
-
name: user?.name,
|
|
456
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
457
|
-
});
|
|
458
|
-
return {
|
|
459
|
-
success: true,
|
|
460
|
-
user: user || {
|
|
461
|
-
id: decoded.sub,
|
|
462
|
-
name: decoded["user.first_name"]
|
|
463
|
-
},
|
|
464
|
-
storageMethod: method
|
|
465
|
-
};
|
|
466
|
-
}
|
|
467
|
-
async function getAuthState() {
|
|
468
|
-
const credentials = await loadCredentials();
|
|
469
|
-
if (!credentials || !credentials.token) {
|
|
470
|
-
return { isAuthenticated: false };
|
|
471
|
-
}
|
|
472
|
-
const credentialSource = credentials.source;
|
|
473
|
-
if (!validateToken(credentials.token)) {
|
|
474
|
-
const newToken = await refreshToken(credentials.token);
|
|
475
|
-
if (newToken && validateToken(newToken)) {
|
|
476
|
-
const user = await fetchUserProfile(newToken);
|
|
477
|
-
const decoded2 = decodeJWT(newToken);
|
|
478
|
-
if (credentialSource === "cli") {
|
|
479
|
-
await saveCredentials({
|
|
480
|
-
token: newToken,
|
|
481
|
-
userId: credentials.userId,
|
|
482
|
-
email: user?.email || credentials.email,
|
|
483
|
-
name: user?.name || credentials.name,
|
|
484
|
-
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
return {
|
|
488
|
-
isAuthenticated: true,
|
|
489
|
-
user: user || {
|
|
490
|
-
id: credentials.userId,
|
|
491
|
-
email: credentials.email,
|
|
492
|
-
name: credentials.name
|
|
493
|
-
},
|
|
494
|
-
tokenExpiry: decoded2 ? new Date(decoded2.exp * 1e3) : void 0,
|
|
495
|
-
credentialSource
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
return { isAuthenticated: false };
|
|
499
|
-
}
|
|
500
|
-
const decoded = decodeJWT(credentials.token);
|
|
501
|
-
return {
|
|
502
|
-
isAuthenticated: true,
|
|
503
|
-
user: {
|
|
504
|
-
id: credentials.userId,
|
|
505
|
-
email: credentials.email,
|
|
506
|
-
name: credentials.name
|
|
507
|
-
},
|
|
508
|
-
tokenExpiry: decoded ? new Date(decoded.exp * 1e3) : void 0,
|
|
509
|
-
credentialSource
|
|
510
|
-
};
|
|
511
|
-
}
|
|
512
|
-
async function logout() {
|
|
513
|
-
await deleteCredentials();
|
|
514
|
-
}
|
|
515
|
-
async function getToken() {
|
|
516
|
-
const credentials = await loadCredentials();
|
|
517
|
-
if (!credentials || !credentials.token)
|
|
518
|
-
return null;
|
|
519
|
-
if (!validateToken(credentials.token))
|
|
520
|
-
return null;
|
|
521
|
-
return credentials.token;
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// apps/cli/src/commands/auth/login.ts
|
|
525
|
-
var CALLBACK_PORT_START = 9876;
|
|
526
|
-
var CALLBACK_PORT_END = 9886;
|
|
527
|
-
var AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
528
|
-
function openBrowser(url) {
|
|
529
|
-
const platform = process.platform;
|
|
530
|
-
let command;
|
|
531
|
-
if (platform === "darwin") {
|
|
532
|
-
command = `open "${url}"`;
|
|
533
|
-
} else if (platform === "win32") {
|
|
534
|
-
command = `start "" "${url}"`;
|
|
535
|
-
} else {
|
|
536
|
-
command = `xdg-open "${url}"`;
|
|
537
|
-
}
|
|
538
|
-
(0, import_child_process.exec)(command, (error) => {
|
|
539
|
-
if (error) {
|
|
540
|
-
console.error("Could not open browser automatically.");
|
|
541
|
-
console.log(`Please open this URL manually:
|
|
542
|
-
${url}`);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
async function findAvailablePort() {
|
|
547
|
-
for (let port = CALLBACK_PORT_START; port <= CALLBACK_PORT_END; port++) {
|
|
548
|
-
const available = await new Promise((resolve) => {
|
|
549
|
-
const server = http.createServer();
|
|
550
|
-
server.once("error", () => resolve(false));
|
|
551
|
-
server.once("listening", () => {
|
|
552
|
-
server.close();
|
|
553
|
-
resolve(true);
|
|
554
|
-
});
|
|
555
|
-
server.listen(port, "127.0.0.1");
|
|
556
|
-
});
|
|
557
|
-
if (available)
|
|
558
|
-
return port;
|
|
559
|
-
}
|
|
560
|
-
throw new Error("No available port found for callback server");
|
|
561
|
-
}
|
|
562
|
-
function createCallbackServer(port, state) {
|
|
563
|
-
return new Promise((resolve, reject) => {
|
|
564
|
-
const server = http.createServer((req, res) => {
|
|
565
|
-
const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
|
|
566
|
-
if (req.method === "OPTIONS") {
|
|
567
|
-
res.writeHead(204, {
|
|
568
|
-
"Access-Control-Allow-Origin": "*",
|
|
569
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
570
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
571
|
-
});
|
|
572
|
-
res.end();
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
if (url.pathname === "/callback") {
|
|
576
|
-
const token = url.searchParams.get("token");
|
|
577
|
-
const returnedState = url.searchParams.get("state");
|
|
578
|
-
if (!token) {
|
|
579
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
580
|
-
res.end(`
|
|
581
|
-
<html>
|
|
582
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
583
|
-
<h1 style="color: #e53e3e;">Authentication Failed</h1>
|
|
584
|
-
<p>No token received. Please try again.</p>
|
|
585
|
-
<p>You can close this window.</p>
|
|
586
|
-
</body>
|
|
587
|
-
</html>
|
|
588
|
-
`);
|
|
589
|
-
server.close();
|
|
590
|
-
reject(new Error("No token received from authentication"));
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
if (returnedState !== state) {
|
|
594
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
595
|
-
res.end(`
|
|
596
|
-
<html>
|
|
597
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
598
|
-
<h1 style="color: #e53e3e;">Authentication Failed</h1>
|
|
599
|
-
<p>Security state mismatch. Please try again.</p>
|
|
600
|
-
<p>You can close this window.</p>
|
|
601
|
-
</body>
|
|
602
|
-
</html>
|
|
603
|
-
`);
|
|
604
|
-
server.close();
|
|
605
|
-
reject(new Error("State mismatch - possible security issue"));
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
609
|
-
res.end(`
|
|
610
|
-
<html>
|
|
611
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
612
|
-
<h1 style="color: #38a169;">Authentication Successful!</h1>
|
|
613
|
-
<p>You can close this window and return to the terminal.</p>
|
|
614
|
-
<script>setTimeout(() => window.close(), 2000);</script>
|
|
615
|
-
</body>
|
|
616
|
-
</html>
|
|
617
|
-
`);
|
|
618
|
-
server.close();
|
|
619
|
-
resolve({ token });
|
|
620
|
-
} else {
|
|
621
|
-
res.writeHead(404);
|
|
622
|
-
res.end("Not found");
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
const timeout = setTimeout(() => {
|
|
626
|
-
server.close();
|
|
627
|
-
reject(new Error("Authentication timed out. Please try again."));
|
|
628
|
-
}, AUTH_TIMEOUT_MS);
|
|
629
|
-
server.on("close", () => clearTimeout(timeout));
|
|
630
|
-
server.listen(port, "127.0.0.1");
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
async function login() {
|
|
634
|
-
try {
|
|
635
|
-
const state = crypto2.randomBytes(16).toString("hex");
|
|
636
|
-
const port = await findAvailablePort();
|
|
637
|
-
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
638
|
-
const authUrl = new URL(`${webauthUrl}/cli-auth`);
|
|
639
|
-
authUrl.searchParams.set("callback", callbackUrl);
|
|
640
|
-
authUrl.searchParams.set("state", state);
|
|
641
|
-
console.log("\nOpening browser for authentication...");
|
|
642
|
-
console.log(`
|
|
643
|
-
If browser doesn't open, visit:
|
|
644
|
-
${authUrl.toString()}
|
|
645
|
-
`);
|
|
646
|
-
const serverPromise = createCallbackServer(port, state);
|
|
647
|
-
openBrowser(authUrl.toString());
|
|
648
|
-
console.log("Waiting for authentication...");
|
|
649
|
-
console.log("(Press Ctrl+C to cancel)\n");
|
|
650
|
-
const { token } = await serverPromise;
|
|
651
|
-
const result = await authenticate(token);
|
|
652
|
-
if (result.success) {
|
|
653
|
-
return {
|
|
654
|
-
success: true,
|
|
655
|
-
user: result.user,
|
|
656
|
-
storageMethod: result.storageMethod
|
|
657
|
-
};
|
|
658
|
-
} else {
|
|
659
|
-
return {
|
|
660
|
-
success: false,
|
|
661
|
-
error: result.error || "Authentication failed"
|
|
662
|
-
};
|
|
663
|
-
}
|
|
664
|
-
} catch (error) {
|
|
665
|
-
return {
|
|
666
|
-
success: false,
|
|
667
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
668
|
-
};
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// apps/cli/src/utils/output.ts
|
|
673
|
-
var ExitCode = {
|
|
674
|
-
SUCCESS: 0,
|
|
675
|
-
GENERAL_ERROR: 1,
|
|
676
|
-
AUTH_REQUIRED: 2,
|
|
677
|
-
NOT_FOUND: 3,
|
|
678
|
-
CONFIGURATION_ERROR: 4,
|
|
679
|
-
VALIDATION_ERROR: 5
|
|
680
|
-
};
|
|
681
|
-
function createOutputContext(options) {
|
|
682
|
-
return {
|
|
683
|
-
json: options.json === true
|
|
684
|
-
};
|
|
685
|
-
}
|
|
686
|
-
var noop = () => {
|
|
687
|
-
};
|
|
688
|
-
function outputSuccess(ctx, data, humanFormatter) {
|
|
689
|
-
if (ctx.json) {
|
|
690
|
-
const response = {
|
|
691
|
-
success: true,
|
|
692
|
-
data
|
|
693
|
-
};
|
|
694
|
-
console.log(JSON.stringify(response, null, 2));
|
|
695
|
-
} else {
|
|
696
|
-
humanFormatter(data);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
function outputData(ctx, data, humanFormatter) {
|
|
700
|
-
if (ctx.json) {
|
|
701
|
-
console.log(JSON.stringify(data, null, 2));
|
|
702
|
-
} else {
|
|
703
|
-
humanFormatter(data);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
function outputError(ctx, message, options = {}) {
|
|
707
|
-
const exitCode = options.exitCode ?? ExitCode.GENERAL_ERROR;
|
|
708
|
-
if (ctx.json) {
|
|
709
|
-
const response = {
|
|
710
|
-
success: false,
|
|
711
|
-
error: {
|
|
712
|
-
message,
|
|
713
|
-
...options.code && { code: options.code },
|
|
714
|
-
...options.details && { details: options.details }
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
console.log(JSON.stringify(response, null, 2));
|
|
718
|
-
} else {
|
|
719
|
-
console.error(`
|
|
720
|
-
Error: ${message}`);
|
|
721
|
-
if (options.details) {
|
|
722
|
-
for (const [key, value] of Object.entries(options.details)) {
|
|
723
|
-
console.error(` ${key}: ${value}`);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
process.exit(exitCode);
|
|
728
|
-
}
|
|
729
|
-
function outputAuthRequired(ctx) {
|
|
730
|
-
outputError(ctx, 'Not authenticated. Run "indigo auth login" first.', {
|
|
731
|
-
code: "AUTH_REQUIRED",
|
|
732
|
-
exitCode: ExitCode.AUTH_REQUIRED
|
|
733
|
-
});
|
|
734
|
-
}
|
|
735
|
-
function outputNotFound(ctx, resource, id) {
|
|
736
|
-
outputError(ctx, `${resource} with ID "${id}" not found.`, {
|
|
737
|
-
code: "NOT_FOUND",
|
|
738
|
-
details: { resource, id },
|
|
739
|
-
exitCode: ExitCode.NOT_FOUND
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
function outputConfigError(ctx, message, requiredVars) {
|
|
743
|
-
outputError(ctx, message, {
|
|
744
|
-
code: "CONFIGURATION_ERROR",
|
|
745
|
-
...requiredVars && { details: { requiredVariables: requiredVars } },
|
|
746
|
-
exitCode: ExitCode.CONFIGURATION_ERROR
|
|
747
|
-
});
|
|
748
|
-
}
|
|
749
|
-
function outputValidationError(ctx, message, howToFix) {
|
|
750
|
-
const fullMessage = howToFix ? `${message}. ${howToFix}` : message;
|
|
751
|
-
outputError(ctx, fullMessage, {
|
|
752
|
-
code: "VALIDATION_ERROR",
|
|
753
|
-
exitCode: ExitCode.VALIDATION_ERROR
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
function logHuman(ctx, message) {
|
|
757
|
-
if (!ctx.json) {
|
|
758
|
-
console.log(message);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// apps/cli/src/commands/auth/index.ts
|
|
763
|
-
function configureValidationErrorHandling(cmd) {
|
|
764
|
-
cmd.showHelpAfterError(false);
|
|
765
|
-
cmd.configureOutput({
|
|
766
|
-
writeErr: (str) => {
|
|
767
|
-
const unknownOptionMatch = str.match(/error: unknown option '([^']+)'/i);
|
|
768
|
-
if (unknownOptionMatch) {
|
|
769
|
-
const option = unknownOptionMatch[1];
|
|
770
|
-
console.error(`
|
|
771
|
-
Error: Unknown option '${option}'. Run 'indigo auth ${cmd.name()} --help' to see available options.`);
|
|
772
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
773
|
-
}
|
|
774
|
-
const errorMatch = str.match(/error: (.+)/i);
|
|
775
|
-
if (errorMatch) {
|
|
776
|
-
console.error(`
|
|
777
|
-
Error: ${errorMatch[1]}. Run 'indigo auth ${cmd.name()} --help' for usage information.`);
|
|
778
|
-
process.exit(ExitCode.VALIDATION_ERROR);
|
|
779
|
-
}
|
|
780
|
-
process.stderr.write(str);
|
|
781
|
-
}
|
|
782
|
-
});
|
|
783
|
-
}
|
|
784
|
-
function validateBooleanFlag(value, flagName) {
|
|
785
|
-
if (value === void 0 || value === true || value === false) {
|
|
786
|
-
return value === true;
|
|
787
|
-
}
|
|
788
|
-
if (typeof value === "string") {
|
|
789
|
-
const lower = value.toLowerCase();
|
|
790
|
-
if (lower === "true" || lower === "1" || lower === "yes") {
|
|
791
|
-
return true;
|
|
792
|
-
}
|
|
793
|
-
if (lower === "false" || lower === "0" || lower === "no") {
|
|
794
|
-
return false;
|
|
795
|
-
}
|
|
796
|
-
throw new Error(`Invalid value '${value}' for ${flagName}. Use --${flagName.replace(/^--/, "")} without a value, or remove it.`);
|
|
797
|
-
}
|
|
798
|
-
return Boolean(value);
|
|
799
|
-
}
|
|
800
|
-
function createAuthCommand() {
|
|
801
|
-
const auth = new import_commander.Command("auth").description("Authentication commands");
|
|
802
|
-
const loginCmd = auth.command("login").description("Log in to your Indigo account").option("--force", "Force CLI-specific login even if Electron credentials exist").option("--json", "Output as JSON for scripting").action(async (options) => {
|
|
803
|
-
const ctx = createOutputContext(options);
|
|
804
|
-
try {
|
|
805
|
-
validateBooleanFlag(options.force, "--force");
|
|
806
|
-
} catch (error) {
|
|
807
|
-
outputValidationError(
|
|
808
|
-
ctx,
|
|
809
|
-
error instanceof Error ? error.message : "Invalid option value",
|
|
810
|
-
"Run 'indigo auth login --help' for usage information"
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
try {
|
|
814
|
-
const currentState = await getAuthState();
|
|
815
|
-
if (currentState.isAuthenticated && currentState.user) {
|
|
816
|
-
const source = currentState.credentialSource;
|
|
817
|
-
const userDisplay = currentState.user.email || currentState.user.name || currentState.user.id;
|
|
818
|
-
if (source === "electron" && !options.force) {
|
|
819
|
-
if (ctx.json) {
|
|
820
|
-
outputSuccess(ctx, {
|
|
821
|
-
status: "already_authenticated",
|
|
822
|
-
source: "electron",
|
|
823
|
-
user: {
|
|
824
|
-
id: currentState.user.id,
|
|
825
|
-
email: currentState.user.email,
|
|
826
|
-
name: currentState.user.name
|
|
827
|
-
},
|
|
828
|
-
message: "Using credentials from Indigo desktop app. Use --force to create CLI-specific credentials."
|
|
829
|
-
}, noop);
|
|
830
|
-
} else {
|
|
831
|
-
console.log(`Using credentials from Indigo desktop app (${userDisplay})`);
|
|
832
|
-
console.log("\nTo create CLI-specific credentials, run:");
|
|
833
|
-
console.log(" indigo auth login --force");
|
|
834
|
-
}
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (source === "cli") {
|
|
838
|
-
if (ctx.json) {
|
|
839
|
-
outputSuccess(ctx, {
|
|
840
|
-
status: "already_authenticated",
|
|
841
|
-
source: "cli",
|
|
842
|
-
user: {
|
|
843
|
-
id: currentState.user.id,
|
|
844
|
-
email: currentState.user.email,
|
|
845
|
-
name: currentState.user.name
|
|
846
|
-
},
|
|
847
|
-
message: 'Already logged in. Use "indigo auth logout" to log in as a different user.'
|
|
848
|
-
}, noop);
|
|
849
|
-
} else {
|
|
850
|
-
console.log(`Already logged in as ${userDisplay}`);
|
|
851
|
-
console.log('Use "indigo auth logout" first to log in as a different user.');
|
|
852
|
-
}
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
const result = await login();
|
|
857
|
-
if (result.success && result.user) {
|
|
858
|
-
const storageDesc = result.storageMethod === "keytar" ? "system keychain" : "encrypted config file";
|
|
859
|
-
if (ctx.json) {
|
|
860
|
-
outputSuccess(ctx, {
|
|
861
|
-
status: "logged_in",
|
|
862
|
-
source: "cli",
|
|
863
|
-
user: {
|
|
864
|
-
id: result.user.id,
|
|
865
|
-
email: result.user.email,
|
|
866
|
-
name: result.user.name
|
|
867
|
-
},
|
|
868
|
-
storageMethod: result.storageMethod
|
|
869
|
-
}, noop);
|
|
870
|
-
} else {
|
|
871
|
-
console.log("\nSuccessfully logged in!");
|
|
872
|
-
console.log(` User: ${result.user.email || result.user.name || result.user.id}`);
|
|
873
|
-
if (result.storageMethod) {
|
|
874
|
-
console.log(` Credentials stored in: ${storageDesc}`);
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
} else {
|
|
878
|
-
outputError(ctx, result.error || "Login failed", {
|
|
879
|
-
code: "LOGIN_FAILED",
|
|
880
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
} catch (error) {
|
|
884
|
-
outputError(ctx, error instanceof Error ? error.message : "Unknown error", {
|
|
885
|
-
code: "LOGIN_FAILED",
|
|
886
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
configureValidationErrorHandling(loginCmd);
|
|
891
|
-
const logoutCmd = auth.command("logout").description("Log out of your Indigo account").option("--json", "Output as JSON for scripting").action(async (options) => {
|
|
892
|
-
const ctx = createOutputContext(options);
|
|
893
|
-
try {
|
|
894
|
-
const currentState = await getAuthState();
|
|
895
|
-
if (!currentState.isAuthenticated) {
|
|
896
|
-
if (ctx.json) {
|
|
897
|
-
outputSuccess(ctx, {
|
|
898
|
-
status: "not_logged_in",
|
|
899
|
-
message: "Not currently logged in."
|
|
900
|
-
}, noop);
|
|
901
|
-
} else {
|
|
902
|
-
console.log("Not currently logged in.");
|
|
903
|
-
}
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
await logout();
|
|
907
|
-
if (ctx.json) {
|
|
908
|
-
outputSuccess(ctx, {
|
|
909
|
-
status: "logged_out",
|
|
910
|
-
message: "Successfully logged out."
|
|
911
|
-
}, noop);
|
|
912
|
-
} else {
|
|
913
|
-
console.log("Successfully logged out.");
|
|
914
|
-
}
|
|
915
|
-
} catch (error) {
|
|
916
|
-
outputError(ctx, error instanceof Error ? error.message : "Unknown error", {
|
|
917
|
-
code: "LOGOUT_FAILED",
|
|
918
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
configureValidationErrorHandling(logoutCmd);
|
|
923
|
-
const statusCmd = auth.command("status").description("Show current authentication status").option("--json", "Output as JSON for scripting").action(async (options) => {
|
|
924
|
-
const ctx = createOutputContext(options);
|
|
925
|
-
try {
|
|
926
|
-
const state = await getAuthState();
|
|
927
|
-
const storageMethod = getStorageMethod();
|
|
928
|
-
if (ctx.json) {
|
|
929
|
-
if (state.isAuthenticated && state.user) {
|
|
930
|
-
let tokenStatus;
|
|
931
|
-
let expiresIn;
|
|
932
|
-
if (state.tokenExpiry) {
|
|
933
|
-
const now = /* @__PURE__ */ new Date();
|
|
934
|
-
expiresIn = Math.floor(
|
|
935
|
-
(state.tokenExpiry.getTime() - now.getTime()) / 1e3 / 60
|
|
936
|
-
);
|
|
937
|
-
tokenStatus = expiresIn > 0 ? "valid" : "expired";
|
|
938
|
-
}
|
|
939
|
-
outputSuccess(ctx, {
|
|
940
|
-
isAuthenticated: true,
|
|
941
|
-
user: {
|
|
942
|
-
id: state.user.id,
|
|
943
|
-
email: state.user.email,
|
|
944
|
-
name: state.user.name
|
|
945
|
-
},
|
|
946
|
-
credentialSource: state.credentialSource,
|
|
947
|
-
storageMethod: state.credentialSource === "cli" ? storageMethod : void 0,
|
|
948
|
-
token: {
|
|
949
|
-
status: tokenStatus,
|
|
950
|
-
expiresInMinutes: expiresIn
|
|
951
|
-
}
|
|
952
|
-
}, noop);
|
|
953
|
-
} else {
|
|
954
|
-
outputSuccess(ctx, {
|
|
955
|
-
isAuthenticated: false,
|
|
956
|
-
user: null,
|
|
957
|
-
message: 'Not logged in. Run "indigo auth login" to authenticate.'
|
|
958
|
-
}, noop);
|
|
959
|
-
}
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
console.log("\nAuthentication Status");
|
|
963
|
-
console.log("\u2500".repeat(40));
|
|
964
|
-
if (state.isAuthenticated && state.user) {
|
|
965
|
-
console.log(`Status: Logged in`);
|
|
966
|
-
console.log(`User: ${state.user.email || state.user.name || "Unknown"}`);
|
|
967
|
-
if (state.user.id) {
|
|
968
|
-
console.log(`User ID: ${state.user.id}`);
|
|
969
|
-
}
|
|
970
|
-
if (state.tokenExpiry) {
|
|
971
|
-
const now = /* @__PURE__ */ new Date();
|
|
972
|
-
const expiresIn = Math.floor(
|
|
973
|
-
(state.tokenExpiry.getTime() - now.getTime()) / 1e3 / 60
|
|
974
|
-
);
|
|
975
|
-
if (expiresIn > 0) {
|
|
976
|
-
console.log(`Token: Expires in ${expiresIn} minutes`);
|
|
977
|
-
} else {
|
|
978
|
-
console.log(`Token: Expired (will refresh on next request)`);
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
const sourceDesc = state.credentialSource === "electron" ? "Indigo desktop app" : "CLI";
|
|
982
|
-
console.log(`Source: ${sourceDesc}`);
|
|
983
|
-
if (state.credentialSource === "cli") {
|
|
984
|
-
const storageDesc = storageMethod === "keytar" ? "System keychain" : "Encrypted config file";
|
|
985
|
-
console.log(`Storage: ${storageDesc}`);
|
|
986
|
-
}
|
|
987
|
-
if (state.credentialSource === "electron") {
|
|
988
|
-
console.log("\n(Using credentials from Indigo desktop app)");
|
|
989
|
-
console.log('Run "indigo auth login --force" to create CLI-specific credentials.');
|
|
990
|
-
}
|
|
991
|
-
} else {
|
|
992
|
-
console.log("Status: Not logged in");
|
|
993
|
-
console.log('\nRun "indigo auth login" to authenticate.');
|
|
994
|
-
}
|
|
995
|
-
console.log("");
|
|
996
|
-
} catch (error) {
|
|
997
|
-
outputError(ctx, error instanceof Error ? error.message : "Unknown error", {
|
|
998
|
-
code: "STATUS_ERROR",
|
|
999
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1000
|
-
});
|
|
1001
|
-
}
|
|
1002
|
-
});
|
|
1003
|
-
configureValidationErrorHandling(statusCmd);
|
|
1004
|
-
return auth;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// apps/cli/src/commands/account/index.ts
|
|
1008
|
-
var import_commander2 = require("commander");
|
|
1009
|
-
var http2 = __toESM(require("http"));
|
|
1010
|
-
var crypto3 = __toESM(require("crypto"));
|
|
1011
|
-
var import_child_process2 = require("child_process");
|
|
1012
|
-
var CALLBACK_PORT_START2 = 9876;
|
|
1013
|
-
var CALLBACK_PORT_END2 = 9886;
|
|
1014
|
-
var AUTH_TIMEOUT_MS2 = 10 * 60 * 1e3;
|
|
1015
|
-
function isValidEmail(email) {
|
|
1016
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
1017
|
-
return emailRegex.test(email);
|
|
1018
|
-
}
|
|
1019
|
-
async function createAccountWithPassword(email, password) {
|
|
1020
|
-
try {
|
|
1021
|
-
const response = await fetch(`${apiUrl}/users/dev/create-with-password`, {
|
|
1022
|
-
method: "POST",
|
|
1023
|
-
headers: {
|
|
1024
|
-
"Content-Type": "application/json"
|
|
1025
|
-
},
|
|
1026
|
-
body: JSON.stringify({ email, password })
|
|
1027
|
-
});
|
|
1028
|
-
if (!response.ok) {
|
|
1029
|
-
const errorText = await response.text();
|
|
1030
|
-
let errorMessage = "Account creation failed";
|
|
1031
|
-
try {
|
|
1032
|
-
const errorJson = JSON.parse(errorText);
|
|
1033
|
-
errorMessage = errorJson.message || errorJson.error || errorText;
|
|
1034
|
-
} catch {
|
|
1035
|
-
errorMessage = errorText;
|
|
1036
|
-
}
|
|
1037
|
-
if (errorMessage.includes("only available in development")) {
|
|
1038
|
-
return {
|
|
1039
|
-
success: false,
|
|
1040
|
-
error: 'Email/password signup is only available in development/test environments. Use "indigo account create" without flags to sign up via browser.'
|
|
1041
|
-
};
|
|
1042
|
-
}
|
|
1043
|
-
return {
|
|
1044
|
-
success: false,
|
|
1045
|
-
error: errorMessage
|
|
1046
|
-
};
|
|
1047
|
-
}
|
|
1048
|
-
const result = await response.json();
|
|
1049
|
-
if (!result.token) {
|
|
1050
|
-
return {
|
|
1051
|
-
success: false,
|
|
1052
|
-
error: "No token received from server"
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
const authResult = await authenticate(result.token);
|
|
1056
|
-
if (authResult.success) {
|
|
1057
|
-
return {
|
|
1058
|
-
success: true,
|
|
1059
|
-
user: authResult.user,
|
|
1060
|
-
storageMethod: authResult.storageMethod
|
|
1061
|
-
};
|
|
1062
|
-
} else {
|
|
1063
|
-
return {
|
|
1064
|
-
success: false,
|
|
1065
|
-
error: authResult.error || "Authentication failed after account creation"
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
1068
|
-
} catch (error) {
|
|
1069
|
-
return {
|
|
1070
|
-
success: false,
|
|
1071
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
function openBrowser2(url) {
|
|
1076
|
-
const platform = process.platform;
|
|
1077
|
-
let command;
|
|
1078
|
-
if (platform === "darwin") {
|
|
1079
|
-
command = `open "${url}"`;
|
|
1080
|
-
} else if (platform === "win32") {
|
|
1081
|
-
command = `start "" "${url}"`;
|
|
1082
|
-
} else {
|
|
1083
|
-
command = `xdg-open "${url}"`;
|
|
1084
|
-
}
|
|
1085
|
-
(0, import_child_process2.exec)(command, (error) => {
|
|
1086
|
-
if (error) {
|
|
1087
|
-
console.error("Could not open browser automatically.");
|
|
1088
|
-
console.log(`Please open this URL manually:
|
|
1089
|
-
${url}`);
|
|
1090
|
-
}
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
|
-
async function findAvailablePort2() {
|
|
1094
|
-
for (let port = CALLBACK_PORT_START2; port <= CALLBACK_PORT_END2; port++) {
|
|
1095
|
-
const available = await new Promise((resolve) => {
|
|
1096
|
-
const server = http2.createServer();
|
|
1097
|
-
server.once("error", () => resolve(false));
|
|
1098
|
-
server.once("listening", () => {
|
|
1099
|
-
server.close();
|
|
1100
|
-
resolve(true);
|
|
1101
|
-
});
|
|
1102
|
-
server.listen(port, "127.0.0.1");
|
|
1103
|
-
});
|
|
1104
|
-
if (available)
|
|
1105
|
-
return port;
|
|
1106
|
-
}
|
|
1107
|
-
throw new Error("No available port found for callback server");
|
|
1108
|
-
}
|
|
1109
|
-
function createCallbackServer2(port, state) {
|
|
1110
|
-
return new Promise((resolve, reject) => {
|
|
1111
|
-
const server = http2.createServer((req, res) => {
|
|
1112
|
-
const url = new URL(req.url || "/", `http://127.0.0.1:${port}`);
|
|
1113
|
-
if (req.method === "OPTIONS") {
|
|
1114
|
-
res.writeHead(204, {
|
|
1115
|
-
"Access-Control-Allow-Origin": "*",
|
|
1116
|
-
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
1117
|
-
"Access-Control-Allow-Headers": "Content-Type"
|
|
1118
|
-
});
|
|
1119
|
-
res.end();
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
if (url.pathname === "/callback") {
|
|
1123
|
-
const token = url.searchParams.get("token");
|
|
1124
|
-
const returnedState = url.searchParams.get("state");
|
|
1125
|
-
if (!token) {
|
|
1126
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1127
|
-
res.end(`
|
|
1128
|
-
<html>
|
|
1129
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center; background: #141414; color: white;">
|
|
1130
|
-
<h1 style="color: #e53e3e;">Account Creation Failed</h1>
|
|
1131
|
-
<p>No token received. Please try again.</p>
|
|
1132
|
-
<p style="color: #666;">You can close this window.</p>
|
|
1133
|
-
</body>
|
|
1134
|
-
</html>
|
|
1135
|
-
`);
|
|
1136
|
-
server.close();
|
|
1137
|
-
reject(new Error("No token received from account creation"));
|
|
1138
|
-
return;
|
|
1139
|
-
}
|
|
1140
|
-
if (returnedState !== state) {
|
|
1141
|
-
res.writeHead(400, { "Content-Type": "text/html" });
|
|
1142
|
-
res.end(`
|
|
1143
|
-
<html>
|
|
1144
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center; background: #141414; color: white;">
|
|
1145
|
-
<h1 style="color: #e53e3e;">Account Creation Failed</h1>
|
|
1146
|
-
<p>Security state mismatch. Please try again.</p>
|
|
1147
|
-
<p style="color: #666;">You can close this window.</p>
|
|
1148
|
-
</body>
|
|
1149
|
-
</html>
|
|
1150
|
-
`);
|
|
1151
|
-
server.close();
|
|
1152
|
-
reject(new Error("State mismatch - possible security issue"));
|
|
1153
|
-
return;
|
|
1154
|
-
}
|
|
1155
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1156
|
-
res.end(`
|
|
1157
|
-
<html>
|
|
1158
|
-
<body style="font-family: system-ui; padding: 40px; text-align: center; background: #141414; color: white;">
|
|
1159
|
-
<h1 style="color: #38a169;">Account Created Successfully!</h1>
|
|
1160
|
-
<p>Welcome to Indigo! You can close this window and return to the terminal.</p>
|
|
1161
|
-
<script>setTimeout(() => window.close(), 2000);</script>
|
|
1162
|
-
</body>
|
|
1163
|
-
</html>
|
|
1164
|
-
`);
|
|
1165
|
-
server.close();
|
|
1166
|
-
resolve({ token });
|
|
1167
|
-
} else {
|
|
1168
|
-
res.writeHead(404);
|
|
1169
|
-
res.end("Not found");
|
|
1170
|
-
}
|
|
1171
|
-
});
|
|
1172
|
-
const timeout = setTimeout(() => {
|
|
1173
|
-
server.close();
|
|
1174
|
-
reject(new Error("Account creation timed out. Please try again."));
|
|
1175
|
-
}, AUTH_TIMEOUT_MS2);
|
|
1176
|
-
server.on("close", () => clearTimeout(timeout));
|
|
1177
|
-
server.listen(port, "127.0.0.1");
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
async function createAccount() {
|
|
1181
|
-
try {
|
|
1182
|
-
const state = crypto3.randomBytes(16).toString("hex");
|
|
1183
|
-
const port = await findAvailablePort2();
|
|
1184
|
-
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
1185
|
-
const signupUrl = new URL(`${webauthUrl}/cli-auth`);
|
|
1186
|
-
signupUrl.searchParams.set("callback", callbackUrl);
|
|
1187
|
-
signupUrl.searchParams.set("state", state);
|
|
1188
|
-
signupUrl.searchParams.set("mode", "signup");
|
|
1189
|
-
console.log("\nOpening browser for account creation...");
|
|
1190
|
-
console.log(`
|
|
1191
|
-
If browser doesn't open, visit:
|
|
1192
|
-
${signupUrl.toString()}
|
|
1193
|
-
`);
|
|
1194
|
-
const serverPromise = createCallbackServer2(port, state);
|
|
1195
|
-
openBrowser2(signupUrl.toString());
|
|
1196
|
-
console.log("Waiting for account creation...");
|
|
1197
|
-
console.log("(Press Ctrl+C to cancel)\n");
|
|
1198
|
-
const { token } = await serverPromise;
|
|
1199
|
-
const result = await authenticate(token);
|
|
1200
|
-
if (result.success) {
|
|
1201
|
-
return {
|
|
1202
|
-
success: true,
|
|
1203
|
-
user: result.user,
|
|
1204
|
-
storageMethod: result.storageMethod
|
|
1205
|
-
};
|
|
1206
|
-
} else {
|
|
1207
|
-
return {
|
|
1208
|
-
success: false,
|
|
1209
|
-
error: result.error || "Account creation failed"
|
|
1210
|
-
};
|
|
1211
|
-
}
|
|
1212
|
-
} catch (error) {
|
|
1213
|
-
return {
|
|
1214
|
-
success: false,
|
|
1215
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
function createAccountCommand() {
|
|
1220
|
-
const account = new import_commander2.Command("account").description("Account management commands");
|
|
1221
|
-
account.command("create").description("Create a new Indigo account").option("--json", "Output as JSON for scripting").option("-e, --email <email>", "Email address (dev/test only)").option("-p, --password <password>", "Password (dev/test only)").action(async (options) => {
|
|
1222
|
-
const ctx = createOutputContext(options);
|
|
1223
|
-
try {
|
|
1224
|
-
const currentState = await getAuthState();
|
|
1225
|
-
if (currentState.isAuthenticated && currentState.user) {
|
|
1226
|
-
const userDisplay = currentState.user.email || currentState.user.name || currentState.user.id;
|
|
1227
|
-
if (ctx.json) {
|
|
1228
|
-
outputSuccess(ctx, {
|
|
1229
|
-
status: "already_authenticated",
|
|
1230
|
-
user: {
|
|
1231
|
-
id: currentState.user.id,
|
|
1232
|
-
email: currentState.user.email,
|
|
1233
|
-
name: currentState.user.name
|
|
1234
|
-
},
|
|
1235
|
-
message: 'Already logged in. Use "indigo auth logout" first to create a new account.'
|
|
1236
|
-
}, noop);
|
|
1237
|
-
} else {
|
|
1238
|
-
console.log(`Already logged in as ${userDisplay}`);
|
|
1239
|
-
console.log('Use "indigo auth logout" first if you want to create a new account.');
|
|
1240
|
-
}
|
|
1241
|
-
return;
|
|
1242
|
-
}
|
|
1243
|
-
if (options.email || options.password) {
|
|
1244
|
-
if (!options.email || !options.password) {
|
|
1245
|
-
outputValidationError(
|
|
1246
|
-
ctx,
|
|
1247
|
-
"Both --email and --password must be provided together",
|
|
1248
|
-
"Use: indigo account create --email <email> --password <password>"
|
|
1249
|
-
);
|
|
1250
|
-
return;
|
|
1251
|
-
}
|
|
1252
|
-
if (!isValidEmail(options.email)) {
|
|
1253
|
-
outputValidationError(
|
|
1254
|
-
ctx,
|
|
1255
|
-
"Invalid email format",
|
|
1256
|
-
"Expected format: user@example.com"
|
|
1257
|
-
);
|
|
1258
|
-
return;
|
|
1259
|
-
}
|
|
1260
|
-
if (options.password.length < 8) {
|
|
1261
|
-
outputValidationError(
|
|
1262
|
-
ctx,
|
|
1263
|
-
"Password must be at least 8 characters",
|
|
1264
|
-
"Password requires minimum 8 characters"
|
|
1265
|
-
);
|
|
1266
|
-
return;
|
|
1267
|
-
}
|
|
1268
|
-
if (environment !== "development" && environment !== "test") {
|
|
1269
|
-
outputError(
|
|
1270
|
-
ctx,
|
|
1271
|
-
'Email/password signup is only available in development/test environments. Use "indigo account create" without flags to sign up via browser.',
|
|
1272
|
-
{
|
|
1273
|
-
code: "ENVIRONMENT_ERROR",
|
|
1274
|
-
exitCode: ExitCode.CONFIGURATION_ERROR
|
|
1275
|
-
}
|
|
1276
|
-
);
|
|
1277
|
-
return;
|
|
1278
|
-
}
|
|
1279
|
-
if (!ctx.json) {
|
|
1280
|
-
console.log(`Creating account for ${options.email}...`);
|
|
1281
|
-
}
|
|
1282
|
-
const result2 = await createAccountWithPassword(options.email, options.password);
|
|
1283
|
-
if (result2.success && result2.user) {
|
|
1284
|
-
const storageDesc = result2.storageMethod === "keytar" ? "system keychain" : "encrypted config file";
|
|
1285
|
-
if (ctx.json) {
|
|
1286
|
-
outputSuccess(ctx, {
|
|
1287
|
-
status: "created",
|
|
1288
|
-
user: {
|
|
1289
|
-
id: result2.user.id,
|
|
1290
|
-
email: result2.user.email,
|
|
1291
|
-
name: result2.user.name
|
|
1292
|
-
},
|
|
1293
|
-
storageMethod: result2.storageMethod
|
|
1294
|
-
}, noop);
|
|
1295
|
-
} else {
|
|
1296
|
-
console.log("\nAccount created successfully!");
|
|
1297
|
-
console.log(` User: ${result2.user.email || result2.user.name || result2.user.id}`);
|
|
1298
|
-
if (result2.storageMethod) {
|
|
1299
|
-
console.log(` Credentials stored in: ${storageDesc}`);
|
|
1300
|
-
}
|
|
1301
|
-
console.log("\nNext steps:");
|
|
1302
|
-
console.log(" 1. Connect your calendar: indigo setup calendar");
|
|
1303
|
-
console.log(" 2. Configure API keys: indigo setup keys");
|
|
1304
|
-
console.log(" 3. View your signals: indigo signals list");
|
|
1305
|
-
}
|
|
1306
|
-
} else {
|
|
1307
|
-
outputError(ctx, result2.error || "Account creation failed", {
|
|
1308
|
-
code: "ACCOUNT_CREATION_FAILED",
|
|
1309
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1310
|
-
});
|
|
1311
|
-
}
|
|
1312
|
-
return;
|
|
1313
|
-
}
|
|
1314
|
-
const result = await createAccount();
|
|
1315
|
-
if (result.success && result.user) {
|
|
1316
|
-
const storageDesc = result.storageMethod === "keytar" ? "system keychain" : "encrypted config file";
|
|
1317
|
-
if (ctx.json) {
|
|
1318
|
-
outputSuccess(ctx, {
|
|
1319
|
-
status: "created",
|
|
1320
|
-
user: {
|
|
1321
|
-
id: result.user.id,
|
|
1322
|
-
email: result.user.email,
|
|
1323
|
-
name: result.user.name
|
|
1324
|
-
},
|
|
1325
|
-
storageMethod: result.storageMethod
|
|
1326
|
-
}, noop);
|
|
1327
|
-
} else {
|
|
1328
|
-
console.log("\nAccount created successfully!");
|
|
1329
|
-
console.log(` User: ${result.user.email || result.user.name || result.user.id}`);
|
|
1330
|
-
if (result.storageMethod) {
|
|
1331
|
-
console.log(` Credentials stored in: ${storageDesc}`);
|
|
1332
|
-
}
|
|
1333
|
-
console.log("\nNext steps:");
|
|
1334
|
-
console.log(" 1. Connect your calendar: indigo setup calendar");
|
|
1335
|
-
console.log(" 2. Configure API keys: indigo setup keys");
|
|
1336
|
-
console.log(" 3. View your signals: indigo signals list");
|
|
1337
|
-
}
|
|
1338
|
-
} else {
|
|
1339
|
-
outputError(ctx, result.error || "Account creation failed", {
|
|
1340
|
-
code: "ACCOUNT_CREATION_FAILED",
|
|
1341
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1342
|
-
});
|
|
1343
|
-
}
|
|
1344
|
-
} catch (error) {
|
|
1345
|
-
outputError(ctx, error instanceof Error ? error.message : "Unknown error", {
|
|
1346
|
-
code: "ACCOUNT_CREATION_FAILED",
|
|
1347
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
});
|
|
1351
|
-
account.command("info").description("Show current account information").option("--json", "Output as JSON for scripting").action(async (options) => {
|
|
1352
|
-
const ctx = createOutputContext(options);
|
|
1353
|
-
try {
|
|
1354
|
-
const state = await getAuthState();
|
|
1355
|
-
if (!state.isAuthenticated || !state.user) {
|
|
1356
|
-
if (ctx.json) {
|
|
1357
|
-
outputSuccess(ctx, {
|
|
1358
|
-
isAuthenticated: false,
|
|
1359
|
-
message: 'Not logged in. Run "indigo account create" to create an account or "indigo auth login" to log in.'
|
|
1360
|
-
}, noop);
|
|
1361
|
-
} else {
|
|
1362
|
-
console.log("Not logged in.");
|
|
1363
|
-
console.log("\nTo create a new account, run:");
|
|
1364
|
-
console.log(" indigo account create");
|
|
1365
|
-
console.log("\nTo log in to an existing account, run:");
|
|
1366
|
-
console.log(" indigo auth login");
|
|
1367
|
-
}
|
|
1368
|
-
return;
|
|
1369
|
-
}
|
|
1370
|
-
if (ctx.json) {
|
|
1371
|
-
outputSuccess(ctx, {
|
|
1372
|
-
isAuthenticated: true,
|
|
1373
|
-
user: {
|
|
1374
|
-
id: state.user.id,
|
|
1375
|
-
email: state.user.email,
|
|
1376
|
-
name: state.user.name,
|
|
1377
|
-
firstName: state.user.firstName,
|
|
1378
|
-
lastName: state.user.lastName,
|
|
1379
|
-
company: state.user.company
|
|
1380
|
-
},
|
|
1381
|
-
credentialSource: state.credentialSource
|
|
1382
|
-
}, noop);
|
|
1383
|
-
} else {
|
|
1384
|
-
console.log("\nAccount Information");
|
|
1385
|
-
console.log("\u2500".repeat(40));
|
|
1386
|
-
if (state.user.email) {
|
|
1387
|
-
console.log(`Email: ${state.user.email}`);
|
|
1388
|
-
}
|
|
1389
|
-
if (state.user.name) {
|
|
1390
|
-
console.log(`Name: ${state.user.name}`);
|
|
1391
|
-
}
|
|
1392
|
-
if (state.user.id) {
|
|
1393
|
-
console.log(`User ID: ${state.user.id}`);
|
|
1394
|
-
}
|
|
1395
|
-
if (state.user.company?.name) {
|
|
1396
|
-
console.log(`Company: ${state.user.company.name}`);
|
|
1397
|
-
}
|
|
1398
|
-
const sourceDesc = state.credentialSource === "electron" ? "Indigo desktop app" : "CLI";
|
|
1399
|
-
console.log(`Source: ${sourceDesc}`);
|
|
1400
|
-
console.log("");
|
|
1401
|
-
}
|
|
1402
|
-
} catch (error) {
|
|
1403
|
-
outputError(ctx, error instanceof Error ? error.message : "Unknown error", {
|
|
1404
|
-
code: "ACCOUNT_INFO_ERROR",
|
|
1405
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
});
|
|
1409
|
-
return account;
|
|
1410
|
-
}
|
|
1411
|
-
|
|
1412
|
-
// apps/cli/src/commands/signals/index.ts
|
|
1413
|
-
var import_commander3 = require("commander");
|
|
1414
|
-
|
|
1415
|
-
// apps/cli/src/services/signals.ts
|
|
1416
|
-
async function fetchSignals(options = {}) {
|
|
1417
|
-
const token = await getToken();
|
|
1418
|
-
if (!token) {
|
|
1419
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1420
|
-
}
|
|
1421
|
-
try {
|
|
1422
|
-
const params = new URLSearchParams();
|
|
1423
|
-
if (options.type)
|
|
1424
|
-
params.set("type", options.type);
|
|
1425
|
-
if (options.startDate)
|
|
1426
|
-
params.set("startDate", options.startDate);
|
|
1427
|
-
if (options.endDate)
|
|
1428
|
-
params.set("endDate", options.endDate);
|
|
1429
|
-
if (options.limit)
|
|
1430
|
-
params.set("limit", String(options.limit));
|
|
1431
|
-
const queryString = params.toString();
|
|
1432
|
-
const url = `${apiUrl}/signals${queryString ? `?${queryString}` : ""}`;
|
|
1433
|
-
const response = await fetch(url, {
|
|
1434
|
-
method: "GET",
|
|
1435
|
-
headers: {
|
|
1436
|
-
"Content-Type": "application/json",
|
|
1437
|
-
Authorization: `Bearer ${token}`
|
|
1438
|
-
}
|
|
1439
|
-
});
|
|
1440
|
-
if (!response.ok) {
|
|
1441
|
-
if (response.status === 401) {
|
|
1442
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1443
|
-
}
|
|
1444
|
-
if (response.status === 403) {
|
|
1445
|
-
return { success: false, error: "Access denied. You may not have permission to view signals." };
|
|
1446
|
-
}
|
|
1447
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1448
|
-
}
|
|
1449
|
-
let signals = await response.json();
|
|
1450
|
-
if (options.limit && options.limit > 0) {
|
|
1451
|
-
signals = signals.slice(0, options.limit);
|
|
1452
|
-
}
|
|
1453
|
-
return { success: true, signals };
|
|
1454
|
-
} catch (error) {
|
|
1455
|
-
if (error instanceof Error) {
|
|
1456
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1457
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
1458
|
-
}
|
|
1459
|
-
return { success: false, error: error.message };
|
|
1460
|
-
}
|
|
1461
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1462
|
-
}
|
|
1463
|
-
}
|
|
1464
|
-
async function searchSignals(options) {
|
|
1465
|
-
const token = await getToken();
|
|
1466
|
-
if (!token) {
|
|
1467
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1468
|
-
}
|
|
1469
|
-
try {
|
|
1470
|
-
const params = new URLSearchParams();
|
|
1471
|
-
params.set("q", options.query);
|
|
1472
|
-
if (options.type)
|
|
1473
|
-
params.set("type", options.type);
|
|
1474
|
-
if (options.limit)
|
|
1475
|
-
params.set("limit", String(options.limit));
|
|
1476
|
-
const url = `${apiUrl}/signals/search?${params.toString()}`;
|
|
1477
|
-
const response = await fetch(url, {
|
|
1478
|
-
method: "GET",
|
|
1479
|
-
headers: {
|
|
1480
|
-
"Content-Type": "application/json",
|
|
1481
|
-
Authorization: `Bearer ${token}`
|
|
1482
|
-
}
|
|
1483
|
-
});
|
|
1484
|
-
if (!response.ok) {
|
|
1485
|
-
if (response.status === 401) {
|
|
1486
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1487
|
-
}
|
|
1488
|
-
if (response.status === 403) {
|
|
1489
|
-
return { success: false, error: "Access denied. You may not have permission to search signals." };
|
|
1490
|
-
}
|
|
1491
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1492
|
-
}
|
|
1493
|
-
const signals = await response.json();
|
|
1494
|
-
return { success: true, signals };
|
|
1495
|
-
} catch (error) {
|
|
1496
|
-
if (error instanceof Error) {
|
|
1497
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1498
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
1499
|
-
}
|
|
1500
|
-
return { success: false, error: error.message };
|
|
1501
|
-
}
|
|
1502
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
async function fetchSignalById(id) {
|
|
1506
|
-
const token = await getToken();
|
|
1507
|
-
if (!token) {
|
|
1508
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1509
|
-
}
|
|
1510
|
-
try {
|
|
1511
|
-
const response = await fetch(`${apiUrl}/signals/${id}`, {
|
|
1512
|
-
method: "GET",
|
|
1513
|
-
headers: {
|
|
1514
|
-
"Content-Type": "application/json",
|
|
1515
|
-
Authorization: `Bearer ${token}`
|
|
1516
|
-
}
|
|
1517
|
-
});
|
|
1518
|
-
if (!response.ok) {
|
|
1519
|
-
if (response.status === 401) {
|
|
1520
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1521
|
-
}
|
|
1522
|
-
if (response.status === 404) {
|
|
1523
|
-
return { success: false, error: `Signal with ID "${id}" not found.` };
|
|
1524
|
-
}
|
|
1525
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1526
|
-
}
|
|
1527
|
-
const signal = await response.json();
|
|
1528
|
-
return { success: true, signal };
|
|
1529
|
-
} catch (error) {
|
|
1530
|
-
if (error instanceof Error) {
|
|
1531
|
-
return { success: false, error: error.message };
|
|
1532
|
-
}
|
|
1533
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1534
|
-
}
|
|
1535
|
-
}
|
|
1536
|
-
function getSignalTitle(signal) {
|
|
1537
|
-
const data = signal.data;
|
|
1538
|
-
if (typeof data.decision === "string")
|
|
1539
|
-
return data.decision;
|
|
1540
|
-
if (typeof data.action_item === "string")
|
|
1541
|
-
return data.action_item;
|
|
1542
|
-
if (typeof data.accomplishment === "string")
|
|
1543
|
-
return data.accomplishment;
|
|
1544
|
-
if (typeof data.key_fact === "string")
|
|
1545
|
-
return data.key_fact;
|
|
1546
|
-
if (typeof data.title === "string")
|
|
1547
|
-
return data.title;
|
|
1548
|
-
if (typeof data.summary === "string")
|
|
1549
|
-
return data.summary;
|
|
1550
|
-
if (typeof data.description === "string")
|
|
1551
|
-
return data.description;
|
|
1552
|
-
for (const value of Object.values(data)) {
|
|
1553
|
-
if (typeof value === "string" && value.length > 0) {
|
|
1554
|
-
return value;
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
return `[${signal.insightType}]`;
|
|
1558
|
-
}
|
|
1559
|
-
function getSignalSource(signal) {
|
|
1560
|
-
const meta = signal.sourceMetadata;
|
|
1561
|
-
const source = signal.source;
|
|
1562
|
-
if (meta?.sourceChannel) {
|
|
1563
|
-
return meta.sourceChannel.charAt(0).toUpperCase() + meta.sourceChannel.slice(1);
|
|
1564
|
-
}
|
|
1565
|
-
if (source?.type) {
|
|
1566
|
-
return source.type.charAt(0).toUpperCase() + source.type.slice(1);
|
|
1567
|
-
}
|
|
1568
|
-
return "Unknown";
|
|
1569
|
-
}
|
|
1570
|
-
function formatDate(dateString) {
|
|
1571
|
-
try {
|
|
1572
|
-
const date = new Date(dateString);
|
|
1573
|
-
return date.toLocaleDateString("en-US", {
|
|
1574
|
-
year: "numeric",
|
|
1575
|
-
month: "short",
|
|
1576
|
-
day: "numeric"
|
|
1577
|
-
});
|
|
1578
|
-
} catch {
|
|
1579
|
-
return dateString;
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
|
|
1583
|
-
// apps/cli/src/commands/signals/index.ts
|
|
1584
|
-
var DEFAULT_LIMIT = 20;
|
|
1585
|
-
var DEFAULT_TERMINAL_WIDTH = 80;
|
|
1586
|
-
var VALID_SIGNAL_TYPES = ["decision", "action", "accomplishment", "key_fact"];
|
|
1587
|
-
function truncate(str, maxLength) {
|
|
1588
|
-
if (str.length <= maxLength)
|
|
1589
|
-
return str;
|
|
1590
|
-
return str.slice(0, maxLength - 3) + "...";
|
|
1591
|
-
}
|
|
1592
|
-
function getTerminalWidth() {
|
|
1593
|
-
return process.stdout.columns || DEFAULT_TERMINAL_WIDTH;
|
|
1594
|
-
}
|
|
1595
|
-
function wordWrap(text, maxWidth) {
|
|
1596
|
-
if (!text)
|
|
1597
|
-
return "";
|
|
1598
|
-
const lines = [];
|
|
1599
|
-
const paragraphs = text.split(/\n/);
|
|
1600
|
-
for (const paragraph of paragraphs) {
|
|
1601
|
-
if (paragraph.trim() === "") {
|
|
1602
|
-
lines.push("");
|
|
1603
|
-
continue;
|
|
1604
|
-
}
|
|
1605
|
-
const words = paragraph.split(/\s+/);
|
|
1606
|
-
let currentLine = "";
|
|
1607
|
-
for (const word of words) {
|
|
1608
|
-
if (currentLine.length === 0) {
|
|
1609
|
-
currentLine = word;
|
|
1610
|
-
} else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
1611
|
-
currentLine += " " + word;
|
|
1612
|
-
} else {
|
|
1613
|
-
lines.push(currentLine);
|
|
1614
|
-
currentLine = word;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
if (currentLine.length > 0) {
|
|
1618
|
-
lines.push(currentLine);
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
return lines.join("\n");
|
|
1622
|
-
}
|
|
1623
|
-
function formatSignalData(data, indent = "") {
|
|
1624
|
-
const lines = [];
|
|
1625
|
-
for (const [key, value] of Object.entries(data)) {
|
|
1626
|
-
const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1627
|
-
if (typeof value === "string") {
|
|
1628
|
-
lines.push(`${indent}${label}: ${value}`);
|
|
1629
|
-
} else if (typeof value === "number" || typeof value === "boolean") {
|
|
1630
|
-
lines.push(`${indent}${label}: ${value}`);
|
|
1631
|
-
} else if (Array.isArray(value)) {
|
|
1632
|
-
if (value.length > 0) {
|
|
1633
|
-
lines.push(`${indent}${label}:`);
|
|
1634
|
-
for (const item of value) {
|
|
1635
|
-
if (typeof item === "string") {
|
|
1636
|
-
lines.push(`${indent} - ${item}`);
|
|
1637
|
-
} else if (typeof item === "object" && item !== null) {
|
|
1638
|
-
lines.push(`${indent} - ${JSON.stringify(item)}`);
|
|
1639
|
-
}
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
} else if (typeof value === "object" && value !== null) {
|
|
1643
|
-
lines.push(`${indent}${label}: ${JSON.stringify(value)}`);
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
return lines.join("\n");
|
|
1647
|
-
}
|
|
1648
|
-
function printSignalDetail(signal) {
|
|
1649
|
-
const width = getTerminalWidth();
|
|
1650
|
-
const separator = "\u2500".repeat(Math.min(width, 80));
|
|
1651
|
-
console.log("");
|
|
1652
|
-
console.log(separator);
|
|
1653
|
-
console.log(`SIGNAL: ${signal._id}`);
|
|
1654
|
-
console.log(separator);
|
|
1655
|
-
console.log("");
|
|
1656
|
-
console.log(`Type: ${signal.insightType}`);
|
|
1657
|
-
console.log(`Title: ${getSignalTitle(signal)}`);
|
|
1658
|
-
console.log(`Source: ${getSignalSource(signal)}`);
|
|
1659
|
-
console.log(`Created: ${formatDate(signal.createdAt)}`);
|
|
1660
|
-
if (signal.updatedAt !== signal.createdAt) {
|
|
1661
|
-
console.log(`Updated: ${formatDate(signal.updatedAt)}`);
|
|
1662
|
-
}
|
|
1663
|
-
if (signal.people && signal.people.length > 0) {
|
|
1664
|
-
console.log(`People: ${signal.people.map((p) => p.name).join(", ")}`);
|
|
1665
|
-
}
|
|
1666
|
-
if (signal.teams && signal.teams.length > 0) {
|
|
1667
|
-
console.log(`Teams: ${signal.teams.map((t) => t.name).join(", ")}`);
|
|
1668
|
-
}
|
|
1669
|
-
if (signal.project) {
|
|
1670
|
-
console.log(`Project: ${signal.project.name}`);
|
|
1671
|
-
}
|
|
1672
|
-
if (signal.initiative) {
|
|
1673
|
-
console.log(`Initiative: ${signal.initiative.name}`);
|
|
1674
|
-
}
|
|
1675
|
-
if (signal.task) {
|
|
1676
|
-
console.log(`Task: ${signal.task.name}`);
|
|
1677
|
-
}
|
|
1678
|
-
if (signal.sourceMetadata) {
|
|
1679
|
-
const meta = signal.sourceMetadata;
|
|
1680
|
-
if (meta.sourceUrl) {
|
|
1681
|
-
console.log(`URL: ${meta.sourceUrl}`);
|
|
1682
|
-
}
|
|
1683
|
-
if (meta.participants && meta.participants.length > 0) {
|
|
1684
|
-
console.log(`Participants: ${meta.participants.join(", ")}`);
|
|
1685
|
-
}
|
|
1686
|
-
}
|
|
1687
|
-
console.log("");
|
|
1688
|
-
console.log("CONTENT");
|
|
1689
|
-
console.log(separator);
|
|
1690
|
-
const contentWidth = Math.min(width - 2, 78);
|
|
1691
|
-
const formattedData = formatSignalData(signal.data);
|
|
1692
|
-
console.log(wordWrap(formattedData, contentWidth));
|
|
1693
|
-
if (signal.citations && signal.citations.length > 0) {
|
|
1694
|
-
console.log("");
|
|
1695
|
-
console.log("CITATIONS");
|
|
1696
|
-
console.log(separator);
|
|
1697
|
-
for (let i = 0; i < signal.citations.length; i++) {
|
|
1698
|
-
const citation = signal.citations[i];
|
|
1699
|
-
console.log(`[${i + 1}] "${wordWrap(citation.quote, contentWidth - 4)}"`);
|
|
1700
|
-
if (citation.speaker) {
|
|
1701
|
-
console.log(` \u2014 ${citation.speaker}`);
|
|
1702
|
-
}
|
|
1703
|
-
if (citation.surroundingContext) {
|
|
1704
|
-
console.log(` Context: ${truncate(citation.surroundingContext, contentWidth - 13)}`);
|
|
1705
|
-
}
|
|
1706
|
-
if (i < signal.citations.length - 1) {
|
|
1707
|
-
console.log("");
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
console.log("");
|
|
1712
|
-
}
|
|
1713
|
-
function printSignalsTable(signals) {
|
|
1714
|
-
if (signals.length === 0) {
|
|
1715
|
-
console.log("\nNo signals found.");
|
|
1716
|
-
console.log("Signals are generated from your meetings, emails, and chats.");
|
|
1717
|
-
return;
|
|
1718
|
-
}
|
|
1719
|
-
const typeWidth = 14;
|
|
1720
|
-
const dateWidth = 12;
|
|
1721
|
-
const sourceWidth = 10;
|
|
1722
|
-
const titleWidth = 50;
|
|
1723
|
-
console.log("");
|
|
1724
|
-
console.log(
|
|
1725
|
-
"TYPE".padEnd(typeWidth) + "DATE".padEnd(dateWidth) + "SOURCE".padEnd(sourceWidth) + "TITLE"
|
|
1726
|
-
);
|
|
1727
|
-
console.log("\u2500".repeat(typeWidth + dateWidth + sourceWidth + titleWidth));
|
|
1728
|
-
for (const signal of signals) {
|
|
1729
|
-
const type = truncate(signal.insightType, typeWidth - 2).padEnd(typeWidth);
|
|
1730
|
-
const date = formatDate(signal.createdAt).padEnd(dateWidth);
|
|
1731
|
-
const source = truncate(getSignalSource(signal), sourceWidth - 2).padEnd(sourceWidth);
|
|
1732
|
-
const title = truncate(getSignalTitle(signal), titleWidth);
|
|
1733
|
-
console.log(`${type}${date}${source}${title}`);
|
|
1734
|
-
}
|
|
1735
|
-
console.log("");
|
|
1736
|
-
console.log(`Showing ${signals.length} signal${signals.length !== 1 ? "s" : ""}`);
|
|
1737
|
-
}
|
|
1738
|
-
function createSignalsCommand() {
|
|
1739
|
-
const signals = new import_commander3.Command("signals").description("Signal management commands");
|
|
1740
|
-
signals.command("list").description("List your signals").option("--json", "Output as JSON for scripting").option("-n, --limit <number>", `Number of results to show (default: ${DEFAULT_LIMIT})`, String(DEFAULT_LIMIT)).option("-t, --type <type>", "Filter by signal type (e.g., decision, action, accomplishment)").action(async (options) => {
|
|
1741
|
-
const ctx = createOutputContext(options);
|
|
1742
|
-
const parsedLimit = parseInt(options.limit, 10);
|
|
1743
|
-
if (isNaN(parsedLimit) || parsedLimit <= 0) {
|
|
1744
|
-
outputValidationError(
|
|
1745
|
-
ctx,
|
|
1746
|
-
"Invalid value for --limit",
|
|
1747
|
-
"Provide a positive integer (e.g., --limit 10)"
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
|
-
const limit = parsedLimit;
|
|
1751
|
-
if (options.type && !VALID_SIGNAL_TYPES.includes(options.type)) {
|
|
1752
|
-
outputValidationError(
|
|
1753
|
-
ctx,
|
|
1754
|
-
`Invalid signal type "${options.type}"`,
|
|
1755
|
-
`Valid types are: ${VALID_SIGNAL_TYPES.join(", ")}`
|
|
1756
|
-
);
|
|
1757
|
-
}
|
|
1758
|
-
const result = await fetchSignals({
|
|
1759
|
-
type: options.type,
|
|
1760
|
-
limit
|
|
1761
|
-
});
|
|
1762
|
-
if (!result.success) {
|
|
1763
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
1764
|
-
outputAuthRequired(ctx);
|
|
1765
|
-
}
|
|
1766
|
-
outputError(ctx, result.error || "Failed to fetch signals", {
|
|
1767
|
-
code: "FETCH_ERROR",
|
|
1768
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1769
|
-
});
|
|
1770
|
-
}
|
|
1771
|
-
const signals2 = result.signals || [];
|
|
1772
|
-
outputData(ctx, signals2, printSignalsTable);
|
|
1773
|
-
});
|
|
1774
|
-
signals.command("search <query>").description("Search your signals").option("--json", "Output as JSON for scripting").option("-n, --limit <number>", `Number of results to show (default: ${DEFAULT_LIMIT})`, String(DEFAULT_LIMIT)).option("-t, --type <type>", "Filter by signal type (e.g., decision, action, accomplishment)").action(async (query, options) => {
|
|
1775
|
-
const ctx = createOutputContext(options);
|
|
1776
|
-
if (!query || query.trim() === "") {
|
|
1777
|
-
outputValidationError(
|
|
1778
|
-
ctx,
|
|
1779
|
-
"Search query cannot be empty",
|
|
1780
|
-
'Provide a search term (e.g., indigo signals search "budget")'
|
|
1781
|
-
);
|
|
1782
|
-
}
|
|
1783
|
-
const parsedLimit = parseInt(options.limit, 10);
|
|
1784
|
-
if (isNaN(parsedLimit) || parsedLimit <= 0) {
|
|
1785
|
-
outputValidationError(
|
|
1786
|
-
ctx,
|
|
1787
|
-
"Invalid value for --limit",
|
|
1788
|
-
"Provide a positive integer (e.g., --limit 10)"
|
|
1789
|
-
);
|
|
1790
|
-
}
|
|
1791
|
-
const limit = parsedLimit;
|
|
1792
|
-
if (options.type && !VALID_SIGNAL_TYPES.includes(options.type)) {
|
|
1793
|
-
outputValidationError(
|
|
1794
|
-
ctx,
|
|
1795
|
-
`Invalid signal type "${options.type}"`,
|
|
1796
|
-
`Valid types are: ${VALID_SIGNAL_TYPES.join(", ")}`
|
|
1797
|
-
);
|
|
1798
|
-
}
|
|
1799
|
-
const result = await searchSignals({
|
|
1800
|
-
query,
|
|
1801
|
-
type: options.type,
|
|
1802
|
-
limit
|
|
1803
|
-
});
|
|
1804
|
-
if (!result.success) {
|
|
1805
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
1806
|
-
outputAuthRequired(ctx);
|
|
1807
|
-
}
|
|
1808
|
-
outputError(ctx, result.error || "Failed to search signals", {
|
|
1809
|
-
code: "SEARCH_ERROR",
|
|
1810
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
1813
|
-
const signals2 = result.signals || [];
|
|
1814
|
-
outputData(ctx, signals2, (data) => {
|
|
1815
|
-
if (data.length === 0) {
|
|
1816
|
-
console.log(`
|
|
1817
|
-
No signals found matching "${query}".`);
|
|
1818
|
-
console.log("Try a different search term or check your signals with: indigo signals list");
|
|
1819
|
-
} else {
|
|
1820
|
-
console.log(`
|
|
1821
|
-
Search results for "${query}":`);
|
|
1822
|
-
printSignalsTable(data);
|
|
1823
|
-
}
|
|
1824
|
-
});
|
|
1825
|
-
});
|
|
1826
|
-
signals.command("view <id>").description("View a specific signal's full details").option("--json", "Output as JSON for scripting").action(async (id, options) => {
|
|
1827
|
-
const ctx = createOutputContext(options);
|
|
1828
|
-
if (!id || id.trim() === "") {
|
|
1829
|
-
outputValidationError(
|
|
1830
|
-
ctx,
|
|
1831
|
-
"Signal ID cannot be empty",
|
|
1832
|
-
"Provide a valid signal ID (e.g., indigo signals view 507f1f77bcf86cd799439011)"
|
|
1833
|
-
);
|
|
1834
|
-
}
|
|
1835
|
-
const objectIdRegex = /^[a-fA-F0-9]{24}$/;
|
|
1836
|
-
if (!objectIdRegex.test(id)) {
|
|
1837
|
-
outputValidationError(
|
|
1838
|
-
ctx,
|
|
1839
|
-
`Invalid signal ID format "${id}"`,
|
|
1840
|
-
"Signal ID must be a 24-character hexadecimal string"
|
|
1841
|
-
);
|
|
1842
|
-
}
|
|
1843
|
-
const result = await fetchSignalById(id);
|
|
1844
|
-
if (!result.success) {
|
|
1845
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
1846
|
-
outputAuthRequired(ctx);
|
|
1847
|
-
}
|
|
1848
|
-
if (result.error?.includes("not found") || result.error?.includes("404")) {
|
|
1849
|
-
outputNotFound(ctx, "Signal", id);
|
|
1850
|
-
}
|
|
1851
|
-
outputError(ctx, result.error || "Failed to fetch signal", {
|
|
1852
|
-
code: "FETCH_ERROR",
|
|
1853
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
const signal = result.signal;
|
|
1857
|
-
if (!signal) {
|
|
1858
|
-
outputNotFound(ctx, "Signal", id);
|
|
1859
|
-
}
|
|
1860
|
-
outputData(ctx, signal, printSignalDetail);
|
|
1861
|
-
});
|
|
1862
|
-
return signals;
|
|
1863
|
-
}
|
|
1864
|
-
|
|
1865
|
-
// apps/cli/src/commands/chat/index.ts
|
|
1866
|
-
var import_commander4 = require("commander");
|
|
1867
|
-
var readline = __toESM(require("readline"));
|
|
1868
|
-
|
|
1869
|
-
// apps/cli/src/services/chat.ts
|
|
1870
|
-
async function createChat(input) {
|
|
1871
|
-
const token = await getToken();
|
|
1872
|
-
if (!token) {
|
|
1873
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1874
|
-
}
|
|
1875
|
-
try {
|
|
1876
|
-
const response = await fetch(`${apiUrl}/chat`, {
|
|
1877
|
-
method: "POST",
|
|
1878
|
-
headers: {
|
|
1879
|
-
"Content-Type": "application/json",
|
|
1880
|
-
Authorization: `Bearer ${token}`
|
|
1881
|
-
},
|
|
1882
|
-
body: JSON.stringify(input)
|
|
1883
|
-
});
|
|
1884
|
-
if (!response.ok) {
|
|
1885
|
-
if (response.status === 401) {
|
|
1886
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1887
|
-
}
|
|
1888
|
-
if (response.status === 403) {
|
|
1889
|
-
return { success: false, error: "Access denied." };
|
|
1890
|
-
}
|
|
1891
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1892
|
-
}
|
|
1893
|
-
const chat = await response.json();
|
|
1894
|
-
return { success: true, chat };
|
|
1895
|
-
} catch (error) {
|
|
1896
|
-
if (error instanceof Error) {
|
|
1897
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1898
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
1899
|
-
}
|
|
1900
|
-
return { success: false, error: error.message };
|
|
1901
|
-
}
|
|
1902
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
async function getChatById(chatId) {
|
|
1906
|
-
const token = await getToken();
|
|
1907
|
-
if (!token) {
|
|
1908
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1909
|
-
}
|
|
1910
|
-
try {
|
|
1911
|
-
const response = await fetch(`${apiUrl}/chat/${chatId}`, {
|
|
1912
|
-
method: "GET",
|
|
1913
|
-
headers: {
|
|
1914
|
-
"Content-Type": "application/json",
|
|
1915
|
-
Authorization: `Bearer ${token}`
|
|
1916
|
-
}
|
|
1917
|
-
});
|
|
1918
|
-
if (!response.ok) {
|
|
1919
|
-
if (response.status === 401) {
|
|
1920
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1921
|
-
}
|
|
1922
|
-
if (response.status === 404) {
|
|
1923
|
-
return { success: false, error: `Chat session "${chatId}" not found.` };
|
|
1924
|
-
}
|
|
1925
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1926
|
-
}
|
|
1927
|
-
const chat = await response.json();
|
|
1928
|
-
return { success: true, chat };
|
|
1929
|
-
} catch (error) {
|
|
1930
|
-
if (error instanceof Error) {
|
|
1931
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1932
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
1933
|
-
}
|
|
1934
|
-
return { success: false, error: error.message };
|
|
1935
|
-
}
|
|
1936
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
async function listChats(options = {}) {
|
|
1940
|
-
const token = await getToken();
|
|
1941
|
-
if (!token) {
|
|
1942
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1943
|
-
}
|
|
1944
|
-
try {
|
|
1945
|
-
const params = new URLSearchParams();
|
|
1946
|
-
if (options.page)
|
|
1947
|
-
params.set("page", String(options.page));
|
|
1948
|
-
if (options.limit)
|
|
1949
|
-
params.set("limit", String(options.limit));
|
|
1950
|
-
if (options.search)
|
|
1951
|
-
params.set("search", options.search);
|
|
1952
|
-
const queryString = params.toString();
|
|
1953
|
-
const url = `${apiUrl}/chat/all${queryString ? `?${queryString}` : ""}`;
|
|
1954
|
-
const response = await fetch(url, {
|
|
1955
|
-
method: "GET",
|
|
1956
|
-
headers: {
|
|
1957
|
-
"Content-Type": "application/json",
|
|
1958
|
-
Authorization: `Bearer ${token}`
|
|
1959
|
-
}
|
|
1960
|
-
});
|
|
1961
|
-
if (!response.ok) {
|
|
1962
|
-
if (response.status === 401) {
|
|
1963
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
1964
|
-
}
|
|
1965
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
1966
|
-
}
|
|
1967
|
-
const result = await response.json();
|
|
1968
|
-
return {
|
|
1969
|
-
success: true,
|
|
1970
|
-
chats: result.chats || result.data || result,
|
|
1971
|
-
pagination: result.pagination
|
|
1972
|
-
};
|
|
1973
|
-
} catch (error) {
|
|
1974
|
-
if (error instanceof Error) {
|
|
1975
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
1976
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
1977
|
-
}
|
|
1978
|
-
return { success: false, error: error.message };
|
|
1979
|
-
}
|
|
1980
|
-
return { success: false, error: "Unknown error occurred" };
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
async function getChatMessages(chatId, options = {}) {
|
|
1984
|
-
const token = await getToken();
|
|
1985
|
-
if (!token) {
|
|
1986
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
1987
|
-
}
|
|
1988
|
-
try {
|
|
1989
|
-
const params = new URLSearchParams();
|
|
1990
|
-
if (options.page)
|
|
1991
|
-
params.set("page", String(options.page));
|
|
1992
|
-
if (options.limit)
|
|
1993
|
-
params.set("limit", String(options.limit));
|
|
1994
|
-
if (options.sortOrder)
|
|
1995
|
-
params.set("sortOrder", options.sortOrder);
|
|
1996
|
-
const queryString = params.toString();
|
|
1997
|
-
const url = `${apiUrl}/chat/${chatId}/messages${queryString ? `?${queryString}` : ""}`;
|
|
1998
|
-
const response = await fetch(url, {
|
|
1999
|
-
method: "GET",
|
|
2000
|
-
headers: {
|
|
2001
|
-
"Content-Type": "application/json",
|
|
2002
|
-
Authorization: `Bearer ${token}`
|
|
2003
|
-
}
|
|
2004
|
-
});
|
|
2005
|
-
if (!response.ok) {
|
|
2006
|
-
if (response.status === 401) {
|
|
2007
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2008
|
-
}
|
|
2009
|
-
if (response.status === 404) {
|
|
2010
|
-
return { success: false, error: `Chat session "${chatId}" not found.` };
|
|
2011
|
-
}
|
|
2012
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2013
|
-
}
|
|
2014
|
-
const result = await response.json();
|
|
2015
|
-
return {
|
|
2016
|
-
success: true,
|
|
2017
|
-
messages: result.messages || result,
|
|
2018
|
-
pagination: result.pagination
|
|
2019
|
-
};
|
|
2020
|
-
} catch (error) {
|
|
2021
|
-
if (error instanceof Error) {
|
|
2022
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2023
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2024
|
-
}
|
|
2025
|
-
return { success: false, error: error.message };
|
|
2026
|
-
}
|
|
2027
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
async function createMessage(input) {
|
|
2031
|
-
const token = await getToken();
|
|
2032
|
-
if (!token) {
|
|
2033
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2034
|
-
}
|
|
2035
|
-
try {
|
|
2036
|
-
const response = await fetch(`${apiUrl}/chat/messages`, {
|
|
2037
|
-
method: "POST",
|
|
2038
|
-
headers: {
|
|
2039
|
-
"Content-Type": "application/json",
|
|
2040
|
-
Authorization: `Bearer ${token}`
|
|
2041
|
-
},
|
|
2042
|
-
body: JSON.stringify(input)
|
|
2043
|
-
});
|
|
2044
|
-
if (!response.ok) {
|
|
2045
|
-
if (response.status === 401) {
|
|
2046
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2047
|
-
}
|
|
2048
|
-
if (response.status === 404) {
|
|
2049
|
-
return { success: false, error: `Chat session not found.` };
|
|
2050
|
-
}
|
|
2051
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2052
|
-
}
|
|
2053
|
-
const message = await response.json();
|
|
2054
|
-
return { success: true, message };
|
|
2055
|
-
} catch (error) {
|
|
2056
|
-
if (error instanceof Error) {
|
|
2057
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2058
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2059
|
-
}
|
|
2060
|
-
return { success: false, error: error.message };
|
|
2061
|
-
}
|
|
2062
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
async function updateChat(chatId, input) {
|
|
2066
|
-
const token = await getToken();
|
|
2067
|
-
if (!token) {
|
|
2068
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2069
|
-
}
|
|
2070
|
-
try {
|
|
2071
|
-
const response = await fetch(`${apiUrl}/chat/${chatId}`, {
|
|
2072
|
-
method: "PATCH",
|
|
2073
|
-
headers: {
|
|
2074
|
-
"Content-Type": "application/json",
|
|
2075
|
-
Authorization: `Bearer ${token}`
|
|
2076
|
-
},
|
|
2077
|
-
body: JSON.stringify(input)
|
|
2078
|
-
});
|
|
2079
|
-
if (!response.ok) {
|
|
2080
|
-
if (response.status === 401) {
|
|
2081
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2082
|
-
}
|
|
2083
|
-
if (response.status === 404) {
|
|
2084
|
-
return { success: false, error: `Chat session "${chatId}" not found.` };
|
|
2085
|
-
}
|
|
2086
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2087
|
-
}
|
|
2088
|
-
const chat = await response.json();
|
|
2089
|
-
return { success: true, chat };
|
|
2090
|
-
} catch (error) {
|
|
2091
|
-
if (error instanceof Error) {
|
|
2092
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2093
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2094
|
-
}
|
|
2095
|
-
return { success: false, error: error.message };
|
|
2096
|
-
}
|
|
2097
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
function formatChatDate(dateString) {
|
|
2101
|
-
try {
|
|
2102
|
-
const date = new Date(dateString);
|
|
2103
|
-
return date.toLocaleDateString("en-US", {
|
|
2104
|
-
year: "numeric",
|
|
2105
|
-
month: "short",
|
|
2106
|
-
day: "numeric",
|
|
2107
|
-
hour: "2-digit",
|
|
2108
|
-
minute: "2-digit"
|
|
2109
|
-
});
|
|
2110
|
-
} catch {
|
|
2111
|
-
return dateString;
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
function truncateText(text, maxLength) {
|
|
2115
|
-
if (text.length <= maxLength)
|
|
2116
|
-
return text;
|
|
2117
|
-
return text.slice(0, maxLength - 3) + "...";
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
// apps/cli/src/services/langgraph.ts
|
|
2121
|
-
var LANGGRAPH_API_URL = process.env.LANGGRAPH_API_URL;
|
|
2122
|
-
var LANGSMITH_API_KEY = process.env.LANGSMITH_API_KEY;
|
|
2123
|
-
var LANGGRAPH_ASSISTANT_ID = process.env.LANGGRAPH_ASSISTANT_ID;
|
|
2124
|
-
function getLangGraphConfig() {
|
|
2125
|
-
console.log(LANGGRAPH_API_URL, LANGSMITH_API_KEY, LANGGRAPH_ASSISTANT_ID);
|
|
2126
|
-
if (!LANGGRAPH_API_URL || !LANGSMITH_API_KEY || !LANGGRAPH_ASSISTANT_ID) {
|
|
2127
|
-
return null;
|
|
2128
|
-
}
|
|
2129
|
-
return {
|
|
2130
|
-
apiUrl: LANGGRAPH_API_URL,
|
|
2131
|
-
apiKey: LANGSMITH_API_KEY,
|
|
2132
|
-
assistantId: LANGGRAPH_ASSISTANT_ID
|
|
2133
|
-
};
|
|
2134
|
-
}
|
|
2135
|
-
async function createThread(config, metadata) {
|
|
2136
|
-
try {
|
|
2137
|
-
const response = await fetch(`${config.apiUrl}/threads`, {
|
|
2138
|
-
method: "POST",
|
|
2139
|
-
headers: {
|
|
2140
|
-
"Content-Type": "application/json",
|
|
2141
|
-
"x-api-key": config.apiKey
|
|
2142
|
-
},
|
|
2143
|
-
body: JSON.stringify({ metadata: metadata || {} })
|
|
2144
|
-
});
|
|
2145
|
-
if (!response.ok) {
|
|
2146
|
-
const text = await response.text();
|
|
2147
|
-
return { success: false, error: `Failed to create thread: ${response.status} ${text}` };
|
|
2148
|
-
}
|
|
2149
|
-
const thread = await response.json();
|
|
2150
|
-
return { success: true, threadId: thread.thread_id };
|
|
2151
|
-
} catch (error) {
|
|
2152
|
-
if (error instanceof Error) {
|
|
2153
|
-
return { success: false, error: `Failed to create thread: ${error.message}` };
|
|
2154
|
-
}
|
|
2155
|
-
return { success: false, error: "Failed to create thread" };
|
|
2156
|
-
}
|
|
2157
|
-
}
|
|
2158
|
-
function toLangGraphMessages(messages) {
|
|
2159
|
-
return messages.map((msg) => ({
|
|
2160
|
-
role: msg.role,
|
|
2161
|
-
content: [{ type: "text", text: msg.content }]
|
|
2162
|
-
}));
|
|
2163
|
-
}
|
|
2164
|
-
async function* streamChat(config, threadId, messages, options) {
|
|
2165
|
-
const credentials = await loadCredentials();
|
|
2166
|
-
const token = await getToken();
|
|
2167
|
-
if (!token) {
|
|
2168
|
-
yield { type: "error", error: "Not authenticated" };
|
|
2169
|
-
return;
|
|
2170
|
-
}
|
|
2171
|
-
const lgMessages = toLangGraphMessages(messages);
|
|
2172
|
-
try {
|
|
2173
|
-
const response = await fetch(`${config.apiUrl}/threads/${threadId}/runs/stream`, {
|
|
2174
|
-
method: "POST",
|
|
2175
|
-
headers: {
|
|
2176
|
-
"Content-Type": "application/json",
|
|
2177
|
-
"x-api-key": config.apiKey
|
|
2178
|
-
},
|
|
2179
|
-
body: JSON.stringify({
|
|
2180
|
-
assistant_id: config.assistantId,
|
|
2181
|
-
input: { messages: lgMessages },
|
|
2182
|
-
stream_mode: ["messages-tuple"],
|
|
2183
|
-
config: {
|
|
2184
|
-
configurable: {
|
|
2185
|
-
sub: credentials?.userId,
|
|
2186
|
-
userId: options?.userId,
|
|
2187
|
-
orgId: options?.orgId,
|
|
2188
|
-
chatId: options?.chatId,
|
|
2189
|
-
lastThreadId: threadId
|
|
2190
|
-
}
|
|
2191
|
-
}
|
|
2192
|
-
})
|
|
2193
|
-
});
|
|
2194
|
-
if (!response.ok) {
|
|
2195
|
-
const text = await response.text();
|
|
2196
|
-
yield { type: "error", error: `LangGraph error: ${response.status} ${text}` };
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
if (!response.body) {
|
|
2200
|
-
yield { type: "error", error: "No response body" };
|
|
2201
|
-
return;
|
|
2202
|
-
}
|
|
2203
|
-
const reader = response.body.getReader();
|
|
2204
|
-
const decoder = new TextDecoder();
|
|
2205
|
-
let buffer = "";
|
|
2206
|
-
while (true) {
|
|
2207
|
-
const { done, value } = await reader.read();
|
|
2208
|
-
if (done)
|
|
2209
|
-
break;
|
|
2210
|
-
buffer += decoder.decode(value, { stream: true });
|
|
2211
|
-
const lines = buffer.split("\n");
|
|
2212
|
-
buffer = lines.pop() || "";
|
|
2213
|
-
for (const line of lines) {
|
|
2214
|
-
const trimmed = line.trim();
|
|
2215
|
-
if (!trimmed)
|
|
2216
|
-
continue;
|
|
2217
|
-
let data;
|
|
2218
|
-
if (trimmed.startsWith("data:")) {
|
|
2219
|
-
data = trimmed.slice(5).trim();
|
|
2220
|
-
} else if (trimmed.startsWith("{")) {
|
|
2221
|
-
data = trimmed;
|
|
2222
|
-
} else {
|
|
2223
|
-
continue;
|
|
2224
|
-
}
|
|
2225
|
-
if (!data || data === "[DONE]")
|
|
2226
|
-
continue;
|
|
2227
|
-
try {
|
|
2228
|
-
const chunk = JSON.parse(data);
|
|
2229
|
-
if (chunk.event === "messages") {
|
|
2230
|
-
const messages2 = chunk.data;
|
|
2231
|
-
if (Array.isArray(messages2)) {
|
|
2232
|
-
for (const msg of messages2) {
|
|
2233
|
-
const msgType = msg.type ?? "ai";
|
|
2234
|
-
if (msgType === "ai" || msgType === "assistant") {
|
|
2235
|
-
const content = extractTextContent(msg.content);
|
|
2236
|
-
if (content) {
|
|
2237
|
-
yield { type: "text", content };
|
|
2238
|
-
}
|
|
2239
|
-
}
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
} else if (chunk.event?.toLowerCase().includes("error")) {
|
|
2243
|
-
const errorData = chunk.data;
|
|
2244
|
-
const errorMsg = typeof errorData?.error === "string" ? errorData.error : typeof errorData?.message === "string" ? errorData.message : "Unknown error";
|
|
2245
|
-
yield { type: "error", error: errorMsg };
|
|
2246
|
-
}
|
|
2247
|
-
} catch {
|
|
2248
|
-
}
|
|
2249
|
-
}
|
|
2250
|
-
}
|
|
2251
|
-
if (buffer.trim()) {
|
|
2252
|
-
const data = buffer.trim().startsWith("data:") ? buffer.trim().slice(5).trim() : buffer.trim();
|
|
2253
|
-
if (data && data !== "[DONE]") {
|
|
2254
|
-
try {
|
|
2255
|
-
const chunk = JSON.parse(data);
|
|
2256
|
-
if (chunk.event === "messages") {
|
|
2257
|
-
const messages2 = chunk.data;
|
|
2258
|
-
if (Array.isArray(messages2)) {
|
|
2259
|
-
for (const msg of messages2) {
|
|
2260
|
-
const msgType = msg.type ?? "ai";
|
|
2261
|
-
if (msgType === "ai" || msgType === "assistant") {
|
|
2262
|
-
const content = extractTextContent(msg.content);
|
|
2263
|
-
if (content) {
|
|
2264
|
-
yield { type: "text", content };
|
|
2265
|
-
}
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
} catch {
|
|
2271
|
-
}
|
|
2272
|
-
}
|
|
2273
|
-
}
|
|
2274
|
-
yield { type: "done" };
|
|
2275
|
-
} catch (error) {
|
|
2276
|
-
if (error instanceof Error) {
|
|
2277
|
-
yield { type: "error", error: error.message };
|
|
2278
|
-
} else {
|
|
2279
|
-
yield { type: "error", error: "Unknown error occurred" };
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
function extractTextContent(content) {
|
|
2284
|
-
if (typeof content === "string") {
|
|
2285
|
-
return content;
|
|
2286
|
-
}
|
|
2287
|
-
if (Array.isArray(content)) {
|
|
2288
|
-
const texts = [];
|
|
2289
|
-
for (const item of content) {
|
|
2290
|
-
if (typeof item === "string") {
|
|
2291
|
-
texts.push(item);
|
|
2292
|
-
} else if (item && typeof item === "object" && "text" in item) {
|
|
2293
|
-
const text = item.text;
|
|
2294
|
-
if (typeof text === "string") {
|
|
2295
|
-
texts.push(text);
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
}
|
|
2299
|
-
return texts.join("");
|
|
2300
|
-
}
|
|
2301
|
-
return "";
|
|
2302
|
-
}
|
|
2303
|
-
async function chatCompletion(config, threadId, messages, options) {
|
|
2304
|
-
const credentials = await loadCredentials();
|
|
2305
|
-
const token = await getToken();
|
|
2306
|
-
if (!token) {
|
|
2307
|
-
return { success: false, error: "Not authenticated" };
|
|
2308
|
-
}
|
|
2309
|
-
const lgMessages = toLangGraphMessages(messages);
|
|
2310
|
-
try {
|
|
2311
|
-
const response = await fetch(`${config.apiUrl}/threads/${threadId}/runs/wait`, {
|
|
2312
|
-
method: "POST",
|
|
2313
|
-
headers: {
|
|
2314
|
-
"Content-Type": "application/json",
|
|
2315
|
-
"x-api-key": config.apiKey
|
|
2316
|
-
},
|
|
2317
|
-
body: JSON.stringify({
|
|
2318
|
-
assistant_id: config.assistantId,
|
|
2319
|
-
input: { messages: lgMessages },
|
|
2320
|
-
config: {
|
|
2321
|
-
configurable: {
|
|
2322
|
-
sub: credentials?.userId,
|
|
2323
|
-
userId: options?.userId,
|
|
2324
|
-
orgId: options?.orgId,
|
|
2325
|
-
chatId: options?.chatId,
|
|
2326
|
-
lastThreadId: threadId
|
|
2327
|
-
}
|
|
2328
|
-
}
|
|
2329
|
-
})
|
|
2330
|
-
});
|
|
2331
|
-
if (!response.ok) {
|
|
2332
|
-
const text = await response.text();
|
|
2333
|
-
return { success: false, error: `LangGraph error: ${response.status} ${text}` };
|
|
2334
|
-
}
|
|
2335
|
-
const result = await response.json();
|
|
2336
|
-
let content = "";
|
|
2337
|
-
if (Array.isArray(result?.messages)) {
|
|
2338
|
-
const lastMessage = result.messages[result.messages.length - 1];
|
|
2339
|
-
if (lastMessage?.content) {
|
|
2340
|
-
content = extractTextContent(lastMessage.content);
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
return { success: true, content };
|
|
2344
|
-
} catch (error) {
|
|
2345
|
-
if (error instanceof Error) {
|
|
2346
|
-
return { success: false, error: error.message };
|
|
2347
|
-
}
|
|
2348
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
|
|
2352
|
-
// apps/cli/src/commands/chat/index.ts
|
|
2353
|
-
function isValidObjectId(id) {
|
|
2354
|
-
return /^[a-fA-F0-9]{24}$/.test(id);
|
|
2355
|
-
}
|
|
2356
|
-
function createChatCommand() {
|
|
2357
|
-
const chat = new import_commander4.Command("chat").description("Chat with Indigo AI");
|
|
2358
|
-
chat.command("start").description("Start a new interactive chat session").option("--json", "Output in JSON format (for non-interactive use)").option("--no-stream", "Disable streaming (wait for complete response)").action(async (options) => {
|
|
2359
|
-
await handleChatStart(options);
|
|
2360
|
-
});
|
|
2361
|
-
chat.command("continue <session-id>").description("Continue an existing chat session").option("--json", "Output in JSON format").option("--no-stream", "Disable streaming").action(async (sessionId, options) => {
|
|
2362
|
-
await handleChatContinue(sessionId, options);
|
|
2363
|
-
});
|
|
2364
|
-
chat.command("list").description("List recent chat sessions").option("-n, --limit <number>", "Number of sessions to show", "10").option("--json", "Output in JSON format").action(async (options) => {
|
|
2365
|
-
await handleChatList(options);
|
|
2366
|
-
});
|
|
2367
|
-
chat.action(() => {
|
|
2368
|
-
console.log("Chat commands:");
|
|
2369
|
-
console.log(" indigo chat start Start a new chat session");
|
|
2370
|
-
console.log(" indigo chat continue <id> Continue an existing session");
|
|
2371
|
-
console.log(" indigo chat list List recent sessions");
|
|
2372
|
-
console.log("");
|
|
2373
|
-
console.log('Run "indigo chat --help" for more information.');
|
|
2374
|
-
});
|
|
2375
|
-
return chat;
|
|
2376
|
-
}
|
|
2377
|
-
async function handleChatStart(options) {
|
|
2378
|
-
const ctx = createOutputContext(options);
|
|
2379
|
-
const authState = await getAuthState();
|
|
2380
|
-
if (!authState.isAuthenticated) {
|
|
2381
|
-
outputAuthRequired(ctx);
|
|
2382
|
-
}
|
|
2383
|
-
const lgConfig = getLangGraphConfig();
|
|
2384
|
-
if (!lgConfig) {
|
|
2385
|
-
outputConfigError(ctx, "Chat is not configured. LangGraph environment variables are missing.", [
|
|
2386
|
-
"LANGGRAPH_API_URL",
|
|
2387
|
-
"LANGSMITH_API_KEY",
|
|
2388
|
-
"LANGGRAPH_ASSISTANT_ID"
|
|
2389
|
-
]);
|
|
2390
|
-
}
|
|
2391
|
-
const threadResult = await createThread(lgConfig, {
|
|
2392
|
-
userId: authState.user?.id,
|
|
2393
|
-
source: "cli"
|
|
2394
|
-
});
|
|
2395
|
-
if (!threadResult.success || !threadResult.threadId) {
|
|
2396
|
-
outputError(ctx, threadResult.error || "Failed to create thread", {
|
|
2397
|
-
code: "THREAD_ERROR",
|
|
2398
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
2399
|
-
});
|
|
2400
|
-
}
|
|
2401
|
-
const chatResult = await createChat({
|
|
2402
|
-
lastThreadId: threadResult.threadId,
|
|
2403
|
-
messages: [],
|
|
2404
|
-
summary: "New CLI chat session"
|
|
2405
|
-
});
|
|
2406
|
-
if (!chatResult.success || !chatResult.chat) {
|
|
2407
|
-
outputError(ctx, chatResult.error || "Failed to create chat session", {
|
|
2408
|
-
code: "SESSION_ERROR",
|
|
2409
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
2410
|
-
});
|
|
2411
|
-
}
|
|
2412
|
-
const sessionState = {
|
|
2413
|
-
chatId: chatResult.chat._id,
|
|
2414
|
-
threadId: threadResult.threadId,
|
|
2415
|
-
messages: []
|
|
2416
|
-
};
|
|
2417
|
-
if (ctx.json) {
|
|
2418
|
-
outputSuccess(
|
|
2419
|
-
ctx,
|
|
2420
|
-
{
|
|
2421
|
-
sessionId: sessionState.chatId,
|
|
2422
|
-
threadId: sessionState.threadId,
|
|
2423
|
-
message: 'Chat session created. Use "indigo chat continue" with this session ID.'
|
|
2424
|
-
},
|
|
2425
|
-
noop
|
|
2426
|
-
);
|
|
2427
|
-
return;
|
|
2428
|
-
}
|
|
2429
|
-
console.log("");
|
|
2430
|
-
console.log("Indigo Chat");
|
|
2431
|
-
console.log("Session ID: " + sessionState.chatId);
|
|
2432
|
-
console.log("");
|
|
2433
|
-
console.log("Type your message and press Enter to send.");
|
|
2434
|
-
console.log('Type "exit" or press Ctrl+C to end the session.');
|
|
2435
|
-
console.log("");
|
|
2436
|
-
await runInteractiveSession(sessionState, lgConfig, options.stream !== false);
|
|
2437
|
-
}
|
|
2438
|
-
async function handleChatContinue(sessionId, options) {
|
|
2439
|
-
const ctx = createOutputContext(options);
|
|
2440
|
-
if (!sessionId || sessionId.trim() === "") {
|
|
2441
|
-
outputValidationError(
|
|
2442
|
-
ctx,
|
|
2443
|
-
"Session ID is required",
|
|
2444
|
-
'Provide a valid session ID. Run "indigo chat list" to see available sessions.'
|
|
2445
|
-
);
|
|
2446
|
-
}
|
|
2447
|
-
if (!isValidObjectId(sessionId)) {
|
|
2448
|
-
outputValidationError(
|
|
2449
|
-
ctx,
|
|
2450
|
-
"Invalid session ID format",
|
|
2451
|
-
'Session ID must be a 24-character hexadecimal string. Run "indigo chat list" to see valid session IDs.'
|
|
2452
|
-
);
|
|
2453
|
-
}
|
|
2454
|
-
const authState = await getAuthState();
|
|
2455
|
-
if (!authState.isAuthenticated) {
|
|
2456
|
-
outputAuthRequired(ctx);
|
|
2457
|
-
}
|
|
2458
|
-
const lgConfig = getLangGraphConfig();
|
|
2459
|
-
if (!lgConfig) {
|
|
2460
|
-
outputConfigError(ctx, "Chat is not configured. LangGraph environment variables are missing.", [
|
|
2461
|
-
"LANGGRAPH_API_URL",
|
|
2462
|
-
"LANGSMITH_API_KEY",
|
|
2463
|
-
"LANGGRAPH_ASSISTANT_ID"
|
|
2464
|
-
]);
|
|
2465
|
-
}
|
|
2466
|
-
const chatResult = await getChatById(sessionId);
|
|
2467
|
-
if (!chatResult.success || !chatResult.chat) {
|
|
2468
|
-
if (chatResult.error?.includes("not found") || chatResult.error?.includes("404")) {
|
|
2469
|
-
outputNotFound(ctx, "Chat session", sessionId);
|
|
2470
|
-
}
|
|
2471
|
-
outputError(ctx, chatResult.error || "Failed to load chat session", {
|
|
2472
|
-
code: "SESSION_ERROR",
|
|
2473
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
2474
|
-
});
|
|
2475
|
-
}
|
|
2476
|
-
const chat = chatResult.chat;
|
|
2477
|
-
let threadId = chat.lastThreadId;
|
|
2478
|
-
if (!threadId) {
|
|
2479
|
-
const threadResult = await createThread(lgConfig, {
|
|
2480
|
-
userId: authState.user?.id,
|
|
2481
|
-
chatId: sessionId,
|
|
2482
|
-
source: "cli"
|
|
2483
|
-
});
|
|
2484
|
-
if (!threadResult.success || !threadResult.threadId) {
|
|
2485
|
-
outputError(ctx, threadResult.error || "Failed to create thread", {
|
|
2486
|
-
code: "THREAD_ERROR",
|
|
2487
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
2488
|
-
});
|
|
2489
|
-
}
|
|
2490
|
-
threadId = threadResult.threadId;
|
|
2491
|
-
await updateChat(sessionId, { lastThreadId: threadId });
|
|
2492
|
-
}
|
|
2493
|
-
const messagesResult = await getChatMessages(sessionId, { limit: 50, sortOrder: "asc" });
|
|
2494
|
-
const previousMessages = [];
|
|
2495
|
-
if (messagesResult.success && messagesResult.messages) {
|
|
2496
|
-
for (const msg of messagesResult.messages) {
|
|
2497
|
-
previousMessages.push({
|
|
2498
|
-
role: msg.role,
|
|
2499
|
-
content: msg.content
|
|
2500
|
-
});
|
|
2501
|
-
}
|
|
2502
|
-
}
|
|
2503
|
-
const sessionState = {
|
|
2504
|
-
chatId: sessionId,
|
|
2505
|
-
threadId,
|
|
2506
|
-
messages: previousMessages
|
|
2507
|
-
};
|
|
2508
|
-
if (ctx.json) {
|
|
2509
|
-
outputSuccess(
|
|
2510
|
-
ctx,
|
|
2511
|
-
{
|
|
2512
|
-
sessionId: sessionState.chatId,
|
|
2513
|
-
threadId: sessionState.threadId,
|
|
2514
|
-
messageCount: previousMessages.length,
|
|
2515
|
-
message: "Chat session loaded."
|
|
2516
|
-
},
|
|
2517
|
-
noop
|
|
2518
|
-
);
|
|
2519
|
-
return;
|
|
2520
|
-
}
|
|
2521
|
-
console.log("");
|
|
2522
|
-
console.log("Indigo Chat (Continued)");
|
|
2523
|
-
console.log("Session ID: " + sessionState.chatId);
|
|
2524
|
-
console.log("");
|
|
2525
|
-
if (previousMessages.length > 0) {
|
|
2526
|
-
console.log("Recent messages:");
|
|
2527
|
-
console.log("-".repeat(40));
|
|
2528
|
-
const recentMessages = previousMessages.slice(-6);
|
|
2529
|
-
for (const msg of recentMessages) {
|
|
2530
|
-
const prefix = msg.role === "user" ? "You: " : "Indigo: ";
|
|
2531
|
-
const content = truncateText(msg.content, 200);
|
|
2532
|
-
console.log(prefix + content);
|
|
2533
|
-
console.log("");
|
|
2534
|
-
}
|
|
2535
|
-
console.log("-".repeat(40));
|
|
2536
|
-
console.log("");
|
|
2537
|
-
}
|
|
2538
|
-
console.log("Type your message and press Enter to send.");
|
|
2539
|
-
console.log('Type "exit" or press Ctrl+C to end the session.');
|
|
2540
|
-
console.log("");
|
|
2541
|
-
await runInteractiveSession(sessionState, lgConfig, options.stream !== false);
|
|
2542
|
-
}
|
|
2543
|
-
async function handleChatList(options) {
|
|
2544
|
-
const ctx = createOutputContext(options);
|
|
2545
|
-
if (options.limit !== void 0) {
|
|
2546
|
-
const limitValue = parseInt(options.limit, 10);
|
|
2547
|
-
if (isNaN(limitValue) || limitValue <= 0 || !Number.isInteger(limitValue)) {
|
|
2548
|
-
outputValidationError(
|
|
2549
|
-
ctx,
|
|
2550
|
-
"--limit must be a positive integer",
|
|
2551
|
-
"Provide a number greater than 0, e.g., --limit 10"
|
|
2552
|
-
);
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
const authState = await getAuthState();
|
|
2556
|
-
if (!authState.isAuthenticated) {
|
|
2557
|
-
outputAuthRequired(ctx);
|
|
2558
|
-
}
|
|
2559
|
-
const limit = parseInt(options.limit || "10", 10);
|
|
2560
|
-
const result = await listChats({ limit });
|
|
2561
|
-
if (!result.success) {
|
|
2562
|
-
outputError(ctx, result.error || "Failed to list chat sessions", {
|
|
2563
|
-
code: "LIST_ERROR",
|
|
2564
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
2565
|
-
});
|
|
2566
|
-
}
|
|
2567
|
-
const chats = result.chats || [];
|
|
2568
|
-
outputData(ctx, chats, (data) => {
|
|
2569
|
-
if (data.length === 0) {
|
|
2570
|
-
console.log("No chat sessions found.");
|
|
2571
|
-
console.log("Start a new chat with: indigo chat start");
|
|
2572
|
-
return;
|
|
2573
|
-
}
|
|
2574
|
-
console.log("");
|
|
2575
|
-
console.log("Recent Chat Sessions");
|
|
2576
|
-
console.log("=".repeat(80));
|
|
2577
|
-
console.log("");
|
|
2578
|
-
console.log(padRight("ID", 26) + padRight("DATE", 20) + "SUMMARY");
|
|
2579
|
-
console.log("-".repeat(80));
|
|
2580
|
-
for (const chat of data) {
|
|
2581
|
-
const id = chat._id;
|
|
2582
|
-
const date = formatChatDate(chat.createdAt);
|
|
2583
|
-
const summary = truncateText(chat.summary || "(no summary)", 30);
|
|
2584
|
-
console.log(padRight(id, 26) + padRight(date, 20) + summary);
|
|
2585
|
-
}
|
|
2586
|
-
console.log("");
|
|
2587
|
-
console.log("To continue a session: indigo chat continue <id>");
|
|
2588
|
-
});
|
|
2589
|
-
}
|
|
2590
|
-
async function runInteractiveSession(state, lgConfig, useStreaming) {
|
|
2591
|
-
if (!lgConfig) {
|
|
2592
|
-
console.error("LangGraph not configured");
|
|
2593
|
-
process.exit(1);
|
|
2594
|
-
}
|
|
2595
|
-
const rl = readline.createInterface({
|
|
2596
|
-
input: process.stdin,
|
|
2597
|
-
output: process.stdout
|
|
2598
|
-
});
|
|
2599
|
-
let isExiting = false;
|
|
2600
|
-
const handleExit = () => {
|
|
2601
|
-
if (isExiting)
|
|
2602
|
-
return;
|
|
2603
|
-
isExiting = true;
|
|
2604
|
-
console.log("\n");
|
|
2605
|
-
console.log("Chat session ended.");
|
|
2606
|
-
console.log("Session ID: " + state.chatId);
|
|
2607
|
-
console.log("To continue this session: indigo chat continue " + state.chatId);
|
|
2608
|
-
rl.close();
|
|
2609
|
-
process.exit(0);
|
|
2610
|
-
};
|
|
2611
|
-
process.on("SIGINT", handleExit);
|
|
2612
|
-
rl.on("close", handleExit);
|
|
2613
|
-
const prompt2 = () => {
|
|
2614
|
-
rl.question("You: ", async (input) => {
|
|
2615
|
-
const trimmed = input.trim();
|
|
2616
|
-
if (!trimmed) {
|
|
2617
|
-
prompt2();
|
|
2618
|
-
return;
|
|
2619
|
-
}
|
|
2620
|
-
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
2621
|
-
handleExit();
|
|
2622
|
-
return;
|
|
2623
|
-
}
|
|
2624
|
-
state.messages.push({ role: "user", content: trimmed });
|
|
2625
|
-
await createMessage({
|
|
2626
|
-
content: trimmed,
|
|
2627
|
-
role: "user",
|
|
2628
|
-
chatId: state.chatId,
|
|
2629
|
-
parts: [{ type: "text", text: trimmed }]
|
|
2630
|
-
});
|
|
2631
|
-
process.stdout.write("\nIndigo: ");
|
|
2632
|
-
let fullResponse = "";
|
|
2633
|
-
if (useStreaming) {
|
|
2634
|
-
try {
|
|
2635
|
-
for await (const chunk of streamChat(lgConfig, state.threadId, state.messages, {
|
|
2636
|
-
chatId: state.chatId
|
|
2637
|
-
})) {
|
|
2638
|
-
if (chunk.type === "text" && chunk.content) {
|
|
2639
|
-
process.stdout.write(chunk.content);
|
|
2640
|
-
fullResponse += chunk.content;
|
|
2641
|
-
} else if (chunk.type === "error") {
|
|
2642
|
-
console.error("\nError:", chunk.error);
|
|
2643
|
-
break;
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
} catch (error) {
|
|
2647
|
-
if (error instanceof Error) {
|
|
2648
|
-
console.error("\nError:", error.message);
|
|
2649
|
-
} else {
|
|
2650
|
-
console.error("\nAn error occurred");
|
|
2651
|
-
}
|
|
2652
|
-
}
|
|
2653
|
-
} else {
|
|
2654
|
-
const result = await chatCompletion(lgConfig, state.threadId, state.messages, {
|
|
2655
|
-
chatId: state.chatId
|
|
2656
|
-
});
|
|
2657
|
-
if (result.success && result.content) {
|
|
2658
|
-
fullResponse = result.content;
|
|
2659
|
-
process.stdout.write(fullResponse);
|
|
2660
|
-
} else {
|
|
2661
|
-
console.error("\nError:", result.error || "Failed to get response");
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
console.log("\n");
|
|
2665
|
-
if (fullResponse) {
|
|
2666
|
-
state.messages.push({ role: "assistant", content: fullResponse });
|
|
2667
|
-
await createMessage({
|
|
2668
|
-
content: fullResponse,
|
|
2669
|
-
role: "assistant",
|
|
2670
|
-
chatId: state.chatId,
|
|
2671
|
-
parts: [{ type: "text", text: fullResponse }]
|
|
2672
|
-
});
|
|
2673
|
-
}
|
|
2674
|
-
prompt2();
|
|
2675
|
-
});
|
|
2676
|
-
};
|
|
2677
|
-
prompt2();
|
|
2678
|
-
}
|
|
2679
|
-
function padRight(str, length) {
|
|
2680
|
-
if (str.length >= length)
|
|
2681
|
-
return str.slice(0, length);
|
|
2682
|
-
return str + " ".repeat(length - str.length);
|
|
2683
|
-
}
|
|
2684
|
-
|
|
2685
|
-
// apps/cli/src/commands/setup/index.ts
|
|
2686
|
-
var import_commander5 = require("commander");
|
|
2687
|
-
var import_child_process3 = require("child_process");
|
|
2688
|
-
var readline2 = __toESM(require("readline"));
|
|
2689
|
-
|
|
2690
|
-
// apps/cli/src/services/calendar.ts
|
|
2691
|
-
async function getCalendarScopeStatus() {
|
|
2692
|
-
const token = await getToken();
|
|
2693
|
-
if (!token) {
|
|
2694
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2695
|
-
}
|
|
2696
|
-
try {
|
|
2697
|
-
const response = await fetch(`${apiUrl}/calendar/scope-status`, {
|
|
2698
|
-
method: "GET",
|
|
2699
|
-
headers: {
|
|
2700
|
-
"Content-Type": "application/json",
|
|
2701
|
-
Authorization: `Bearer ${token}`
|
|
2702
|
-
}
|
|
2703
|
-
});
|
|
2704
|
-
if (!response.ok) {
|
|
2705
|
-
if (response.status === 401) {
|
|
2706
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2707
|
-
}
|
|
2708
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2709
|
-
}
|
|
2710
|
-
const data = await response.json();
|
|
2711
|
-
return { success: true, data };
|
|
2712
|
-
} catch (error) {
|
|
2713
|
-
if (error instanceof Error) {
|
|
2714
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2715
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2716
|
-
}
|
|
2717
|
-
return { success: false, error: error.message };
|
|
2718
|
-
}
|
|
2719
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2720
|
-
}
|
|
2721
|
-
}
|
|
2722
|
-
async function getConnectUrl() {
|
|
2723
|
-
const token = await getToken();
|
|
2724
|
-
if (!token) {
|
|
2725
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2726
|
-
}
|
|
2727
|
-
try {
|
|
2728
|
-
const response = await fetch(`${apiUrl}/calendar/connect-url`, {
|
|
2729
|
-
method: "GET",
|
|
2730
|
-
headers: {
|
|
2731
|
-
"Content-Type": "application/json",
|
|
2732
|
-
Authorization: `Bearer ${token}`
|
|
2733
|
-
}
|
|
2734
|
-
});
|
|
2735
|
-
if (!response.ok) {
|
|
2736
|
-
if (response.status === 401) {
|
|
2737
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2738
|
-
}
|
|
2739
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2740
|
-
}
|
|
2741
|
-
const data = await response.json();
|
|
2742
|
-
return { success: true, data };
|
|
2743
|
-
} catch (error) {
|
|
2744
|
-
if (error instanceof Error) {
|
|
2745
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2746
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2747
|
-
}
|
|
2748
|
-
return { success: false, error: error.message };
|
|
2749
|
-
}
|
|
2750
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2751
|
-
}
|
|
2752
|
-
}
|
|
2753
|
-
async function getUser(token) {
|
|
2754
|
-
try {
|
|
2755
|
-
const response = await fetch(`${apiUrl}/users/profile`, {
|
|
2756
|
-
method: "GET",
|
|
2757
|
-
headers: {
|
|
2758
|
-
"Content-Type": "application/json",
|
|
2759
|
-
Authorization: `Bearer ${token}`
|
|
2760
|
-
}
|
|
2761
|
-
});
|
|
2762
|
-
if (!response.ok) {
|
|
2763
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2764
|
-
}
|
|
2765
|
-
return { success: true, data: await response.json() };
|
|
2766
|
-
} catch (error) {
|
|
2767
|
-
if (error instanceof Error) {
|
|
2768
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2769
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2770
|
-
}
|
|
2771
|
-
return { success: false, error: error.message };
|
|
2772
|
-
}
|
|
2773
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2774
|
-
}
|
|
2775
|
-
}
|
|
2776
|
-
async function listCalendars() {
|
|
2777
|
-
const token = await getToken();
|
|
2778
|
-
if (!token) {
|
|
2779
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2780
|
-
}
|
|
2781
|
-
try {
|
|
2782
|
-
const user = await getUser(token);
|
|
2783
|
-
if (!user.success) {
|
|
2784
|
-
return { success: false, error: user.error };
|
|
2785
|
-
}
|
|
2786
|
-
const response = await fetch(`${apiUrl}/calendar/list`, {
|
|
2787
|
-
method: "GET",
|
|
2788
|
-
headers: {
|
|
2789
|
-
"Content-Type": "application/json",
|
|
2790
|
-
Authorization: `Bearer ${token}`
|
|
2791
|
-
}
|
|
2792
|
-
});
|
|
2793
|
-
if (!response.ok) {
|
|
2794
|
-
if (response.status === 401) {
|
|
2795
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2796
|
-
}
|
|
2797
|
-
if (response.status === 403) {
|
|
2798
|
-
return { success: false, error: "Calendar access not granted. Connect your calendar first." };
|
|
2799
|
-
}
|
|
2800
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2801
|
-
}
|
|
2802
|
-
const data = await response.json();
|
|
2803
|
-
const calendars = data.map((cal) => {
|
|
2804
|
-
return {
|
|
2805
|
-
id: cal.id,
|
|
2806
|
-
name: cal.summary,
|
|
2807
|
-
accessRole: cal.accessRole,
|
|
2808
|
-
primary: cal.primary,
|
|
2809
|
-
selected: user.data.googleCalendar?.selectedCalendars?.some((selectedCal) => selectedCal.id === cal.id) || false
|
|
2810
|
-
};
|
|
2811
|
-
});
|
|
2812
|
-
return { success: true, data: calendars };
|
|
2813
|
-
} catch (error) {
|
|
2814
|
-
if (error instanceof Error) {
|
|
2815
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2816
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2817
|
-
}
|
|
2818
|
-
return { success: false, error: error.message };
|
|
2819
|
-
}
|
|
2820
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2821
|
-
}
|
|
2822
|
-
}
|
|
2823
|
-
async function selectCalendars(calendars) {
|
|
2824
|
-
const token = await getToken();
|
|
2825
|
-
if (!token) {
|
|
2826
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2827
|
-
}
|
|
2828
|
-
try {
|
|
2829
|
-
const response = await fetch(`${apiUrl}/calendar/select`, {
|
|
2830
|
-
method: "POST",
|
|
2831
|
-
headers: {
|
|
2832
|
-
"Content-Type": "application/json",
|
|
2833
|
-
Authorization: `Bearer ${token}`
|
|
2834
|
-
},
|
|
2835
|
-
body: JSON.stringify({ calendars })
|
|
2836
|
-
});
|
|
2837
|
-
if (!response.ok) {
|
|
2838
|
-
if (response.status === 401) {
|
|
2839
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2840
|
-
}
|
|
2841
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2842
|
-
}
|
|
2843
|
-
return { success: true };
|
|
2844
|
-
} catch (error) {
|
|
2845
|
-
if (error instanceof Error) {
|
|
2846
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2847
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2848
|
-
}
|
|
2849
|
-
return { success: false, error: error.message };
|
|
2850
|
-
}
|
|
2851
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
async function disconnectCalendar() {
|
|
2855
|
-
const token = await getToken();
|
|
2856
|
-
if (!token) {
|
|
2857
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2858
|
-
}
|
|
2859
|
-
try {
|
|
2860
|
-
const response = await fetch(`${apiUrl}/calendar/disconnect`, {
|
|
2861
|
-
method: "POST",
|
|
2862
|
-
headers: {
|
|
2863
|
-
"Content-Type": "application/json",
|
|
2864
|
-
Authorization: `Bearer ${token}`
|
|
2865
|
-
}
|
|
2866
|
-
});
|
|
2867
|
-
if (!response.ok) {
|
|
2868
|
-
if (response.status === 401) {
|
|
2869
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2870
|
-
}
|
|
2871
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
2872
|
-
}
|
|
2873
|
-
return { success: true };
|
|
2874
|
-
} catch (error) {
|
|
2875
|
-
if (error instanceof Error) {
|
|
2876
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2877
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2878
|
-
}
|
|
2879
|
-
return { success: false, error: error.message };
|
|
2880
|
-
}
|
|
2881
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2882
|
-
}
|
|
2883
|
-
}
|
|
2884
|
-
async function getCalendarConnectionStatus() {
|
|
2885
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
2886
|
-
if (!scopeResult.success || !scopeResult.data) {
|
|
2887
|
-
return { success: false, error: scopeResult.error };
|
|
2888
|
-
}
|
|
2889
|
-
const { hasGoogleAccount, hasCalendarScope, googleEmail } = scopeResult.data;
|
|
2890
|
-
if (!hasGoogleAccount || !hasCalendarScope) {
|
|
2891
|
-
return {
|
|
2892
|
-
success: true,
|
|
2893
|
-
data: {
|
|
2894
|
-
isConnected: false,
|
|
2895
|
-
email: googleEmail
|
|
2896
|
-
}
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
|
-
const calendarsResult = await listCalendars();
|
|
2900
|
-
const selectedCalendars = calendarsResult.success && calendarsResult.data ? calendarsResult.data.filter((c) => c.selected).map((c) => ({ id: c.id, name: c.name })) : [];
|
|
2901
|
-
return {
|
|
2902
|
-
success: true,
|
|
2903
|
-
data: {
|
|
2904
|
-
isConnected: true,
|
|
2905
|
-
email: googleEmail,
|
|
2906
|
-
selectedCalendars
|
|
2907
|
-
}
|
|
2908
|
-
};
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
// apps/cli/src/services/api-keys.ts
|
|
2912
|
-
var API_KEY_PROVIDERS = ["openai", "anthropic", "google", "xai"];
|
|
2913
|
-
var PROVIDER_INFO = {
|
|
2914
|
-
openai: {
|
|
2915
|
-
name: "openai",
|
|
2916
|
-
displayName: "OpenAI",
|
|
2917
|
-
placeholder: "sk-...",
|
|
2918
|
-
description: "Powers GPT-4, GPT-4o, and other OpenAI models",
|
|
2919
|
-
docsUrl: "https://platform.openai.com/api-keys"
|
|
2920
|
-
},
|
|
2921
|
-
anthropic: {
|
|
2922
|
-
name: "anthropic",
|
|
2923
|
-
displayName: "Anthropic",
|
|
2924
|
-
placeholder: "sk-ant-...",
|
|
2925
|
-
description: "Powers Claude models (Claude 3, Claude 3.5, etc.)",
|
|
2926
|
-
docsUrl: "https://console.anthropic.com/settings/keys"
|
|
2927
|
-
},
|
|
2928
|
-
google: {
|
|
2929
|
-
name: "google",
|
|
2930
|
-
displayName: "Google (Gemini)",
|
|
2931
|
-
placeholder: "AIza...",
|
|
2932
|
-
description: "Powers Gemini models",
|
|
2933
|
-
docsUrl: "https://aistudio.google.com/app/apikey"
|
|
2934
|
-
},
|
|
2935
|
-
xai: {
|
|
2936
|
-
name: "xai",
|
|
2937
|
-
displayName: "xAI (Grok)",
|
|
2938
|
-
placeholder: "xai-...",
|
|
2939
|
-
description: "Powers Grok models",
|
|
2940
|
-
docsUrl: "https://console.x.ai/"
|
|
2941
|
-
}
|
|
2942
|
-
};
|
|
2943
|
-
function isAdminRequired() {
|
|
2944
|
-
return "API key management requires admin or owner permissions. Contact your organization admin.";
|
|
2945
|
-
}
|
|
2946
|
-
async function listApiKeys() {
|
|
2947
|
-
const token = await getToken();
|
|
2948
|
-
if (!token) {
|
|
2949
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2950
|
-
}
|
|
2951
|
-
try {
|
|
2952
|
-
const response = await fetch(`${apiUrl}/company/api-keys`, {
|
|
2953
|
-
method: "GET",
|
|
2954
|
-
headers: {
|
|
2955
|
-
"Content-Type": "application/json",
|
|
2956
|
-
Authorization: `Bearer ${token}`
|
|
2957
|
-
}
|
|
2958
|
-
});
|
|
2959
|
-
if (!response.ok) {
|
|
2960
|
-
if (response.status === 401) {
|
|
2961
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2962
|
-
}
|
|
2963
|
-
if (response.status === 403) {
|
|
2964
|
-
return { success: false, error: isAdminRequired() };
|
|
2965
|
-
}
|
|
2966
|
-
const errorData = await response.json().catch(() => ({}));
|
|
2967
|
-
return { success: false, error: errorData.message || `API error: ${response.status} ${response.statusText}` };
|
|
2968
|
-
}
|
|
2969
|
-
const data = await response.json();
|
|
2970
|
-
return { success: true, data };
|
|
2971
|
-
} catch (error) {
|
|
2972
|
-
if (error instanceof Error) {
|
|
2973
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
2974
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
2975
|
-
}
|
|
2976
|
-
return { success: false, error: error.message };
|
|
2977
|
-
}
|
|
2978
|
-
return { success: false, error: "Unknown error occurred" };
|
|
2979
|
-
}
|
|
2980
|
-
}
|
|
2981
|
-
async function saveApiKey(provider, key) {
|
|
2982
|
-
const token = await getToken();
|
|
2983
|
-
if (!token) {
|
|
2984
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
2985
|
-
}
|
|
2986
|
-
try {
|
|
2987
|
-
const response = await fetch(`${apiUrl}/company/api-keys`, {
|
|
2988
|
-
method: "POST",
|
|
2989
|
-
headers: {
|
|
2990
|
-
"Content-Type": "application/json",
|
|
2991
|
-
Authorization: `Bearer ${token}`
|
|
2992
|
-
},
|
|
2993
|
-
body: JSON.stringify({ provider, key })
|
|
2994
|
-
});
|
|
2995
|
-
if (!response.ok) {
|
|
2996
|
-
if (response.status === 401) {
|
|
2997
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
2998
|
-
}
|
|
2999
|
-
if (response.status === 403) {
|
|
3000
|
-
return { success: false, error: isAdminRequired() };
|
|
3001
|
-
}
|
|
3002
|
-
if (response.status === 400) {
|
|
3003
|
-
const errorData2 = await response.json().catch(() => ({}));
|
|
3004
|
-
return { success: false, error: errorData2.message || "Invalid API key. Please check and try again." };
|
|
3005
|
-
}
|
|
3006
|
-
const errorData = await response.json().catch(() => ({}));
|
|
3007
|
-
return { success: false, error: errorData.message || `API error: ${response.status} ${response.statusText}` };
|
|
3008
|
-
}
|
|
3009
|
-
return { success: true };
|
|
3010
|
-
} catch (error) {
|
|
3011
|
-
if (error instanceof Error) {
|
|
3012
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
3013
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
3014
|
-
}
|
|
3015
|
-
return { success: false, error: error.message };
|
|
3016
|
-
}
|
|
3017
|
-
return { success: false, error: "Unknown error occurred" };
|
|
3018
|
-
}
|
|
3019
|
-
}
|
|
3020
|
-
async function removeApiKey(provider) {
|
|
3021
|
-
const token = await getToken();
|
|
3022
|
-
if (!token) {
|
|
3023
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
3024
|
-
}
|
|
3025
|
-
try {
|
|
3026
|
-
const response = await fetch(`${apiUrl}/company/api-keys/${provider}`, {
|
|
3027
|
-
method: "DELETE",
|
|
3028
|
-
headers: {
|
|
3029
|
-
"Content-Type": "application/json",
|
|
3030
|
-
Authorization: `Bearer ${token}`
|
|
3031
|
-
}
|
|
3032
|
-
});
|
|
3033
|
-
if (!response.ok) {
|
|
3034
|
-
if (response.status === 401) {
|
|
3035
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
3036
|
-
}
|
|
3037
|
-
if (response.status === 403) {
|
|
3038
|
-
return { success: false, error: isAdminRequired() };
|
|
3039
|
-
}
|
|
3040
|
-
if (response.status === 404) {
|
|
3041
|
-
return { success: false, error: `No API key configured for ${provider}.` };
|
|
3042
|
-
}
|
|
3043
|
-
const errorData = await response.json().catch(() => ({}));
|
|
3044
|
-
return { success: false, error: errorData.message || `API error: ${response.status} ${response.statusText}` };
|
|
3045
|
-
}
|
|
3046
|
-
return { success: true };
|
|
3047
|
-
} catch (error) {
|
|
3048
|
-
if (error instanceof Error) {
|
|
3049
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
3050
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
3051
|
-
}
|
|
3052
|
-
return { success: false, error: error.message };
|
|
3053
|
-
}
|
|
3054
|
-
return { success: false, error: "Unknown error occurred" };
|
|
3055
|
-
}
|
|
3056
|
-
}
|
|
3057
|
-
function isValidProvider(provider) {
|
|
3058
|
-
return API_KEY_PROVIDERS.includes(provider);
|
|
3059
|
-
}
|
|
3060
|
-
async function getApiKeysStatus() {
|
|
3061
|
-
const result = await listApiKeys();
|
|
3062
|
-
if (!result.success) {
|
|
3063
|
-
return { success: false, error: result.error };
|
|
3064
|
-
}
|
|
3065
|
-
const configuredProviders = (result.data || []).map((k) => k.provider);
|
|
3066
|
-
const configured = API_KEY_PROVIDERS.filter((p) => configuredProviders.includes(p));
|
|
3067
|
-
const missing = API_KEY_PROVIDERS.filter((p) => !configuredProviders.includes(p));
|
|
3068
|
-
return { success: true, data: { configured, missing } };
|
|
3069
|
-
}
|
|
3070
|
-
|
|
3071
|
-
// apps/cli/src/commands/setup/index.ts
|
|
3072
|
-
function openBrowser3(url) {
|
|
3073
|
-
const platform = process.platform;
|
|
3074
|
-
let command;
|
|
3075
|
-
if (platform === "darwin") {
|
|
3076
|
-
command = `open "${url}"`;
|
|
3077
|
-
} else if (platform === "win32") {
|
|
3078
|
-
command = `start "" "${url}"`;
|
|
3079
|
-
} else {
|
|
3080
|
-
command = `xdg-open "${url}"`;
|
|
3081
|
-
}
|
|
3082
|
-
(0, import_child_process3.exec)(command, (error) => {
|
|
3083
|
-
if (error) {
|
|
3084
|
-
console.error("Could not open browser automatically.");
|
|
3085
|
-
console.log(`Please open this URL manually:
|
|
3086
|
-
${url}`);
|
|
3087
|
-
}
|
|
3088
|
-
});
|
|
3089
|
-
}
|
|
3090
|
-
function prompt(question) {
|
|
3091
|
-
const rl = readline2.createInterface({
|
|
3092
|
-
input: process.stdin,
|
|
3093
|
-
output: process.stdout
|
|
3094
|
-
});
|
|
3095
|
-
return new Promise((resolve) => {
|
|
3096
|
-
rl.question(question, (answer) => {
|
|
3097
|
-
rl.close();
|
|
3098
|
-
resolve(answer.trim());
|
|
3099
|
-
});
|
|
3100
|
-
});
|
|
3101
|
-
}
|
|
3102
|
-
function promptYesNo(question) {
|
|
3103
|
-
return prompt(`${question} (y/n): `).then((answer) => answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
3104
|
-
}
|
|
3105
|
-
async function handleCalendarStatus(options) {
|
|
3106
|
-
const ctx = createOutputContext(options);
|
|
3107
|
-
const authState = await getAuthState();
|
|
3108
|
-
if (!authState.isAuthenticated) {
|
|
3109
|
-
return outputAuthRequired(ctx);
|
|
3110
|
-
}
|
|
3111
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
3112
|
-
if (!scopeResult.success || !scopeResult.data) {
|
|
3113
|
-
return outputError(ctx, scopeResult.error || "Failed to check calendar status", {
|
|
3114
|
-
code: "CALENDAR_STATUS_ERROR",
|
|
3115
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3116
|
-
});
|
|
3117
|
-
}
|
|
3118
|
-
const { hasGoogleAccount, hasCalendarScope, googleEmail } = scopeResult.data;
|
|
3119
|
-
if (ctx.json) {
|
|
3120
|
-
const calendarsResult = hasCalendarScope ? await listCalendars() : { success: false };
|
|
3121
|
-
const selectedCalendars = calendarsResult.success && calendarsResult.data ? calendarsResult.data.filter((c) => c.selected) : [];
|
|
3122
|
-
outputSuccess(
|
|
3123
|
-
ctx,
|
|
3124
|
-
{
|
|
3125
|
-
isConnected: hasGoogleAccount && hasCalendarScope,
|
|
3126
|
-
hasGoogleAccount,
|
|
3127
|
-
hasCalendarScope,
|
|
3128
|
-
email: googleEmail,
|
|
3129
|
-
selectedCalendars: selectedCalendars.map((c) => ({ id: c.id, name: c.name }))
|
|
3130
|
-
},
|
|
3131
|
-
noop
|
|
3132
|
-
);
|
|
3133
|
-
return;
|
|
3134
|
-
}
|
|
3135
|
-
console.log("\nCalendar Connection Status");
|
|
3136
|
-
console.log("\u2500".repeat(40));
|
|
3137
|
-
if (!hasGoogleAccount) {
|
|
3138
|
-
console.log("Status: Not connected");
|
|
3139
|
-
console.log("\nNo Google account linked to your Indigo account.");
|
|
3140
|
-
console.log("\nTo connect your calendar, run:");
|
|
3141
|
-
console.log(" indigo setup calendar");
|
|
3142
|
-
} else if (!hasCalendarScope) {
|
|
3143
|
-
console.log("Status: Partial");
|
|
3144
|
-
console.log(`Account: ${googleEmail}`);
|
|
3145
|
-
console.log("\nGoogle account linked but calendar permission not granted.");
|
|
3146
|
-
console.log("\nTo grant calendar access, run:");
|
|
3147
|
-
console.log(" indigo setup calendar");
|
|
3148
|
-
} else {
|
|
3149
|
-
console.log("Status: Connected");
|
|
3150
|
-
console.log(`Account: ${googleEmail}`);
|
|
3151
|
-
const calendarsResult = await listCalendars();
|
|
3152
|
-
if (calendarsResult.success && calendarsResult.data) {
|
|
3153
|
-
const selected = calendarsResult.data.filter((c) => c.selected);
|
|
3154
|
-
if (selected.length > 0) {
|
|
3155
|
-
console.log("\nSelected calendars:");
|
|
3156
|
-
for (const cal of selected) {
|
|
3157
|
-
console.log(` \u2022 ${cal.name}${cal.primary ? " (primary)" : ""}`);
|
|
3158
|
-
}
|
|
3159
|
-
} else {
|
|
3160
|
-
console.log("\nNo calendars selected for sync.");
|
|
3161
|
-
console.log('Run "indigo setup calendar --select" to choose calendars.');
|
|
3162
|
-
}
|
|
3163
|
-
}
|
|
3164
|
-
console.log("\nTo disconnect, run:");
|
|
3165
|
-
console.log(" indigo setup calendar --disconnect");
|
|
3166
|
-
}
|
|
3167
|
-
console.log("");
|
|
3168
|
-
}
|
|
3169
|
-
async function handleCalendarConnect(options) {
|
|
3170
|
-
const ctx = createOutputContext(options);
|
|
3171
|
-
const authState = await getAuthState();
|
|
3172
|
-
if (!authState.isAuthenticated) {
|
|
3173
|
-
return outputAuthRequired(ctx);
|
|
3174
|
-
}
|
|
3175
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
3176
|
-
if (!scopeResult.success || !scopeResult.data) {
|
|
3177
|
-
return outputError(ctx, scopeResult.error || "Failed to check calendar status", {
|
|
3178
|
-
code: "CALENDAR_STATUS_ERROR",
|
|
3179
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3180
|
-
});
|
|
3181
|
-
}
|
|
3182
|
-
const { hasGoogleAccount, hasCalendarScope, googleEmail } = scopeResult.data;
|
|
3183
|
-
if (hasGoogleAccount && hasCalendarScope) {
|
|
3184
|
-
if (ctx.json) {
|
|
3185
|
-
outputSuccess(
|
|
3186
|
-
ctx,
|
|
3187
|
-
{
|
|
3188
|
-
status: "already_connected",
|
|
3189
|
-
email: googleEmail,
|
|
3190
|
-
message: "Calendar is already connected."
|
|
3191
|
-
},
|
|
3192
|
-
noop
|
|
3193
|
-
);
|
|
3194
|
-
} else {
|
|
3195
|
-
console.log(`
|
|
3196
|
-
Google Calendar already connected (${googleEmail})`);
|
|
3197
|
-
console.log("\nTo check status: indigo setup calendar --status");
|
|
3198
|
-
console.log("To disconnect: indigo setup calendar --disconnect");
|
|
3199
|
-
}
|
|
3200
|
-
return;
|
|
3201
|
-
}
|
|
3202
|
-
const urlResult = await getConnectUrl();
|
|
3203
|
-
if (!urlResult.success || !urlResult.data?.connectUrl) {
|
|
3204
|
-
return outputError(ctx, urlResult.error || "Failed to get calendar connect URL", {
|
|
3205
|
-
code: "CALENDAR_CONNECT_ERROR",
|
|
3206
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3207
|
-
});
|
|
3208
|
-
}
|
|
3209
|
-
const connectUrl = urlResult.data.connectUrl;
|
|
3210
|
-
if (ctx.json) {
|
|
3211
|
-
outputSuccess(
|
|
3212
|
-
ctx,
|
|
3213
|
-
{
|
|
3214
|
-
status: "connect_required",
|
|
3215
|
-
connectUrl,
|
|
3216
|
-
message: "Open the URL to authorize Google Calendar access."
|
|
3217
|
-
},
|
|
3218
|
-
noop
|
|
3219
|
-
);
|
|
3220
|
-
return;
|
|
3221
|
-
}
|
|
3222
|
-
console.log("\nGoogle Calendar Connection");
|
|
3223
|
-
console.log("\u2500".repeat(40));
|
|
3224
|
-
console.log("\nTo connect your Google Calendar, you need to authorize access in your browser.");
|
|
3225
|
-
console.log("\nOpening browser for authorization...\n");
|
|
3226
|
-
openBrowser3(connectUrl);
|
|
3227
|
-
console.log("If the browser did not open, visit this URL:");
|
|
3228
|
-
console.log(`
|
|
3229
|
-
${connectUrl}
|
|
3230
|
-
`);
|
|
3231
|
-
await prompt("Press Enter after completing authorization in the browser...");
|
|
3232
|
-
console.log("\nVerifying connection...");
|
|
3233
|
-
const verifyResult = await getCalendarScopeStatus();
|
|
3234
|
-
if (verifyResult.success && verifyResult.data?.hasCalendarScope) {
|
|
3235
|
-
console.log("\n\u2713 Calendar connected successfully!");
|
|
3236
|
-
console.log(` Account: ${verifyResult.data.googleEmail}`);
|
|
3237
|
-
const shouldSelect = await promptYesNo("\nWould you like to select calendars to sync now?");
|
|
3238
|
-
if (shouldSelect) {
|
|
3239
|
-
await handleCalendarSelect({ json: false });
|
|
3240
|
-
} else {
|
|
3241
|
-
console.log("\nYou can select calendars later with:");
|
|
3242
|
-
console.log(" indigo setup calendar --select");
|
|
3243
|
-
}
|
|
3244
|
-
} else {
|
|
3245
|
-
console.log("\n\u2717 Calendar connection could not be verified.");
|
|
3246
|
-
console.log(" Please try again or check that you granted calendar permissions.");
|
|
3247
|
-
console.log("\nTo retry: indigo setup calendar");
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
async function handleCalendarSelect(options) {
|
|
3251
|
-
const ctx = createOutputContext(options);
|
|
3252
|
-
const authState = await getAuthState();
|
|
3253
|
-
if (!authState.isAuthenticated) {
|
|
3254
|
-
return outputAuthRequired(ctx);
|
|
3255
|
-
}
|
|
3256
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
3257
|
-
if (!scopeResult.success || !scopeResult.data) {
|
|
3258
|
-
return outputError(ctx, scopeResult.error || "Failed to check calendar status", {
|
|
3259
|
-
code: "CALENDAR_STATUS_ERROR",
|
|
3260
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3261
|
-
});
|
|
3262
|
-
}
|
|
3263
|
-
if (!scopeResult.data.hasCalendarScope) {
|
|
3264
|
-
return outputError(ctx, 'Calendar not connected. Run "indigo setup calendar" first.', {
|
|
3265
|
-
code: "CALENDAR_NOT_CONNECTED",
|
|
3266
|
-
exitCode: ExitCode.CONFIGURATION_ERROR
|
|
3267
|
-
});
|
|
3268
|
-
}
|
|
3269
|
-
const calendarsResult = await listCalendars();
|
|
3270
|
-
if (!calendarsResult.success || !calendarsResult.data) {
|
|
3271
|
-
return outputError(ctx, calendarsResult.error || "Failed to fetch calendars", {
|
|
3272
|
-
code: "CALENDAR_FETCH_ERROR",
|
|
3273
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3274
|
-
});
|
|
3275
|
-
}
|
|
3276
|
-
const calendars = calendarsResult.data;
|
|
3277
|
-
if (ctx.json) {
|
|
3278
|
-
outputSuccess(
|
|
3279
|
-
ctx,
|
|
3280
|
-
{
|
|
3281
|
-
calendars: calendars.map((c) => ({
|
|
3282
|
-
id: c.id,
|
|
3283
|
-
name: c.name,
|
|
3284
|
-
accessRole: c.accessRole,
|
|
3285
|
-
primary: c.primary,
|
|
3286
|
-
selected: c.selected
|
|
3287
|
-
}))
|
|
3288
|
-
},
|
|
3289
|
-
noop
|
|
3290
|
-
);
|
|
3291
|
-
return;
|
|
3292
|
-
}
|
|
3293
|
-
console.log("\nAvailable Calendars");
|
|
3294
|
-
console.log("\u2500".repeat(40));
|
|
3295
|
-
if (calendars.length === 0) {
|
|
3296
|
-
console.log("No calendars found in your Google account.");
|
|
3297
|
-
return;
|
|
3298
|
-
}
|
|
3299
|
-
calendars.forEach((cal, index) => {
|
|
3300
|
-
const selected = cal.selected ? "[\u2713]" : "[ ]";
|
|
3301
|
-
const primary = cal.primary ? " (primary)" : "";
|
|
3302
|
-
console.log(` ${index + 1}. ${selected} ${cal.name}${primary}`);
|
|
3303
|
-
});
|
|
3304
|
-
console.log("\nEnter calendar numbers to toggle selection (comma-separated)");
|
|
3305
|
-
console.log("Example: 1,3,5 or just press Enter to keep current selection");
|
|
3306
|
-
const input = await prompt("\nSelect calendars: ");
|
|
3307
|
-
if (!input) {
|
|
3308
|
-
console.log("\nKeeping current selection.");
|
|
3309
|
-
return;
|
|
3310
|
-
}
|
|
3311
|
-
const numbers = input.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));
|
|
3312
|
-
const updatedCalendars = calendars.map((cal, index) => {
|
|
3313
|
-
const shouldToggle = numbers.includes(index + 1);
|
|
3314
|
-
return {
|
|
3315
|
-
...cal,
|
|
3316
|
-
selected: shouldToggle ? !cal.selected : cal.selected
|
|
3317
|
-
};
|
|
3318
|
-
});
|
|
3319
|
-
const selectedCalendars = updatedCalendars.filter((c) => c.selected).map((c) => ({ id: c.id, name: c.name, accessRole: c.accessRole }));
|
|
3320
|
-
if (selectedCalendars.length === 0) {
|
|
3321
|
-
const confirm = await promptYesNo("No calendars selected. This will disable calendar sync. Continue?");
|
|
3322
|
-
if (!confirm) {
|
|
3323
|
-
console.log("Selection cancelled.");
|
|
3324
|
-
return;
|
|
3325
|
-
}
|
|
3326
|
-
}
|
|
3327
|
-
logHuman(ctx, "\nSaving calendar selection...");
|
|
3328
|
-
const saveResult = await selectCalendars(selectedCalendars);
|
|
3329
|
-
if (!saveResult.success) {
|
|
3330
|
-
return outputError(ctx, saveResult.error || "Failed to save calendar selection", {
|
|
3331
|
-
code: "CALENDAR_SAVE_ERROR",
|
|
3332
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3333
|
-
});
|
|
3334
|
-
}
|
|
3335
|
-
console.log("\n\u2713 Calendar selection saved!");
|
|
3336
|
-
console.log("\nSelected calendars:");
|
|
3337
|
-
for (const cal of selectedCalendars) {
|
|
3338
|
-
console.log(` \u2022 ${cal.name}`);
|
|
3339
|
-
}
|
|
3340
|
-
}
|
|
3341
|
-
async function handleCalendarDisconnect(options) {
|
|
3342
|
-
const ctx = createOutputContext(options);
|
|
3343
|
-
const authState = await getAuthState();
|
|
3344
|
-
if (!authState.isAuthenticated) {
|
|
3345
|
-
return outputAuthRequired(ctx);
|
|
3346
|
-
}
|
|
3347
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
3348
|
-
if (!scopeResult.success || !scopeResult.data) {
|
|
3349
|
-
return outputError(ctx, scopeResult.error || "Failed to check calendar status", {
|
|
3350
|
-
code: "CALENDAR_STATUS_ERROR",
|
|
3351
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3352
|
-
});
|
|
3353
|
-
}
|
|
3354
|
-
if (!scopeResult.data.hasCalendarScope) {
|
|
3355
|
-
if (ctx.json) {
|
|
3356
|
-
outputSuccess(
|
|
3357
|
-
ctx,
|
|
3358
|
-
{
|
|
3359
|
-
status: "not_connected",
|
|
3360
|
-
message: "Calendar is not connected."
|
|
3361
|
-
},
|
|
3362
|
-
noop
|
|
3363
|
-
);
|
|
3364
|
-
} else {
|
|
3365
|
-
console.log("\nCalendar is not connected. Nothing to disconnect.");
|
|
3366
|
-
}
|
|
3367
|
-
return;
|
|
3368
|
-
}
|
|
3369
|
-
const email = scopeResult.data.googleEmail;
|
|
3370
|
-
if (!ctx.json) {
|
|
3371
|
-
const confirm = await promptYesNo(`
|
|
3372
|
-
Disconnect Google Calendar (${email})?`);
|
|
3373
|
-
if (!confirm) {
|
|
3374
|
-
console.log("Disconnect cancelled.");
|
|
3375
|
-
return;
|
|
3376
|
-
}
|
|
3377
|
-
console.log("\nDisconnecting...");
|
|
3378
|
-
}
|
|
3379
|
-
const disconnectResult = await disconnectCalendar();
|
|
3380
|
-
if (!disconnectResult.success) {
|
|
3381
|
-
return outputError(ctx, disconnectResult.error || "Failed to disconnect calendar", {
|
|
3382
|
-
code: "CALENDAR_DISCONNECT_ERROR",
|
|
3383
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3384
|
-
});
|
|
3385
|
-
}
|
|
3386
|
-
if (ctx.json) {
|
|
3387
|
-
outputSuccess(
|
|
3388
|
-
ctx,
|
|
3389
|
-
{
|
|
3390
|
-
status: "disconnected",
|
|
3391
|
-
previousEmail: email,
|
|
3392
|
-
message: "Calendar disconnected successfully."
|
|
3393
|
-
},
|
|
3394
|
-
noop
|
|
3395
|
-
);
|
|
3396
|
-
} else {
|
|
3397
|
-
console.log("\n\u2713 Calendar disconnected successfully.");
|
|
3398
|
-
console.log("\nTo reconnect, run:");
|
|
3399
|
-
console.log(" indigo setup calendar");
|
|
3400
|
-
}
|
|
3401
|
-
}
|
|
3402
|
-
function printWizardStep(step) {
|
|
3403
|
-
console.log(`
|
|
3404
|
-
${"\u2550".repeat(50)}`);
|
|
3405
|
-
console.log(` Step ${step.number} of ${step.total}: ${step.name}`);
|
|
3406
|
-
console.log(`${"\u2550".repeat(50)}`);
|
|
3407
|
-
console.log(`
|
|
3408
|
-
${step.description}
|
|
3409
|
-
`);
|
|
3410
|
-
}
|
|
3411
|
-
function printProgressBar(configured, total) {
|
|
3412
|
-
const filled = Math.round(configured / total * 20);
|
|
3413
|
-
const empty = 20 - filled;
|
|
3414
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
3415
|
-
const percent = Math.round(configured / total * 100);
|
|
3416
|
-
console.log(` Progress: [${bar}] ${percent}%`);
|
|
3417
|
-
}
|
|
3418
|
-
async function getSetupStatus() {
|
|
3419
|
-
const authState = await getAuthState();
|
|
3420
|
-
const authentication = {
|
|
3421
|
-
isConfigured: authState.isAuthenticated,
|
|
3422
|
-
user: authState.user,
|
|
3423
|
-
credentialSource: authState.credentialSource
|
|
3424
|
-
};
|
|
3425
|
-
let apiKeys = {
|
|
3426
|
-
isConfigured: false,
|
|
3427
|
-
configured: [],
|
|
3428
|
-
missing: API_KEY_PROVIDERS
|
|
3429
|
-
};
|
|
3430
|
-
if (authState.isAuthenticated) {
|
|
3431
|
-
const keysResult = await getApiKeysStatus();
|
|
3432
|
-
if (keysResult.success && keysResult.data) {
|
|
3433
|
-
apiKeys = {
|
|
3434
|
-
isConfigured: keysResult.data.configured.length > 0,
|
|
3435
|
-
configured: keysResult.data.configured,
|
|
3436
|
-
missing: keysResult.data.missing
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
}
|
|
3440
|
-
let calendar = {
|
|
3441
|
-
isConfigured: false
|
|
3442
|
-
};
|
|
3443
|
-
if (authState.isAuthenticated) {
|
|
3444
|
-
const calendarResult = await getCalendarConnectionStatus();
|
|
3445
|
-
if (calendarResult.success && calendarResult.data) {
|
|
3446
|
-
calendar = {
|
|
3447
|
-
isConfigured: calendarResult.data.isConnected,
|
|
3448
|
-
email: calendarResult.data.email,
|
|
3449
|
-
selectedCalendars: calendarResult.data.selectedCalendars
|
|
3450
|
-
};
|
|
3451
|
-
}
|
|
3452
|
-
}
|
|
3453
|
-
return { authentication, apiKeys, calendar };
|
|
3454
|
-
}
|
|
3455
|
-
async function handleSetupStatus(options) {
|
|
3456
|
-
const ctx = createOutputContext(options);
|
|
3457
|
-
if (!ctx.json) {
|
|
3458
|
-
console.log("\nChecking configuration status...\n");
|
|
3459
|
-
}
|
|
3460
|
-
const status = await getSetupStatus();
|
|
3461
|
-
if (ctx.json) {
|
|
3462
|
-
outputSuccess(
|
|
3463
|
-
ctx,
|
|
3464
|
-
{
|
|
3465
|
-
authentication: {
|
|
3466
|
-
isConfigured: status.authentication.isConfigured,
|
|
3467
|
-
user: status.authentication.user ? {
|
|
3468
|
-
id: status.authentication.user.id,
|
|
3469
|
-
email: status.authentication.user.email,
|
|
3470
|
-
name: status.authentication.user.name
|
|
3471
|
-
} : void 0,
|
|
3472
|
-
credentialSource: status.authentication.credentialSource
|
|
3473
|
-
},
|
|
3474
|
-
apiKeys: status.apiKeys,
|
|
3475
|
-
calendar: status.calendar,
|
|
3476
|
-
overallProgress: {
|
|
3477
|
-
configured: [
|
|
3478
|
-
status.authentication.isConfigured,
|
|
3479
|
-
status.apiKeys.isConfigured,
|
|
3480
|
-
status.calendar.isConfigured
|
|
3481
|
-
].filter(Boolean).length,
|
|
3482
|
-
total: 3
|
|
3483
|
-
}
|
|
3484
|
-
},
|
|
3485
|
-
noop
|
|
3486
|
-
);
|
|
3487
|
-
return;
|
|
3488
|
-
}
|
|
3489
|
-
console.log("Indigo Setup Status");
|
|
3490
|
-
console.log("\u2500".repeat(50));
|
|
3491
|
-
const configuredCount = [
|
|
3492
|
-
status.authentication.isConfigured,
|
|
3493
|
-
status.apiKeys.isConfigured,
|
|
3494
|
-
status.calendar.isConfigured
|
|
3495
|
-
].filter(Boolean).length;
|
|
3496
|
-
printProgressBar(configuredCount, 3);
|
|
3497
|
-
console.log("");
|
|
3498
|
-
const authIcon = status.authentication.isConfigured ? "\u2713" : "\u25CB";
|
|
3499
|
-
const authStatus = status.authentication.isConfigured ? "Configured" : "Not configured";
|
|
3500
|
-
console.log(`
|
|
3501
|
-
${authIcon} Authentication: ${authStatus}`);
|
|
3502
|
-
if (status.authentication.isConfigured && status.authentication.user) {
|
|
3503
|
-
const userDisplay = status.authentication.user.email || status.authentication.user.name || status.authentication.user.id;
|
|
3504
|
-
const sourceDisplay = status.authentication.credentialSource === "electron" ? " (via desktop app)" : "";
|
|
3505
|
-
console.log(` User: ${userDisplay}${sourceDisplay}`);
|
|
3506
|
-
} else {
|
|
3507
|
-
console.log(" Run: indigo auth login");
|
|
3508
|
-
}
|
|
3509
|
-
const keysIcon = status.apiKeys.isConfigured ? "\u2713" : "\u25CB";
|
|
3510
|
-
const keysStatus = status.apiKeys.isConfigured ? `Configured (${status.apiKeys.configured.length} provider${status.apiKeys.configured.length !== 1 ? "s" : ""})` : "Not configured";
|
|
3511
|
-
console.log(`
|
|
3512
|
-
${keysIcon} API Keys (BYOK): ${keysStatus}`);
|
|
3513
|
-
if (status.apiKeys.isConfigured) {
|
|
3514
|
-
for (const provider of status.apiKeys.configured) {
|
|
3515
|
-
const info = PROVIDER_INFO[provider];
|
|
3516
|
-
console.log(` \u2022 ${info?.displayName || provider}`);
|
|
3517
|
-
}
|
|
3518
|
-
}
|
|
3519
|
-
if (status.apiKeys.missing.length > 0 && status.apiKeys.missing.length < API_KEY_PROVIDERS.length) {
|
|
3520
|
-
console.log(` Missing: ${status.apiKeys.missing.map((p) => PROVIDER_INFO[p]?.displayName || p).join(", ")}`);
|
|
3521
|
-
}
|
|
3522
|
-
if (!status.apiKeys.isConfigured) {
|
|
3523
|
-
console.log(" Run: indigo setup keys");
|
|
3524
|
-
}
|
|
3525
|
-
const calIcon = status.calendar.isConfigured ? "\u2713" : "\u25CB";
|
|
3526
|
-
const calStatus = status.calendar.isConfigured ? "Connected" : "Not connected";
|
|
3527
|
-
console.log(`
|
|
3528
|
-
${calIcon} Calendar: ${calStatus}`);
|
|
3529
|
-
if (status.calendar.isConfigured) {
|
|
3530
|
-
if (status.calendar.email) {
|
|
3531
|
-
console.log(` Account: ${status.calendar.email}`);
|
|
3532
|
-
}
|
|
3533
|
-
if (status.calendar.selectedCalendars && status.calendar.selectedCalendars.length > 0) {
|
|
3534
|
-
console.log(` Calendars: ${status.calendar.selectedCalendars.length} selected`);
|
|
3535
|
-
}
|
|
3536
|
-
} else {
|
|
3537
|
-
console.log(" Run: indigo setup calendar");
|
|
3538
|
-
}
|
|
3539
|
-
console.log("\n" + "\u2500".repeat(50));
|
|
3540
|
-
if (configuredCount === 3) {
|
|
3541
|
-
console.log("\n\u2713 All setup complete! You're ready to use Indigo.");
|
|
3542
|
-
console.log("\nTry these commands:");
|
|
3543
|
-
console.log(" indigo signals list View your signals");
|
|
3544
|
-
console.log(" indigo meetings list View your meetings");
|
|
3545
|
-
console.log(" indigo chat start Start a chat session");
|
|
3546
|
-
} else {
|
|
3547
|
-
console.log(`
|
|
3548
|
-
${configuredCount}/3 steps configured.`);
|
|
3549
|
-
console.log("\nTo complete setup, run:");
|
|
3550
|
-
console.log(" indigo setup");
|
|
3551
|
-
}
|
|
3552
|
-
console.log("");
|
|
3553
|
-
}
|
|
3554
|
-
async function handleSetupWizard(options) {
|
|
3555
|
-
const ctx = createOutputContext(options);
|
|
3556
|
-
if (ctx.json) {
|
|
3557
|
-
const status2 = await getSetupStatus();
|
|
3558
|
-
outputSuccess(
|
|
3559
|
-
ctx,
|
|
3560
|
-
{
|
|
3561
|
-
status: "interactive_required",
|
|
3562
|
-
currentStatus: {
|
|
3563
|
-
authentication: status2.authentication.isConfigured,
|
|
3564
|
-
apiKeys: status2.apiKeys.isConfigured,
|
|
3565
|
-
calendar: status2.calendar.isConfigured
|
|
3566
|
-
},
|
|
3567
|
-
message: "Interactive setup required. Run without --json flag to start the wizard."
|
|
3568
|
-
},
|
|
3569
|
-
noop
|
|
3570
|
-
);
|
|
3571
|
-
return;
|
|
3572
|
-
}
|
|
3573
|
-
console.log("\n" + "\u2550".repeat(50));
|
|
3574
|
-
console.log(" Welcome to Indigo Setup Wizard");
|
|
3575
|
-
console.log("\u2550".repeat(50));
|
|
3576
|
-
console.log("\nThis wizard will help you configure Indigo for first use.");
|
|
3577
|
-
console.log("You can skip any step and complete it later.\n");
|
|
3578
|
-
console.log("Checking current configuration...");
|
|
3579
|
-
const status = await getSetupStatus();
|
|
3580
|
-
const configuredCount = [
|
|
3581
|
-
status.authentication.isConfigured,
|
|
3582
|
-
status.apiKeys.isConfigured,
|
|
3583
|
-
status.calendar.isConfigured
|
|
3584
|
-
].filter(Boolean).length;
|
|
3585
|
-
printProgressBar(configuredCount, 3);
|
|
3586
|
-
printWizardStep({
|
|
3587
|
-
number: 1,
|
|
3588
|
-
total: 3,
|
|
3589
|
-
name: "Authentication",
|
|
3590
|
-
description: "Ensure you are logged in to your Indigo account."
|
|
3591
|
-
});
|
|
3592
|
-
if (status.authentication.isConfigured && status.authentication.user) {
|
|
3593
|
-
const userDisplay = status.authentication.user.email || status.authentication.user.name || "Unknown";
|
|
3594
|
-
const sourceDisplay = status.authentication.credentialSource === "electron" ? " (via desktop app)" : "";
|
|
3595
|
-
console.log(`\u2713 Already logged in as: ${userDisplay}${sourceDisplay}`);
|
|
3596
|
-
} else {
|
|
3597
|
-
console.log("\u25CB Not logged in.");
|
|
3598
|
-
console.log("\nYou need to log in to continue with setup.");
|
|
3599
|
-
console.log("\nTo log in, run:");
|
|
3600
|
-
console.log(" indigo auth login");
|
|
3601
|
-
console.log("\nTo create a new account, run:");
|
|
3602
|
-
console.log(" indigo account create");
|
|
3603
|
-
console.log('\nAfter logging in, run "indigo setup" again to continue.\n');
|
|
3604
|
-
process.exit(ExitCode.AUTH_REQUIRED);
|
|
3605
|
-
}
|
|
3606
|
-
printWizardStep({
|
|
3607
|
-
number: 2,
|
|
3608
|
-
total: 3,
|
|
3609
|
-
name: "API Keys (BYOK)",
|
|
3610
|
-
description: "Configure your own API keys for AI model providers.\nThis allows you to use your own billing and API quotas."
|
|
3611
|
-
});
|
|
3612
|
-
if (status.apiKeys.isConfigured) {
|
|
3613
|
-
console.log("\u2713 API keys already configured:");
|
|
3614
|
-
for (const provider of status.apiKeys.configured) {
|
|
3615
|
-
const info = PROVIDER_INFO[provider];
|
|
3616
|
-
console.log(` \u2022 ${info?.displayName || provider}`);
|
|
3617
|
-
}
|
|
3618
|
-
if (status.apiKeys.missing.length > 0) {
|
|
3619
|
-
const shouldConfigure = await promptYesNo("\nWould you like to configure additional providers?");
|
|
3620
|
-
if (shouldConfigure) {
|
|
3621
|
-
await handleKeysInteractive({ json: false });
|
|
3622
|
-
} else {
|
|
3623
|
-
console.log("Skipping API keys configuration.");
|
|
3624
|
-
}
|
|
3625
|
-
}
|
|
3626
|
-
} else {
|
|
3627
|
-
console.log("\u25CB No API keys configured yet.");
|
|
3628
|
-
console.log("\nAPI keys are optional but recommended for:");
|
|
3629
|
-
console.log(" \u2022 Using your own billing account");
|
|
3630
|
-
console.log(" \u2022 Higher rate limits");
|
|
3631
|
-
console.log(" \u2022 Choice of AI providers\n");
|
|
3632
|
-
const shouldConfigure = await promptYesNo("Would you like to configure API keys now?");
|
|
3633
|
-
if (shouldConfigure) {
|
|
3634
|
-
await handleKeysInteractive({ json: false });
|
|
3635
|
-
} else {
|
|
3636
|
-
console.log("\nSkipping API keys configuration.");
|
|
3637
|
-
console.log("You can configure them later with: indigo setup keys");
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
printWizardStep({
|
|
3641
|
-
number: 3,
|
|
3642
|
-
total: 3,
|
|
3643
|
-
name: "Calendar Connection",
|
|
3644
|
-
description: "Connect your Google Calendar to enable meeting insights.\nThis allows Indigo to analyze your meetings and provide relevant signals."
|
|
3645
|
-
});
|
|
3646
|
-
if (status.calendar.isConfigured) {
|
|
3647
|
-
console.log("\u2713 Calendar already connected:");
|
|
3648
|
-
if (status.calendar.email) {
|
|
3649
|
-
console.log(` Account: ${status.calendar.email}`);
|
|
3650
|
-
}
|
|
3651
|
-
if (status.calendar.selectedCalendars && status.calendar.selectedCalendars.length > 0) {
|
|
3652
|
-
console.log(` Calendars: ${status.calendar.selectedCalendars.length} selected`);
|
|
3653
|
-
for (const cal of status.calendar.selectedCalendars.slice(0, 3)) {
|
|
3654
|
-
console.log(` \u2022 ${cal.name}`);
|
|
3655
|
-
}
|
|
3656
|
-
if (status.calendar.selectedCalendars.length > 3) {
|
|
3657
|
-
console.log(` ... and ${status.calendar.selectedCalendars.length - 3} more`);
|
|
3658
|
-
}
|
|
3659
|
-
}
|
|
3660
|
-
const shouldReconfigure = await promptYesNo("\nWould you like to manage calendar settings?");
|
|
3661
|
-
if (shouldReconfigure) {
|
|
3662
|
-
await handleCalendarSelect({ json: false });
|
|
3663
|
-
}
|
|
3664
|
-
} else {
|
|
3665
|
-
console.log("\u25CB Calendar not connected yet.");
|
|
3666
|
-
console.log("\nConnecting your calendar enables:");
|
|
3667
|
-
console.log(" \u2022 Meeting preparation insights");
|
|
3668
|
-
console.log(" \u2022 Attendee information");
|
|
3669
|
-
console.log(" \u2022 Meeting-related signals\n");
|
|
3670
|
-
const shouldConnect = await promptYesNo("Would you like to connect your Google Calendar now?");
|
|
3671
|
-
if (shouldConnect) {
|
|
3672
|
-
await handleCalendarConnect({ json: false });
|
|
3673
|
-
} else {
|
|
3674
|
-
console.log("\nSkipping calendar connection.");
|
|
3675
|
-
console.log("You can connect it later with: indigo setup calendar");
|
|
3676
|
-
}
|
|
3677
|
-
}
|
|
3678
|
-
console.log("\n" + "\u2550".repeat(50));
|
|
3679
|
-
console.log(" Setup Complete!");
|
|
3680
|
-
console.log("\u2550".repeat(50));
|
|
3681
|
-
const finalStatus = await getSetupStatus();
|
|
3682
|
-
const finalConfigured = [
|
|
3683
|
-
finalStatus.authentication.isConfigured,
|
|
3684
|
-
finalStatus.apiKeys.isConfigured,
|
|
3685
|
-
finalStatus.calendar.isConfigured
|
|
3686
|
-
].filter(Boolean).length;
|
|
3687
|
-
console.log("");
|
|
3688
|
-
printProgressBar(finalConfigured, 3);
|
|
3689
|
-
console.log("");
|
|
3690
|
-
const authIcon = finalStatus.authentication.isConfigured ? "\u2713" : "\u25CB";
|
|
3691
|
-
const keysIcon = finalStatus.apiKeys.isConfigured ? "\u2713" : "\u25CB";
|
|
3692
|
-
const calIcon = finalStatus.calendar.isConfigured ? "\u2713" : "\u25CB";
|
|
3693
|
-
console.log(` ${authIcon} Authentication`);
|
|
3694
|
-
console.log(
|
|
3695
|
-
` ${keysIcon} API Keys${finalStatus.apiKeys.isConfigured ? ` (${finalStatus.apiKeys.configured.length} providers)` : ""}`
|
|
3696
|
-
);
|
|
3697
|
-
console.log(` ${calIcon} Calendar${finalStatus.calendar.isConfigured ? " (connected)" : ""}`);
|
|
3698
|
-
console.log("\n" + "\u2500".repeat(50));
|
|
3699
|
-
if (finalConfigured === 3) {
|
|
3700
|
-
console.log("\nYou're all set! Try these commands to get started:");
|
|
3701
|
-
} else {
|
|
3702
|
-
console.log(`
|
|
3703
|
-
${finalConfigured}/3 steps configured. To complete remaining steps:`);
|
|
3704
|
-
if (!finalStatus.apiKeys.isConfigured) {
|
|
3705
|
-
console.log(" indigo setup keys Configure API keys");
|
|
3706
|
-
}
|
|
3707
|
-
if (!finalStatus.calendar.isConfigured) {
|
|
3708
|
-
console.log(" indigo setup calendar Connect your calendar");
|
|
3709
|
-
}
|
|
3710
|
-
console.log("\nOr try these commands:");
|
|
3711
|
-
}
|
|
3712
|
-
console.log(" indigo signals list View your signals");
|
|
3713
|
-
console.log(" indigo meetings list View your meetings");
|
|
3714
|
-
console.log(" indigo chat start Start a chat session");
|
|
3715
|
-
console.log("\nTo check status anytime: indigo setup status");
|
|
3716
|
-
console.log("");
|
|
3717
|
-
}
|
|
3718
|
-
function createSetupCommand() {
|
|
3719
|
-
const setup = new import_commander5.Command("setup").description("Setup and configuration commands").action(async () => {
|
|
3720
|
-
await handleSetupWizard({ json: false });
|
|
3721
|
-
});
|
|
3722
|
-
setup.command("status").description("Show current setup configuration status").option("--json", "Output as JSON for scripting").action(async (options) => {
|
|
3723
|
-
await handleSetupStatus(options);
|
|
3724
|
-
});
|
|
3725
|
-
setup.command("wizard").description("Start the interactive setup wizard").option("--json", "Output current status as JSON (non-interactive)").action(async (options) => {
|
|
3726
|
-
await handleSetupWizard(options);
|
|
3727
|
-
});
|
|
3728
|
-
setup.addHelpText(
|
|
3729
|
-
"after",
|
|
3730
|
-
`
|
|
3731
|
-
Examples:
|
|
3732
|
-
$ indigo setup Start interactive setup wizard
|
|
3733
|
-
$ indigo setup status Show current configuration status
|
|
3734
|
-
$ indigo setup status --json Get configuration status as JSON
|
|
3735
|
-
$ indigo setup calendar Connect Google Calendar
|
|
3736
|
-
$ indigo setup keys Configure API keys
|
|
3737
|
-
|
|
3738
|
-
The setup wizard walks you through:
|
|
3739
|
-
1. Authentication - Verify you're logged in
|
|
3740
|
-
2. API Keys - Configure your own AI provider keys (optional)
|
|
3741
|
-
3. Calendar - Connect your Google Calendar (optional)
|
|
3742
|
-
|
|
3743
|
-
Each step can be skipped and completed later using individual commands.
|
|
3744
|
-
`
|
|
3745
|
-
);
|
|
3746
|
-
const calendar = setup.command("calendar").description("Connect and manage Google Calendar integration").option("--json", "Output as JSON for scripting").option("--status", "Show current calendar connection status").option("--select", "Select which calendars to sync").option("--disconnect", "Disconnect Google Calendar").action(async (options) => {
|
|
3747
|
-
if (options.status) {
|
|
3748
|
-
await handleCalendarStatus(options);
|
|
3749
|
-
} else if (options.disconnect) {
|
|
3750
|
-
await handleCalendarDisconnect(options);
|
|
3751
|
-
} else if (options.select) {
|
|
3752
|
-
await handleCalendarSelect(options);
|
|
3753
|
-
} else {
|
|
3754
|
-
await handleCalendarConnect(options);
|
|
3755
|
-
}
|
|
3756
|
-
});
|
|
3757
|
-
calendar.addHelpText(
|
|
3758
|
-
"after",
|
|
3759
|
-
`
|
|
3760
|
-
Examples:
|
|
3761
|
-
$ indigo setup calendar Connect Google Calendar
|
|
3762
|
-
$ indigo setup calendar --status Check connection status
|
|
3763
|
-
$ indigo setup calendar --select Choose calendars to sync
|
|
3764
|
-
$ indigo setup calendar --disconnect Disconnect Google Calendar
|
|
3765
|
-
$ indigo setup calendar --json Output as JSON for scripting
|
|
3766
|
-
|
|
3767
|
-
Note: Only Google Calendar is supported at this time.
|
|
3768
|
-
`
|
|
3769
|
-
);
|
|
3770
|
-
const keys = setup.command("keys").description("Configure API keys for AI model providers (BYOK)").option("--json", "Output as JSON for scripting").option("--list", "List configured API key providers").option("-p, --provider <provider>", "Configure a specific provider (openai, anthropic, google, xai)").option("--remove <provider>", "Remove API key for a provider").action(async (options) => {
|
|
3771
|
-
if (options.list) {
|
|
3772
|
-
await handleKeysList(options);
|
|
3773
|
-
} else if (options.remove) {
|
|
3774
|
-
await handleKeysRemove(options.remove, options);
|
|
3775
|
-
} else if (options.provider) {
|
|
3776
|
-
await handleKeysConfigure(options.provider, options);
|
|
3777
|
-
} else {
|
|
3778
|
-
await handleKeysInteractive(options);
|
|
3779
|
-
}
|
|
3780
|
-
});
|
|
3781
|
-
keys.addHelpText(
|
|
3782
|
-
"after",
|
|
3783
|
-
`
|
|
3784
|
-
Examples:
|
|
3785
|
-
$ indigo setup keys Interactive setup for all providers
|
|
3786
|
-
$ indigo setup keys --list Show which providers are configured
|
|
3787
|
-
$ indigo setup keys --provider openai Configure OpenAI API key
|
|
3788
|
-
$ indigo setup keys --remove openai Remove OpenAI API key
|
|
3789
|
-
$ indigo setup keys --json Output as JSON for scripting
|
|
3790
|
-
|
|
3791
|
-
Supported providers:
|
|
3792
|
-
openai - GPT-4, GPT-4o and other OpenAI models
|
|
3793
|
-
anthropic - Claude models (Claude 3, Claude 3.5, etc.)
|
|
3794
|
-
google - Gemini models
|
|
3795
|
-
xai - Grok models
|
|
3796
|
-
|
|
3797
|
-
Note: API key management requires admin or owner permissions.
|
|
3798
|
-
`
|
|
3799
|
-
);
|
|
3800
|
-
return setup;
|
|
3801
|
-
}
|
|
3802
|
-
async function handleKeysList(options) {
|
|
3803
|
-
const ctx = createOutputContext(options);
|
|
3804
|
-
const authState = await getAuthState();
|
|
3805
|
-
if (!authState.isAuthenticated) {
|
|
3806
|
-
return outputAuthRequired(ctx);
|
|
3807
|
-
}
|
|
3808
|
-
const result = await listApiKeys();
|
|
3809
|
-
if (!result.success || !result.data) {
|
|
3810
|
-
return outputError(ctx, result.error || "Failed to list API keys", {
|
|
3811
|
-
code: "API_KEYS_ERROR",
|
|
3812
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3813
|
-
});
|
|
3814
|
-
}
|
|
3815
|
-
const keys = result.data;
|
|
3816
|
-
if (ctx.json) {
|
|
3817
|
-
outputSuccess(
|
|
3818
|
-
ctx,
|
|
3819
|
-
{
|
|
3820
|
-
providers: keys.map((k) => ({
|
|
3821
|
-
provider: k.provider,
|
|
3822
|
-
displayName: PROVIDER_INFO[k.provider]?.displayName || k.provider,
|
|
3823
|
-
isValid: k.isValid,
|
|
3824
|
-
lastUsedAt: k.lastUsedAt,
|
|
3825
|
-
lastValidatedAt: k.lastValidatedAt,
|
|
3826
|
-
createdAt: k.createdAt,
|
|
3827
|
-
createdBy: k.createdBy ? `${k.createdBy.first_name || ""} ${k.createdBy.last_name || ""}`.trim() || k.createdBy.email : void 0
|
|
3828
|
-
})),
|
|
3829
|
-
configured: keys.map((k) => k.provider),
|
|
3830
|
-
missing: API_KEY_PROVIDERS.filter((p) => !keys.some((k) => k.provider === p))
|
|
3831
|
-
},
|
|
3832
|
-
noop
|
|
3833
|
-
);
|
|
3834
|
-
return;
|
|
3835
|
-
}
|
|
3836
|
-
console.log("\nAPI Keys Configuration");
|
|
3837
|
-
console.log("\u2500".repeat(50));
|
|
3838
|
-
if (keys.length === 0) {
|
|
3839
|
-
console.log("\nNo API keys configured.");
|
|
3840
|
-
console.log("\nTo configure API keys, run:");
|
|
3841
|
-
console.log(" indigo setup keys");
|
|
3842
|
-
console.log("");
|
|
3843
|
-
return;
|
|
3844
|
-
}
|
|
3845
|
-
console.log("\nConfigured Providers:");
|
|
3846
|
-
for (const key of keys) {
|
|
3847
|
-
const info = PROVIDER_INFO[key.provider];
|
|
3848
|
-
const displayName = info?.displayName || key.provider;
|
|
3849
|
-
const validStatus = key.isValid === false ? " (invalid)" : "";
|
|
3850
|
-
const lastUsed = key.lastUsedAt ? ` - Last used: ${new Date(key.lastUsedAt).toLocaleDateString()}` : "";
|
|
3851
|
-
console.log(` \u2713 ${displayName}${validStatus}${lastUsed}`);
|
|
3852
|
-
}
|
|
3853
|
-
const missing = API_KEY_PROVIDERS.filter((p) => !keys.some((k) => k.provider === p));
|
|
3854
|
-
if (missing.length > 0) {
|
|
3855
|
-
console.log("\nNot Configured:");
|
|
3856
|
-
for (const provider of missing) {
|
|
3857
|
-
const info = PROVIDER_INFO[provider];
|
|
3858
|
-
console.log(` \u25CB ${info?.displayName || provider}`);
|
|
3859
|
-
}
|
|
3860
|
-
}
|
|
3861
|
-
console.log("\nTo configure a specific provider:");
|
|
3862
|
-
console.log(" indigo setup keys --provider <provider>");
|
|
3863
|
-
console.log("\nTo remove a key:");
|
|
3864
|
-
console.log(" indigo setup keys --remove <provider>");
|
|
3865
|
-
console.log("");
|
|
3866
|
-
}
|
|
3867
|
-
async function handleKeysRemove(provider, options) {
|
|
3868
|
-
const ctx = createOutputContext(options);
|
|
3869
|
-
if (!isValidProvider(provider)) {
|
|
3870
|
-
return outputValidationError(
|
|
3871
|
-
ctx,
|
|
3872
|
-
`Invalid provider "${provider}"`,
|
|
3873
|
-
`Valid providers: ${API_KEY_PROVIDERS.join(", ")}`
|
|
3874
|
-
);
|
|
3875
|
-
}
|
|
3876
|
-
const authState = await getAuthState();
|
|
3877
|
-
if (!authState.isAuthenticated) {
|
|
3878
|
-
return outputAuthRequired(ctx);
|
|
3879
|
-
}
|
|
3880
|
-
const info = PROVIDER_INFO[provider];
|
|
3881
|
-
if (!ctx.json) {
|
|
3882
|
-
const confirm = await promptYesNo(`
|
|
3883
|
-
Remove API key for ${info.displayName}?`);
|
|
3884
|
-
if (!confirm) {
|
|
3885
|
-
console.log("Removal cancelled.");
|
|
3886
|
-
return;
|
|
3887
|
-
}
|
|
3888
|
-
console.log("\nRemoving API key...");
|
|
3889
|
-
}
|
|
3890
|
-
const result = await removeApiKey(provider);
|
|
3891
|
-
if (!result.success) {
|
|
3892
|
-
return outputError(ctx, result.error || "Failed to remove API key", {
|
|
3893
|
-
code: "API_KEY_REMOVE_ERROR",
|
|
3894
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3895
|
-
});
|
|
3896
|
-
}
|
|
3897
|
-
if (ctx.json) {
|
|
3898
|
-
outputSuccess(
|
|
3899
|
-
ctx,
|
|
3900
|
-
{
|
|
3901
|
-
status: "removed",
|
|
3902
|
-
provider,
|
|
3903
|
-
displayName: info.displayName,
|
|
3904
|
-
message: `API key for ${info.displayName} has been removed.`
|
|
3905
|
-
},
|
|
3906
|
-
noop
|
|
3907
|
-
);
|
|
3908
|
-
} else {
|
|
3909
|
-
console.log(`
|
|
3910
|
-
\u2713 API key for ${info.displayName} has been removed.`);
|
|
3911
|
-
}
|
|
3912
|
-
}
|
|
3913
|
-
async function handleKeysConfigure(provider, options) {
|
|
3914
|
-
const ctx = createOutputContext(options);
|
|
3915
|
-
if (!isValidProvider(provider)) {
|
|
3916
|
-
return outputValidationError(
|
|
3917
|
-
ctx,
|
|
3918
|
-
`Invalid provider "${provider}"`,
|
|
3919
|
-
`Valid providers: ${API_KEY_PROVIDERS.join(", ")}`
|
|
3920
|
-
);
|
|
3921
|
-
}
|
|
3922
|
-
const authState = await getAuthState();
|
|
3923
|
-
if (!authState.isAuthenticated) {
|
|
3924
|
-
return outputAuthRequired(ctx);
|
|
3925
|
-
}
|
|
3926
|
-
const info = PROVIDER_INFO[provider];
|
|
3927
|
-
if (ctx.json) {
|
|
3928
|
-
outputSuccess(
|
|
3929
|
-
ctx,
|
|
3930
|
-
{
|
|
3931
|
-
status: "interactive_required",
|
|
3932
|
-
provider,
|
|
3933
|
-
displayName: info.displayName,
|
|
3934
|
-
docsUrl: info.docsUrl,
|
|
3935
|
-
placeholder: info.placeholder,
|
|
3936
|
-
message: "Interactive input required. Run without --json flag to configure."
|
|
3937
|
-
},
|
|
3938
|
-
noop
|
|
3939
|
-
);
|
|
3940
|
-
return;
|
|
3941
|
-
}
|
|
3942
|
-
console.log(`
|
|
3943
|
-
Configure ${info.displayName} API Key`);
|
|
3944
|
-
console.log("\u2500".repeat(50));
|
|
3945
|
-
console.log(`
|
|
3946
|
-
${info.description}`);
|
|
3947
|
-
console.log(`
|
|
3948
|
-
Get your API key at: ${info.docsUrl}`);
|
|
3949
|
-
console.log(`Expected format: ${info.placeholder}`);
|
|
3950
|
-
const key = await prompt("\nEnter your API key (or press Enter to skip): ");
|
|
3951
|
-
if (!key) {
|
|
3952
|
-
console.log("Skipped.");
|
|
3953
|
-
return;
|
|
3954
|
-
}
|
|
3955
|
-
const trimmedKey = key.trim();
|
|
3956
|
-
if (trimmedKey.length < 10) {
|
|
3957
|
-
return outputValidationError(
|
|
3958
|
-
ctx,
|
|
3959
|
-
"API key is too short (minimum 10 characters)",
|
|
3960
|
-
`Check your API key and try again. Get a valid key at: ${info.docsUrl}`
|
|
3961
|
-
);
|
|
3962
|
-
}
|
|
3963
|
-
console.log("\nValidating API key...");
|
|
3964
|
-
const result = await saveApiKey(provider, trimmedKey);
|
|
3965
|
-
if (!result.success) {
|
|
3966
|
-
console.log(`
|
|
3967
|
-
\u2717 ${result.error}`);
|
|
3968
|
-
console.log("\nPlease check your API key and try again.");
|
|
3969
|
-
return;
|
|
3970
|
-
}
|
|
3971
|
-
console.log(`
|
|
3972
|
-
\u2713 ${info.displayName} API key configured successfully!`);
|
|
3973
|
-
}
|
|
3974
|
-
async function handleKeysInteractive(options) {
|
|
3975
|
-
const ctx = createOutputContext(options);
|
|
3976
|
-
const authState = await getAuthState();
|
|
3977
|
-
if (!authState.isAuthenticated) {
|
|
3978
|
-
return outputAuthRequired(ctx);
|
|
3979
|
-
}
|
|
3980
|
-
if (ctx.json) {
|
|
3981
|
-
const statusResult2 = await getApiKeysStatus();
|
|
3982
|
-
if (!statusResult2.success || !statusResult2.data) {
|
|
3983
|
-
return outputError(ctx, statusResult2.error || "Failed to get API keys status", {
|
|
3984
|
-
code: "API_KEYS_ERROR",
|
|
3985
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
3986
|
-
});
|
|
3987
|
-
}
|
|
3988
|
-
outputSuccess(
|
|
3989
|
-
ctx,
|
|
3990
|
-
{
|
|
3991
|
-
status: "interactive_required",
|
|
3992
|
-
...statusResult2.data,
|
|
3993
|
-
message: "Interactive input required. Run without --json flag to configure."
|
|
3994
|
-
},
|
|
3995
|
-
noop
|
|
3996
|
-
);
|
|
3997
|
-
return;
|
|
3998
|
-
}
|
|
3999
|
-
console.log("\nAPI Keys Configuration (BYOK - Bring Your Own Key)");
|
|
4000
|
-
console.log("\u2500".repeat(50));
|
|
4001
|
-
console.log("\nIndigo can use your own API keys for AI model providers.");
|
|
4002
|
-
console.log("This allows you to use your own billing and API quotas.");
|
|
4003
|
-
console.log("\nYou can skip any provider by pressing Enter without input.");
|
|
4004
|
-
const statusResult = await getApiKeysStatus();
|
|
4005
|
-
if (statusResult.success && statusResult.data && statusResult.data.configured.length > 0) {
|
|
4006
|
-
console.log("\nAlready configured:");
|
|
4007
|
-
for (const provider of statusResult.data.configured) {
|
|
4008
|
-
const info = PROVIDER_INFO[provider];
|
|
4009
|
-
console.log(` \u2713 ${info?.displayName || provider}`);
|
|
4010
|
-
}
|
|
4011
|
-
console.log("\nYou can update these or configure additional providers.");
|
|
4012
|
-
}
|
|
4013
|
-
let configuredCount = 0;
|
|
4014
|
-
let skippedCount = 0;
|
|
4015
|
-
for (const provider of API_KEY_PROVIDERS) {
|
|
4016
|
-
const info = PROVIDER_INFO[provider];
|
|
4017
|
-
console.log(`
|
|
4018
|
-
${"\u2500".repeat(50)}`);
|
|
4019
|
-
console.log(`
|
|
4020
|
-
${info.displayName}`);
|
|
4021
|
-
console.log(info.description);
|
|
4022
|
-
console.log(`Get your API key at: ${info.docsUrl}`);
|
|
4023
|
-
console.log(`Expected format: ${info.placeholder}`);
|
|
4024
|
-
const key = await prompt(`
|
|
4025
|
-
Enter ${info.displayName} API key (or press Enter to skip): `);
|
|
4026
|
-
if (!key) {
|
|
4027
|
-
console.log("Skipped.");
|
|
4028
|
-
skippedCount++;
|
|
4029
|
-
continue;
|
|
4030
|
-
}
|
|
4031
|
-
const trimmedKey = key.trim();
|
|
4032
|
-
if (trimmedKey.length < 10) {
|
|
4033
|
-
console.log(`\u2717 API key is too short (minimum 10 characters). Get a valid key at: ${info.docsUrl}`);
|
|
4034
|
-
skippedCount++;
|
|
4035
|
-
continue;
|
|
4036
|
-
}
|
|
4037
|
-
console.log("Validating...");
|
|
4038
|
-
const result = await saveApiKey(provider, trimmedKey);
|
|
4039
|
-
if (!result.success) {
|
|
4040
|
-
console.log(`\u2717 ${result.error}`);
|
|
4041
|
-
skippedCount++;
|
|
4042
|
-
} else {
|
|
4043
|
-
console.log(`\u2713 ${info.displayName} configured!`);
|
|
4044
|
-
configuredCount++;
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
4047
|
-
console.log(`
|
|
4048
|
-
${"\u2500".repeat(50)}`);
|
|
4049
|
-
console.log("\nAPI Keys Setup Complete");
|
|
4050
|
-
console.log(` Configured: ${configuredCount}`);
|
|
4051
|
-
console.log(` Skipped: ${skippedCount}`);
|
|
4052
|
-
if (configuredCount === 0) {
|
|
4053
|
-
console.log("\nNo API keys were configured. You can configure them later with:");
|
|
4054
|
-
console.log(" indigo setup keys --provider <provider>");
|
|
4055
|
-
} else {
|
|
4056
|
-
console.log("\nTo view configured keys: indigo setup keys --list");
|
|
4057
|
-
console.log("To configure more: indigo setup keys --provider <provider>");
|
|
4058
|
-
}
|
|
4059
|
-
console.log("");
|
|
4060
|
-
}
|
|
4061
|
-
|
|
4062
|
-
// apps/cli/src/commands/meetings/index.ts
|
|
4063
|
-
var import_commander6 = require("commander");
|
|
4064
|
-
|
|
4065
|
-
// apps/cli/src/services/meetings.ts
|
|
4066
|
-
async function fetchMeetings(options = {}) {
|
|
4067
|
-
const token = await getToken();
|
|
4068
|
-
if (!token) {
|
|
4069
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
4070
|
-
}
|
|
4071
|
-
try {
|
|
4072
|
-
const params = new URLSearchParams();
|
|
4073
|
-
if (options.page)
|
|
4074
|
-
params.set("page", String(options.page));
|
|
4075
|
-
if (options.limit)
|
|
4076
|
-
params.set("pageSize", String(options.limit));
|
|
4077
|
-
const endpoint = options.past ? "/meetings/past-meetings" : "/meetings";
|
|
4078
|
-
const queryString = params.toString();
|
|
4079
|
-
const url = `${apiUrl}${endpoint}${queryString ? `?${queryString}` : ""}`;
|
|
4080
|
-
const response = await fetch(url, {
|
|
4081
|
-
method: "GET",
|
|
4082
|
-
headers: {
|
|
4083
|
-
"Content-Type": "application/json",
|
|
4084
|
-
Authorization: `Bearer ${token}`
|
|
4085
|
-
}
|
|
4086
|
-
});
|
|
4087
|
-
if (!response.ok) {
|
|
4088
|
-
if (response.status === 401) {
|
|
4089
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
4090
|
-
}
|
|
4091
|
-
if (response.status === 403) {
|
|
4092
|
-
return { success: false, error: "Access denied. You may not have permission to view meetings." };
|
|
4093
|
-
}
|
|
4094
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
4095
|
-
}
|
|
4096
|
-
const result = await response.json();
|
|
4097
|
-
const meetings = result.meetings || result.data || [];
|
|
4098
|
-
const pagination = result.pagination;
|
|
4099
|
-
return { success: true, meetings, pagination };
|
|
4100
|
-
} catch (error) {
|
|
4101
|
-
if (error instanceof Error) {
|
|
4102
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
4103
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
4104
|
-
}
|
|
4105
|
-
return { success: false, error: error.message };
|
|
4106
|
-
}
|
|
4107
|
-
return { success: false, error: "Unknown error occurred" };
|
|
4108
|
-
}
|
|
4109
|
-
}
|
|
4110
|
-
async function fetchMeetingById(id) {
|
|
4111
|
-
const token = await getToken();
|
|
4112
|
-
if (!token) {
|
|
4113
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
4114
|
-
}
|
|
4115
|
-
try {
|
|
4116
|
-
const response = await fetch(`${apiUrl}/meetings/${id}`, {
|
|
4117
|
-
method: "GET",
|
|
4118
|
-
headers: {
|
|
4119
|
-
"Content-Type": "application/json",
|
|
4120
|
-
Authorization: `Bearer ${token}`
|
|
4121
|
-
}
|
|
4122
|
-
});
|
|
4123
|
-
if (!response.ok) {
|
|
4124
|
-
if (response.status === 401) {
|
|
4125
|
-
return { success: false, error: 'Authentication expired. Run "indigo auth login" to re-authenticate.' };
|
|
4126
|
-
}
|
|
4127
|
-
if (response.status === 404) {
|
|
4128
|
-
return { success: false, error: `Meeting with ID "${id}" not found.` };
|
|
4129
|
-
}
|
|
4130
|
-
return { success: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
4131
|
-
}
|
|
4132
|
-
const meeting = await response.json();
|
|
4133
|
-
return { success: true, meeting };
|
|
4134
|
-
} catch (error) {
|
|
4135
|
-
if (error instanceof Error) {
|
|
4136
|
-
return { success: false, error: error.message };
|
|
4137
|
-
}
|
|
4138
|
-
return { success: false, error: "Unknown error occurred" };
|
|
4139
|
-
}
|
|
4140
|
-
}
|
|
4141
|
-
function meetingMatchesQuery(meeting, query) {
|
|
4142
|
-
const lowerQuery = query.toLowerCase();
|
|
4143
|
-
if (meeting.meeting_title?.toLowerCase().includes(lowerQuery)) {
|
|
4144
|
-
return true;
|
|
4145
|
-
}
|
|
4146
|
-
if (meeting.meeting_description?.toLowerCase().includes(lowerQuery)) {
|
|
4147
|
-
return true;
|
|
4148
|
-
}
|
|
4149
|
-
if (meeting.participants && meeting.participants.length > 0) {
|
|
4150
|
-
for (const participant of meeting.participants) {
|
|
4151
|
-
if (participant.email?.toLowerCase().includes(lowerQuery)) {
|
|
4152
|
-
return true;
|
|
4153
|
-
}
|
|
4154
|
-
if (participant.displayName?.toLowerCase().includes(lowerQuery)) {
|
|
4155
|
-
return true;
|
|
4156
|
-
}
|
|
4157
|
-
}
|
|
4158
|
-
}
|
|
4159
|
-
return false;
|
|
4160
|
-
}
|
|
4161
|
-
async function searchMeetings(options) {
|
|
4162
|
-
const token = await getToken();
|
|
4163
|
-
if (!token) {
|
|
4164
|
-
return { success: false, error: 'Not authenticated. Run "indigo auth login" first.' };
|
|
4165
|
-
}
|
|
4166
|
-
try {
|
|
4167
|
-
const allMeetings = [];
|
|
4168
|
-
const fetchUpcoming = options.upcoming === true || options.upcoming !== false && options.past !== true;
|
|
4169
|
-
const fetchPast = options.past === true || options.past !== false && options.upcoming !== true;
|
|
4170
|
-
if (fetchUpcoming) {
|
|
4171
|
-
const upcomingResult = await fetchMeetings({ limit: 100, past: false });
|
|
4172
|
-
if (upcomingResult.success && upcomingResult.meetings) {
|
|
4173
|
-
allMeetings.push(...upcomingResult.meetings);
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4176
|
-
if (fetchPast) {
|
|
4177
|
-
const pastResult = await fetchMeetings({ limit: 100, past: true });
|
|
4178
|
-
if (pastResult.success && pastResult.meetings) {
|
|
4179
|
-
allMeetings.push(...pastResult.meetings);
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
const filteredMeetings = allMeetings.filter(
|
|
4183
|
-
(meeting) => meetingMatchesQuery(meeting, options.query)
|
|
4184
|
-
);
|
|
4185
|
-
filteredMeetings.sort((a, b) => {
|
|
4186
|
-
const dateA = new Date(a.start_time).getTime();
|
|
4187
|
-
const dateB = new Date(b.start_time).getTime();
|
|
4188
|
-
return dateB - dateA;
|
|
4189
|
-
});
|
|
4190
|
-
const limit = options.limit || 20;
|
|
4191
|
-
const limitedMeetings = filteredMeetings.slice(0, limit);
|
|
4192
|
-
return { success: true, meetings: limitedMeetings };
|
|
4193
|
-
} catch (error) {
|
|
4194
|
-
if (error instanceof Error) {
|
|
4195
|
-
if (error.message.includes("ECONNREFUSED") || error.message.includes("fetch failed")) {
|
|
4196
|
-
return { success: false, error: "Could not connect to Indigo API. Check your internet connection." };
|
|
4197
|
-
}
|
|
4198
|
-
return { success: false, error: error.message };
|
|
4199
|
-
}
|
|
4200
|
-
return { success: false, error: "Unknown error occurred" };
|
|
4201
|
-
}
|
|
4202
|
-
}
|
|
4203
|
-
function formatDateTime(dateString) {
|
|
4204
|
-
try {
|
|
4205
|
-
const date = new Date(dateString);
|
|
4206
|
-
return date.toLocaleString("en-US", {
|
|
4207
|
-
month: "short",
|
|
4208
|
-
day: "numeric",
|
|
4209
|
-
hour: "numeric",
|
|
4210
|
-
minute: "2-digit",
|
|
4211
|
-
hour12: true
|
|
4212
|
-
});
|
|
4213
|
-
} catch {
|
|
4214
|
-
return dateString;
|
|
4215
|
-
}
|
|
4216
|
-
}
|
|
4217
|
-
function formatDate2(dateString) {
|
|
4218
|
-
try {
|
|
4219
|
-
const date = new Date(dateString);
|
|
4220
|
-
return date.toLocaleDateString("en-US", {
|
|
4221
|
-
year: "numeric",
|
|
4222
|
-
month: "short",
|
|
4223
|
-
day: "numeric"
|
|
4224
|
-
});
|
|
4225
|
-
} catch {
|
|
4226
|
-
return dateString;
|
|
4227
|
-
}
|
|
4228
|
-
}
|
|
4229
|
-
function getMeetingDuration(meeting) {
|
|
4230
|
-
try {
|
|
4231
|
-
const start = new Date(meeting.start_time);
|
|
4232
|
-
const end = new Date(meeting.end_time);
|
|
4233
|
-
return Math.round((end.getTime() - start.getTime()) / (1e3 * 60));
|
|
4234
|
-
} catch {
|
|
4235
|
-
return 0;
|
|
4236
|
-
}
|
|
4237
|
-
}
|
|
4238
|
-
function formatDuration(minutes) {
|
|
4239
|
-
if (minutes < 60) {
|
|
4240
|
-
return `${minutes}m`;
|
|
4241
|
-
}
|
|
4242
|
-
const hours = Math.floor(minutes / 60);
|
|
4243
|
-
const mins = minutes % 60;
|
|
4244
|
-
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
|
|
4245
|
-
}
|
|
4246
|
-
function formatAttendees(participants, maxLength = 40) {
|
|
4247
|
-
if (!participants || participants.length === 0) {
|
|
4248
|
-
return "-";
|
|
4249
|
-
}
|
|
4250
|
-
const names = participants.map((p) => p.displayName || p.email.split("@")[0]);
|
|
4251
|
-
const joined = names.join(", ");
|
|
4252
|
-
if (joined.length <= maxLength) {
|
|
4253
|
-
return joined;
|
|
4254
|
-
}
|
|
4255
|
-
return joined.slice(0, maxLength - 3) + "...";
|
|
4256
|
-
}
|
|
4257
|
-
function formatStatus(status) {
|
|
4258
|
-
const statusMap = {
|
|
4259
|
-
"scheduled": "Scheduled",
|
|
4260
|
-
"in-progress": "In Progress",
|
|
4261
|
-
"processing": "Processing",
|
|
4262
|
-
"completed": "Completed",
|
|
4263
|
-
"failed": "Failed",
|
|
4264
|
-
"kicked": "Kicked",
|
|
4265
|
-
"denied": "Denied",
|
|
4266
|
-
"cancelled": "Cancelled"
|
|
4267
|
-
};
|
|
4268
|
-
return statusMap[status] || status;
|
|
4269
|
-
}
|
|
4270
|
-
|
|
4271
|
-
// apps/cli/src/commands/meetings/index.ts
|
|
4272
|
-
var DEFAULT_LIMIT2 = 20;
|
|
4273
|
-
function truncate2(str, maxLength) {
|
|
4274
|
-
if (str.length <= maxLength)
|
|
4275
|
-
return str;
|
|
4276
|
-
return str.slice(0, maxLength - 3) + "...";
|
|
4277
|
-
}
|
|
4278
|
-
function getTerminalWidth2() {
|
|
4279
|
-
return process.stdout.columns || 80;
|
|
4280
|
-
}
|
|
4281
|
-
function wordWrap2(text, maxWidth) {
|
|
4282
|
-
if (!text)
|
|
4283
|
-
return "";
|
|
4284
|
-
const lines = [];
|
|
4285
|
-
const paragraphs = text.split(/\n/);
|
|
4286
|
-
for (const paragraph of paragraphs) {
|
|
4287
|
-
if (paragraph.trim() === "") {
|
|
4288
|
-
lines.push("");
|
|
4289
|
-
continue;
|
|
4290
|
-
}
|
|
4291
|
-
const words = paragraph.split(/\s+/);
|
|
4292
|
-
let currentLine = "";
|
|
4293
|
-
for (const word of words) {
|
|
4294
|
-
if (currentLine.length === 0) {
|
|
4295
|
-
currentLine = word;
|
|
4296
|
-
} else if (currentLine.length + 1 + word.length <= maxWidth) {
|
|
4297
|
-
currentLine += " " + word;
|
|
4298
|
-
} else {
|
|
4299
|
-
lines.push(currentLine);
|
|
4300
|
-
currentLine = word;
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
if (currentLine.length > 0) {
|
|
4304
|
-
lines.push(currentLine);
|
|
4305
|
-
}
|
|
4306
|
-
}
|
|
4307
|
-
return lines.join("\n");
|
|
4308
|
-
}
|
|
4309
|
-
async function checkCalendarConnection(ctx) {
|
|
4310
|
-
const scopeResult = await getCalendarScopeStatus();
|
|
4311
|
-
if (!scopeResult.success) {
|
|
4312
|
-
if (scopeResult.error?.includes("Not authenticated") || scopeResult.error?.includes("401")) {
|
|
4313
|
-
outputAuthRequired(ctx);
|
|
4314
|
-
}
|
|
4315
|
-
outputError(ctx, scopeResult.error || "Failed to check calendar status", {
|
|
4316
|
-
code: "CALENDAR_CHECK_ERROR",
|
|
4317
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
4318
|
-
});
|
|
4319
|
-
}
|
|
4320
|
-
if (!scopeResult.data?.hasCalendarScope) {
|
|
4321
|
-
outputConfigError(
|
|
4322
|
-
ctx,
|
|
4323
|
-
'Calendar not connected. Run "indigo setup calendar" to connect your calendar first.'
|
|
4324
|
-
);
|
|
4325
|
-
}
|
|
4326
|
-
return true;
|
|
4327
|
-
}
|
|
4328
|
-
function printMeetingsTable(meetings, options = {}) {
|
|
4329
|
-
if (meetings.length === 0) {
|
|
4330
|
-
const timeframe = options.past ? "past" : "upcoming";
|
|
4331
|
-
console.log(`
|
|
4332
|
-
No ${timeframe} meetings found.`);
|
|
4333
|
-
if (!options.past) {
|
|
4334
|
-
console.log("Check back later or use --past to see past meetings.");
|
|
4335
|
-
}
|
|
4336
|
-
return;
|
|
4337
|
-
}
|
|
4338
|
-
const titleWidth = 35;
|
|
4339
|
-
const dateWidth = 18;
|
|
4340
|
-
const durationWidth = 8;
|
|
4341
|
-
const attendeesWidth = 30;
|
|
4342
|
-
console.log("");
|
|
4343
|
-
console.log(
|
|
4344
|
-
"TITLE".padEnd(titleWidth) + "DATE/TIME".padEnd(dateWidth) + "DURATION".padEnd(durationWidth) + "ATTENDEES"
|
|
4345
|
-
);
|
|
4346
|
-
console.log("\u2500".repeat(titleWidth + dateWidth + durationWidth + attendeesWidth));
|
|
4347
|
-
for (const meeting of meetings) {
|
|
4348
|
-
const title = truncate2(meeting.meeting_title || "Untitled", titleWidth - 2).padEnd(titleWidth);
|
|
4349
|
-
const dateTime = formatDateTime(meeting.start_time).padEnd(dateWidth);
|
|
4350
|
-
const duration = formatDuration(getMeetingDuration(meeting)).padEnd(durationWidth);
|
|
4351
|
-
const attendees = formatAttendees(meeting.participants, attendeesWidth);
|
|
4352
|
-
console.log(`${title}${dateTime}${duration}${attendees}`);
|
|
4353
|
-
}
|
|
4354
|
-
console.log("");
|
|
4355
|
-
console.log(`Showing ${meetings.length} meeting${meetings.length !== 1 ? "s" : ""}`);
|
|
4356
|
-
}
|
|
4357
|
-
function printMeetingDetail(meeting) {
|
|
4358
|
-
const width = getTerminalWidth2();
|
|
4359
|
-
const separator = "\u2500".repeat(Math.min(width, 80));
|
|
4360
|
-
console.log("");
|
|
4361
|
-
console.log(separator);
|
|
4362
|
-
console.log(`MEETING: ${meeting.meeting_title || "Untitled"}`);
|
|
4363
|
-
console.log(separator);
|
|
4364
|
-
console.log("");
|
|
4365
|
-
console.log(`ID: ${meeting._id}`);
|
|
4366
|
-
console.log(`Status: ${formatStatus(meeting.status)}`);
|
|
4367
|
-
console.log(`Date: ${formatDate2(meeting.start_time)}`);
|
|
4368
|
-
console.log(`Time: ${formatDateTime(meeting.start_time)} - ${formatDateTime(meeting.end_time)}`);
|
|
4369
|
-
console.log(`Duration: ${formatDuration(getMeetingDuration(meeting))}`);
|
|
4370
|
-
if (meeting.meeting_url) {
|
|
4371
|
-
console.log(`URL: ${meeting.meeting_url}`);
|
|
4372
|
-
}
|
|
4373
|
-
if (meeting.participants && meeting.participants.length > 0) {
|
|
4374
|
-
console.log("");
|
|
4375
|
-
console.log("PARTICIPANTS");
|
|
4376
|
-
console.log(separator);
|
|
4377
|
-
for (const p of meeting.participants) {
|
|
4378
|
-
const name = p.displayName || p.email;
|
|
4379
|
-
const status = p.responseStatus ? ` (${p.responseStatus})` : "";
|
|
4380
|
-
const role = p.organizer ? " [organizer]" : p.self ? " [you]" : "";
|
|
4381
|
-
console.log(` ${name}${status}${role}`);
|
|
4382
|
-
}
|
|
4383
|
-
}
|
|
4384
|
-
if (meeting.meeting_description) {
|
|
4385
|
-
console.log("");
|
|
4386
|
-
console.log("DESCRIPTION");
|
|
4387
|
-
console.log(separator);
|
|
4388
|
-
const contentWidth = Math.min(width - 2, 78);
|
|
4389
|
-
console.log(wordWrap2(meeting.meeting_description, contentWidth));
|
|
4390
|
-
}
|
|
4391
|
-
if (meeting.summary) {
|
|
4392
|
-
console.log("");
|
|
4393
|
-
console.log("SUMMARY");
|
|
4394
|
-
console.log(separator);
|
|
4395
|
-
const contentWidth = Math.min(width - 2, 78);
|
|
4396
|
-
console.log(wordWrap2(meeting.summary, contentWidth));
|
|
4397
|
-
}
|
|
4398
|
-
if (meeting.bot_enabled) {
|
|
4399
|
-
console.log("");
|
|
4400
|
-
console.log(`Bot: ${meeting.bot_name || "Enabled"}`);
|
|
4401
|
-
if (meeting.summary_recipient) {
|
|
4402
|
-
const recipientText = {
|
|
4403
|
-
"all_participants": "All participants",
|
|
4404
|
-
"just_me": "Just me",
|
|
4405
|
-
"none": "No one"
|
|
4406
|
-
};
|
|
4407
|
-
console.log(`Summary sent to: ${recipientText[meeting.summary_recipient] || meeting.summary_recipient}`);
|
|
4408
|
-
}
|
|
4409
|
-
}
|
|
4410
|
-
console.log("");
|
|
4411
|
-
}
|
|
4412
|
-
function createMeetingsCommand() {
|
|
4413
|
-
const meetings = new import_commander6.Command("meetings").description("Meeting management commands");
|
|
4414
|
-
meetings.command("list").description("List your meetings").option("--json", "Output as JSON for scripting").option("-n, --limit <number>", `Number of results to show (default: ${DEFAULT_LIMIT2})`, String(DEFAULT_LIMIT2)).option("--upcoming", "Show upcoming meetings (default)").option("--past", "Show past meetings").action(async (options) => {
|
|
4415
|
-
const ctx = createOutputContext(options);
|
|
4416
|
-
const parsedLimit = parseInt(options.limit, 10);
|
|
4417
|
-
if (isNaN(parsedLimit) || parsedLimit <= 0) {
|
|
4418
|
-
outputValidationError(
|
|
4419
|
-
ctx,
|
|
4420
|
-
"Invalid value for --limit",
|
|
4421
|
-
"Provide a positive integer (e.g., --limit 10)"
|
|
4422
|
-
);
|
|
4423
|
-
}
|
|
4424
|
-
const limit = parsedLimit;
|
|
4425
|
-
await checkCalendarConnection(ctx);
|
|
4426
|
-
const fetchPast = options.past === true;
|
|
4427
|
-
const result = await fetchMeetings({
|
|
4428
|
-
limit,
|
|
4429
|
-
past: fetchPast
|
|
4430
|
-
});
|
|
4431
|
-
if (!result.success) {
|
|
4432
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
4433
|
-
outputAuthRequired(ctx);
|
|
4434
|
-
}
|
|
4435
|
-
outputError(ctx, result.error || "Failed to fetch meetings", {
|
|
4436
|
-
code: "FETCH_ERROR",
|
|
4437
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
4438
|
-
});
|
|
4439
|
-
}
|
|
4440
|
-
let meetingsData = result.meetings || [];
|
|
4441
|
-
if (limit && meetingsData.length > limit) {
|
|
4442
|
-
meetingsData = meetingsData.slice(0, limit);
|
|
4443
|
-
}
|
|
4444
|
-
outputData(ctx, meetingsData, (data) => printMeetingsTable(data, { past: fetchPast }));
|
|
4445
|
-
});
|
|
4446
|
-
meetings.command("view <id>").description("View a specific meeting's full details").option("--json", "Output as JSON for scripting").action(async (id, options) => {
|
|
4447
|
-
const ctx = createOutputContext(options);
|
|
4448
|
-
if (!id || id.trim() === "") {
|
|
4449
|
-
outputValidationError(
|
|
4450
|
-
ctx,
|
|
4451
|
-
"Meeting ID cannot be empty",
|
|
4452
|
-
"Provide a valid meeting ID (e.g., indigo meetings view 507f1f77bcf86cd799439011)"
|
|
4453
|
-
);
|
|
4454
|
-
}
|
|
4455
|
-
const objectIdRegex = /^[a-fA-F0-9]{24}$/;
|
|
4456
|
-
if (!objectIdRegex.test(id)) {
|
|
4457
|
-
outputValidationError(
|
|
4458
|
-
ctx,
|
|
4459
|
-
`Invalid meeting ID format "${id}"`,
|
|
4460
|
-
"Meeting ID must be a 24-character hexadecimal string"
|
|
4461
|
-
);
|
|
4462
|
-
}
|
|
4463
|
-
await checkCalendarConnection(ctx);
|
|
4464
|
-
const result = await fetchMeetingById(id);
|
|
4465
|
-
if (!result.success) {
|
|
4466
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
4467
|
-
outputAuthRequired(ctx);
|
|
4468
|
-
}
|
|
4469
|
-
if (result.error?.includes("not found") || result.error?.includes("404")) {
|
|
4470
|
-
outputNotFound(ctx, "Meeting", id);
|
|
4471
|
-
}
|
|
4472
|
-
outputError(ctx, result.error || "Failed to fetch meeting", {
|
|
4473
|
-
code: "FETCH_ERROR",
|
|
4474
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
4475
|
-
});
|
|
4476
|
-
}
|
|
4477
|
-
const meeting = result.meeting;
|
|
4478
|
-
if (!meeting) {
|
|
4479
|
-
outputNotFound(ctx, "Meeting", id);
|
|
4480
|
-
}
|
|
4481
|
-
outputData(ctx, meeting, printMeetingDetail);
|
|
4482
|
-
});
|
|
4483
|
-
meetings.command("search <query>").description("Search meetings by title, description, or attendee").option("--json", "Output as JSON for scripting").option("-n, --limit <number>", `Number of results to show (default: ${DEFAULT_LIMIT2})`, String(DEFAULT_LIMIT2)).option("--upcoming", "Search only upcoming meetings").option("--past", "Search only past meetings").action(async (query, options) => {
|
|
4484
|
-
const ctx = createOutputContext(options);
|
|
4485
|
-
if (!query || query.trim() === "") {
|
|
4486
|
-
outputValidationError(
|
|
4487
|
-
ctx,
|
|
4488
|
-
"Search query cannot be empty",
|
|
4489
|
-
'Provide a search term (e.g., indigo meetings search "standup")'
|
|
4490
|
-
);
|
|
4491
|
-
}
|
|
4492
|
-
const parsedLimit = parseInt(options.limit, 10);
|
|
4493
|
-
if (isNaN(parsedLimit) || parsedLimit <= 0) {
|
|
4494
|
-
outputValidationError(
|
|
4495
|
-
ctx,
|
|
4496
|
-
"Invalid value for --limit",
|
|
4497
|
-
"Provide a positive integer (e.g., --limit 10)"
|
|
4498
|
-
);
|
|
4499
|
-
}
|
|
4500
|
-
const limit = parsedLimit;
|
|
4501
|
-
await checkCalendarConnection(ctx);
|
|
4502
|
-
const result = await searchMeetings({
|
|
4503
|
-
query,
|
|
4504
|
-
limit,
|
|
4505
|
-
upcoming: options.upcoming,
|
|
4506
|
-
past: options.past
|
|
4507
|
-
});
|
|
4508
|
-
if (!result.success) {
|
|
4509
|
-
if (result.error?.includes("Not authenticated") || result.error?.includes("401")) {
|
|
4510
|
-
outputAuthRequired(ctx);
|
|
4511
|
-
}
|
|
4512
|
-
outputError(ctx, result.error || "Failed to search meetings", {
|
|
4513
|
-
code: "SEARCH_ERROR",
|
|
4514
|
-
exitCode: ExitCode.GENERAL_ERROR
|
|
4515
|
-
});
|
|
4516
|
-
}
|
|
4517
|
-
const meetingsData = result.meetings || [];
|
|
4518
|
-
outputData(ctx, meetingsData, (data) => {
|
|
4519
|
-
if (data.length === 0) {
|
|
4520
|
-
console.log(`
|
|
4521
|
-
No meetings found matching "${query}".`);
|
|
4522
|
-
console.log("Try a different search term or check your meetings with: indigo meetings list");
|
|
4523
|
-
} else {
|
|
4524
|
-
const scope = options.upcoming ? "upcoming " : options.past ? "past " : "";
|
|
4525
|
-
console.log(`
|
|
4526
|
-
Search results for "${query}" in ${scope}meetings:`);
|
|
4527
|
-
printMeetingsTable(data, { past: options.past });
|
|
4528
|
-
}
|
|
4529
|
-
});
|
|
4530
|
-
});
|
|
4531
|
-
return meetings;
|
|
4532
|
-
}
|
|
4533
|
-
|
|
4534
|
-
// apps/cli/src/main.ts
|
|
4535
|
-
var VERSION = "0.1.0";
|
|
4536
|
-
var program = new import_commander7.Command();
|
|
4537
|
-
program.name("indigo").description("Indigo CLI - Terminal-based access to Indigo").version(VERSION, "-v, --version", "Output the current version").option("--debug-config", "Show configuration details (environment, URLs)").hook("preAction", (thisCommand) => {
|
|
4538
|
-
if (thisCommand.opts().debugConfig) {
|
|
4539
|
-
console.log("Indigo CLI Configuration");
|
|
4540
|
-
console.log("========================");
|
|
4541
|
-
console.log(`Version: ${VERSION}`);
|
|
4542
|
-
console.log(`Environment: ${configuredUrls.environment}`);
|
|
4543
|
-
console.log(`API URL: ${configuredUrls.apiUrl}`);
|
|
4544
|
-
console.log(`Auth URL: ${configuredUrls.webauthUrl}`);
|
|
4545
|
-
console.log("");
|
|
4546
|
-
}
|
|
4547
|
-
});
|
|
4548
|
-
program.addCommand(createAuthCommand());
|
|
4549
|
-
program.addCommand(createAccountCommand());
|
|
4550
|
-
program.addCommand(createSignalsCommand());
|
|
4551
|
-
program.addCommand(createChatCommand());
|
|
4552
|
-
program.addCommand(createSetupCommand());
|
|
4553
|
-
program.addCommand(createMeetingsCommand());
|
|
4554
|
-
program.parse(process.argv);
|
|
4555
|
-
if (!process.argv.slice(2).length) {
|
|
4556
|
-
program.outputHelp();
|
|
4557
|
-
}
|