olakai-cli 0.6.2 → 0.6.5

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/dist/index.js CHANGED
@@ -1,31 +1,67 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ createAgent,
4
+ createCustomDataConfig,
5
+ createKpi,
6
+ createWorkflow,
7
+ deleteAgent,
8
+ deleteCustomDataConfig,
9
+ deleteKpi,
10
+ deleteWorkflow,
11
+ getActivity,
12
+ getActivityKpis,
13
+ getAgent,
14
+ getCurrentUser,
15
+ getCustomDataConfig,
16
+ getKpi,
17
+ getKpiContextVariables,
18
+ getWorkflow,
19
+ listActivity,
20
+ listAgents,
21
+ listCustomDataConfigs,
22
+ listKpis,
23
+ listMyAgents,
24
+ listSessions,
25
+ listWorkflows,
26
+ pollForToken,
27
+ regenerateAgentApiKey,
28
+ requestDeviceCode,
29
+ updateAgent,
30
+ updateCustomDataConfig,
31
+ updateKpi,
32
+ updateWorkflow,
33
+ validateKpiFormula
34
+ } from "./chunk-GU4HEL24.js";
2
35
  import {
3
36
  CLAUDE_DIR,
4
- CLIENT_ID,
5
- HOSTS,
6
37
  OLAKAI_DIR,
7
38
  OLAKAI_HOOK_MARKER,
8
39
  SETTINGS_FILE,
9
- clearToken,
10
40
  deleteClaudeCodeConfig,
11
41
  findConfiguredWorkspace,
12
- getBaseUrl,
13
42
  getClaudeCodeConfigPath,
14
43
  getClaudeCodeStatus,
15
44
  getClaudeDir,
16
- getEnvironment,
17
45
  getLegacyClaudeMonitorConfigPath,
18
46
  getMonitorConfigPath,
19
47
  getSettingsPath,
48
+ loadClaudeCodeConfig,
49
+ mergeHooksSettings,
50
+ readJsonFile,
51
+ writeClaudeCodeConfig,
52
+ writeJsonFile
53
+ } from "./chunk-2Q7JYGCK.js";
54
+ import {
55
+ HOSTS,
56
+ clearToken,
57
+ getBaseUrl,
58
+ getEnvironment,
20
59
  getValidEnvironments,
21
60
  getValidToken,
22
61
  isTokenValid,
23
62
  isValidEnvironment,
24
- loadClaudeCodeConfig,
25
63
  loadToken,
26
- mergeHooksSettings,
27
64
  patchProfile,
28
- readJsonFile,
29
65
  readProfilesFile,
30
66
  removeProfile,
31
67
  resolveActiveProfile,
@@ -34,10 +70,8 @@ import {
34
70
  setDefaultProfile,
35
71
  setEnvironment,
36
72
  setHostOverride,
37
- setProfileOverride,
38
- writeClaudeCodeConfig,
39
- writeJsonFile
40
- } from "./chunk-PGRX347G.js";
73
+ setProfileOverride
74
+ } from "./chunk-AVB4N2UN.js";
41
75
 
42
76
  // src/index.ts
43
77
  import { createRequire } from "module";
@@ -45,753 +79,6 @@ import { Command } from "commander";
45
79
 
46
80
  // src/commands/login.ts
47
81
  import open from "open";
48
-
49
- // src/lib/api.ts
50
- async function requestDeviceCode() {
51
- const response = await fetch(`${getBaseUrl()}/api/auth/device/code`, {
52
- method: "POST",
53
- headers: {
54
- "Content-Type": "application/json"
55
- },
56
- body: JSON.stringify({ client_id: CLIENT_ID })
57
- });
58
- if (!response.ok) {
59
- const error = await response.json();
60
- throw new Error(error.error_description || error.error || "Failed to request device code");
61
- }
62
- return await response.json();
63
- }
64
- async function pollForToken(deviceCode) {
65
- const response = await fetch(`${getBaseUrl()}/api/auth/device/token`, {
66
- method: "POST",
67
- headers: {
68
- "Content-Type": "application/json"
69
- },
70
- body: JSON.stringify({
71
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
72
- device_code: deviceCode,
73
- client_id: CLIENT_ID
74
- })
75
- });
76
- const data = await response.json();
77
- if (!response.ok) {
78
- if ("error" in data && data.error === "authorization_pending") {
79
- return null;
80
- }
81
- const errorMsg = "error_description" in data ? data.error_description : "error" in data ? data.error : "Token exchange failed";
82
- throw new Error(errorMsg || "Token exchange failed");
83
- }
84
- return data;
85
- }
86
- async function getCurrentUser() {
87
- const token = getValidToken();
88
- if (!token) {
89
- throw new Error("Not logged in. Run 'olakai login' first.");
90
- }
91
- const response = await fetch(`${getBaseUrl()}/api/user/me`, {
92
- headers: {
93
- Authorization: `Bearer ${token}`
94
- }
95
- });
96
- if (!response.ok) {
97
- if (response.status === 401) {
98
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
99
- }
100
- throw new Error("Failed to get user info");
101
- }
102
- return await response.json();
103
- }
104
- async function listAgents(options) {
105
- const token = getValidToken();
106
- if (!token) {
107
- throw new Error("Not logged in. Run 'olakai login' first.");
108
- }
109
- const params = new URLSearchParams();
110
- if (options?.includeKpis) {
111
- params.set("includeKpis", "true");
112
- }
113
- const url = `${getBaseUrl()}/api/config/agents${params.toString() ? `?${params}` : ""}`;
114
- const response = await fetch(url, {
115
- headers: {
116
- Authorization: `Bearer ${token}`
117
- }
118
- });
119
- if (!response.ok) {
120
- if (response.status === 401) {
121
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
122
- }
123
- const error = await response.json();
124
- throw new Error(error.error || "Failed to list agents");
125
- }
126
- const data = await response.json();
127
- return data.agents;
128
- }
129
- async function getAgent(id) {
130
- const token = getValidToken();
131
- if (!token) {
132
- throw new Error("Not logged in. Run 'olakai login' first.");
133
- }
134
- const response = await fetch(`${getBaseUrl()}/api/config/agents/${id}`, {
135
- headers: {
136
- Authorization: `Bearer ${token}`
137
- }
138
- });
139
- if (!response.ok) {
140
- if (response.status === 401) {
141
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
142
- }
143
- if (response.status === 404) {
144
- throw new Error("Agent not found");
145
- }
146
- const error = await response.json();
147
- throw new Error(error.error || "Failed to get agent");
148
- }
149
- return await response.json();
150
- }
151
- async function createAgent(payload) {
152
- const token = getValidToken();
153
- if (!token) {
154
- throw new Error("Not logged in. Run 'olakai login' first.");
155
- }
156
- const response = await fetch(`${getBaseUrl()}/api/config/agents`, {
157
- method: "POST",
158
- headers: {
159
- Authorization: `Bearer ${token}`,
160
- "Content-Type": "application/json"
161
- },
162
- body: JSON.stringify(payload)
163
- });
164
- if (!response.ok) {
165
- if (response.status === 401) {
166
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
167
- }
168
- if (response.status === 409) {
169
- throw new Error("An agent with this name already exists");
170
- }
171
- const error = await response.json();
172
- throw new Error(error.error || "Failed to create agent");
173
- }
174
- return await response.json();
175
- }
176
- async function updateAgent(id, payload) {
177
- const token = getValidToken();
178
- if (!token) {
179
- throw new Error("Not logged in. Run 'olakai login' first.");
180
- }
181
- const response = await fetch(`${getBaseUrl()}/api/config/agents/${id}`, {
182
- method: "PUT",
183
- headers: {
184
- Authorization: `Bearer ${token}`,
185
- "Content-Type": "application/json"
186
- },
187
- body: JSON.stringify(payload)
188
- });
189
- if (!response.ok) {
190
- if (response.status === 401) {
191
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
192
- }
193
- if (response.status === 404) {
194
- throw new Error("Agent not found");
195
- }
196
- if (response.status === 409) {
197
- throw new Error("An agent with this name already exists");
198
- }
199
- const error = await response.json();
200
- throw new Error(error.error || "Failed to update agent");
201
- }
202
- return await response.json();
203
- }
204
- async function deleteAgent(id) {
205
- const token = getValidToken();
206
- if (!token) {
207
- throw new Error("Not logged in. Run 'olakai login' first.");
208
- }
209
- const response = await fetch(`${getBaseUrl()}/api/config/agents/${id}`, {
210
- method: "DELETE",
211
- headers: {
212
- Authorization: `Bearer ${token}`
213
- }
214
- });
215
- if (!response.ok) {
216
- if (response.status === 401) {
217
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
218
- }
219
- if (response.status === 404) {
220
- throw new Error("Agent not found");
221
- }
222
- const error = await response.json();
223
- throw new Error(error.error || "Failed to delete agent");
224
- }
225
- }
226
- async function listWorkflows(options) {
227
- const token = getValidToken();
228
- if (!token) {
229
- throw new Error("Not logged in. Run 'olakai login' first.");
230
- }
231
- const params = new URLSearchParams();
232
- if (options?.includeAgents) {
233
- params.set("includeAgents", "true");
234
- }
235
- if (options?.includeInactive) {
236
- params.set("includeInactive", "true");
237
- }
238
- const url = `${getBaseUrl()}/api/config/workflows${params.toString() ? `?${params}` : ""}`;
239
- const response = await fetch(url, {
240
- headers: {
241
- Authorization: `Bearer ${token}`
242
- }
243
- });
244
- if (!response.ok) {
245
- if (response.status === 401) {
246
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
247
- }
248
- const error = await response.json();
249
- throw new Error(error.error || "Failed to list workflows");
250
- }
251
- const data = await response.json();
252
- return data.workflows;
253
- }
254
- async function getWorkflow(id) {
255
- const token = getValidToken();
256
- if (!token) {
257
- throw new Error("Not logged in. Run 'olakai login' first.");
258
- }
259
- const response = await fetch(`${getBaseUrl()}/api/config/workflows/${id}`, {
260
- headers: {
261
- Authorization: `Bearer ${token}`
262
- }
263
- });
264
- if (!response.ok) {
265
- if (response.status === 401) {
266
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
267
- }
268
- if (response.status === 404) {
269
- throw new Error("Workflow not found");
270
- }
271
- const error = await response.json();
272
- throw new Error(error.error || "Failed to get workflow");
273
- }
274
- return await response.json();
275
- }
276
- async function createWorkflow(payload) {
277
- const token = getValidToken();
278
- if (!token) {
279
- throw new Error("Not logged in. Run 'olakai login' first.");
280
- }
281
- const response = await fetch(`${getBaseUrl()}/api/config/workflows`, {
282
- method: "POST",
283
- headers: {
284
- Authorization: `Bearer ${token}`,
285
- "Content-Type": "application/json"
286
- },
287
- body: JSON.stringify(payload)
288
- });
289
- if (!response.ok) {
290
- if (response.status === 401) {
291
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
292
- }
293
- if (response.status === 409) {
294
- throw new Error("A workflow with this name already exists");
295
- }
296
- const error = await response.json();
297
- throw new Error(error.error || "Failed to create workflow");
298
- }
299
- return await response.json();
300
- }
301
- async function updateWorkflow(id, payload) {
302
- const token = getValidToken();
303
- if (!token) {
304
- throw new Error("Not logged in. Run 'olakai login' first.");
305
- }
306
- const response = await fetch(`${getBaseUrl()}/api/config/workflows/${id}`, {
307
- method: "PUT",
308
- headers: {
309
- Authorization: `Bearer ${token}`,
310
- "Content-Type": "application/json"
311
- },
312
- body: JSON.stringify(payload)
313
- });
314
- if (!response.ok) {
315
- if (response.status === 401) {
316
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
317
- }
318
- if (response.status === 404) {
319
- throw new Error("Workflow not found");
320
- }
321
- if (response.status === 409) {
322
- throw new Error("A workflow with this name already exists");
323
- }
324
- const error = await response.json();
325
- throw new Error(error.error || "Failed to update workflow");
326
- }
327
- return await response.json();
328
- }
329
- async function deleteWorkflow(id) {
330
- const token = getValidToken();
331
- if (!token) {
332
- throw new Error("Not logged in. Run 'olakai login' first.");
333
- }
334
- const response = await fetch(`${getBaseUrl()}/api/config/workflows/${id}`, {
335
- method: "DELETE",
336
- headers: {
337
- Authorization: `Bearer ${token}`
338
- }
339
- });
340
- if (!response.ok) {
341
- if (response.status === 401) {
342
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
343
- }
344
- if (response.status === 404) {
345
- throw new Error("Workflow not found");
346
- }
347
- const error = await response.json();
348
- throw new Error(error.error || "Failed to delete workflow");
349
- }
350
- }
351
- async function listKpis(options) {
352
- const token = getValidToken();
353
- if (!token) {
354
- throw new Error("Not logged in. Run 'olakai login' first.");
355
- }
356
- const params = new URLSearchParams();
357
- if (options?.agentId) {
358
- params.set("agentId", options.agentId);
359
- }
360
- if (options?.includeInactive) {
361
- params.set("includeInactive", "true");
362
- }
363
- const url = `${getBaseUrl()}/api/config/kpis${params.toString() ? `?${params}` : ""}`;
364
- const response = await fetch(url, {
365
- headers: {
366
- Authorization: `Bearer ${token}`
367
- }
368
- });
369
- if (!response.ok) {
370
- if (response.status === 401) {
371
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
372
- }
373
- const error = await response.json();
374
- throw new Error(error.error || "Failed to list KPIs");
375
- }
376
- const data = await response.json();
377
- return data.kpiDefinitions;
378
- }
379
- async function getKpi(id) {
380
- const token = getValidToken();
381
- if (!token) {
382
- throw new Error("Not logged in. Run 'olakai login' first.");
383
- }
384
- const response = await fetch(`${getBaseUrl()}/api/config/kpis/${id}`, {
385
- headers: {
386
- Authorization: `Bearer ${token}`
387
- }
388
- });
389
- if (!response.ok) {
390
- if (response.status === 401) {
391
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
392
- }
393
- if (response.status === 404) {
394
- throw new Error("KPI definition not found");
395
- }
396
- const error = await response.json();
397
- throw new Error(error.error || "Failed to get KPI");
398
- }
399
- return await response.json();
400
- }
401
- async function createKpi(payload) {
402
- const token = getValidToken();
403
- if (!token) {
404
- throw new Error("Not logged in. Run 'olakai login' first.");
405
- }
406
- const response = await fetch(`${getBaseUrl()}/api/config/kpis`, {
407
- method: "POST",
408
- headers: {
409
- Authorization: `Bearer ${token}`,
410
- "Content-Type": "application/json"
411
- },
412
- body: JSON.stringify(payload)
413
- });
414
- if (!response.ok) {
415
- if (response.status === 401) {
416
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
417
- }
418
- if (response.status === 409) {
419
- throw new Error("A KPI definition with this name already exists");
420
- }
421
- const error = await response.json();
422
- throw new Error(error.error || "Failed to create KPI");
423
- }
424
- return await response.json();
425
- }
426
- async function updateKpi(id, payload) {
427
- const token = getValidToken();
428
- if (!token) {
429
- throw new Error("Not logged in. Run 'olakai login' first.");
430
- }
431
- const response = await fetch(`${getBaseUrl()}/api/config/kpis/${id}`, {
432
- method: "PUT",
433
- headers: {
434
- Authorization: `Bearer ${token}`,
435
- "Content-Type": "application/json"
436
- },
437
- body: JSON.stringify(payload)
438
- });
439
- if (!response.ok) {
440
- if (response.status === 401) {
441
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
442
- }
443
- if (response.status === 404) {
444
- throw new Error("KPI definition not found");
445
- }
446
- if (response.status === 409) {
447
- throw new Error("A KPI definition with this name already exists");
448
- }
449
- const error = await response.json();
450
- throw new Error(error.error || "Failed to update KPI");
451
- }
452
- return await response.json();
453
- }
454
- async function deleteKpi(id) {
455
- const token = getValidToken();
456
- if (!token) {
457
- throw new Error("Not logged in. Run 'olakai login' first.");
458
- }
459
- const response = await fetch(`${getBaseUrl()}/api/config/kpis/${id}`, {
460
- method: "DELETE",
461
- headers: {
462
- Authorization: `Bearer ${token}`
463
- }
464
- });
465
- if (!response.ok) {
466
- if (response.status === 401) {
467
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
468
- }
469
- if (response.status === 404) {
470
- throw new Error("KPI definition not found");
471
- }
472
- const error = await response.json();
473
- throw new Error(error.error || "Failed to delete KPI");
474
- }
475
- }
476
- async function getKpiContextVariables(agentId, scope) {
477
- const token = getValidToken();
478
- if (!token) {
479
- throw new Error("Not logged in. Run 'olakai login' first.");
480
- }
481
- const params = new URLSearchParams();
482
- if (agentId) {
483
- params.set("agentId", agentId);
484
- }
485
- if (scope) {
486
- params.set("scope", scope);
487
- }
488
- const url = `${getBaseUrl()}/api/config/kpis/context-variables${params.toString() ? `?${params}` : ""}`;
489
- const response = await fetch(url, {
490
- headers: {
491
- Authorization: `Bearer ${token}`
492
- }
493
- });
494
- if (!response.ok) {
495
- if (response.status === 401) {
496
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
497
- }
498
- const error = await response.json();
499
- throw new Error(error.error || "Failed to get context variables");
500
- }
501
- const data = await response.json();
502
- return data.contextVariables;
503
- }
504
- async function validateKpiFormula(formula, agentId, scope) {
505
- const token = getValidToken();
506
- if (!token) {
507
- throw new Error("Not logged in. Run 'olakai login' first.");
508
- }
509
- const body = { formula, agentId };
510
- if (scope) {
511
- body.scope = scope;
512
- }
513
- const response = await fetch(`${getBaseUrl()}/api/config/kpis/validate`, {
514
- method: "POST",
515
- headers: {
516
- Authorization: `Bearer ${token}`,
517
- "Content-Type": "application/json"
518
- },
519
- body: JSON.stringify(body)
520
- });
521
- if (!response.ok) {
522
- if (response.status === 401) {
523
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
524
- }
525
- const error = await response.json();
526
- throw new Error(error.error || "Failed to validate formula");
527
- }
528
- return await response.json();
529
- }
530
- async function validateMonitoringApiKey(monitoringEndpoint, apiKey) {
531
- const normalizedEndpoint = monitoringEndpoint.replace(/\/+$/, "");
532
- const url = `${normalizedEndpoint}/me`;
533
- const controller = new AbortController();
534
- const timeout = setTimeout(() => controller.abort(), 5e3);
535
- try {
536
- const response = await fetch(url, {
537
- method: "GET",
538
- headers: {
539
- "x-api-key": apiKey
540
- },
541
- signal: controller.signal
542
- });
543
- if (!response.ok) {
544
- return null;
545
- }
546
- const data = await response.json();
547
- if (typeof data?.accountId !== "string" || typeof data?.apiKeyId !== "string") {
548
- return null;
549
- }
550
- return { accountId: data.accountId, apiKeyId: data.apiKeyId };
551
- } catch {
552
- return null;
553
- } finally {
554
- clearTimeout(timeout);
555
- }
556
- }
557
- async function listActivity(options = {}) {
558
- const token = getValidToken();
559
- if (!token) {
560
- throw new Error("Not logged in. Run 'olakai login' first.");
561
- }
562
- const params = new URLSearchParams();
563
- if (options.agentId) params.set("agentId", options.agentId);
564
- if (options.workflowId) params.set("workflowId", options.workflowId);
565
- if (options.since) params.set("since", options.since);
566
- if (options.until) params.set("until", options.until);
567
- if (options.limit !== void 0) params.set("limit", String(options.limit));
568
- if (options.offset !== void 0) params.set("offset", String(options.offset));
569
- if (options.includeContent) params.set("includeContent", "true");
570
- if (options.includeAnalytics) params.set("includeAnalytics", "true");
571
- const url = `${getBaseUrl()}/api/activity/prompts${params.toString() ? `?${params}` : ""}`;
572
- const response = await fetch(url, {
573
- headers: {
574
- Authorization: `Bearer ${token}`
575
- }
576
- });
577
- if (!response.ok) {
578
- if (response.status === 401) {
579
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
580
- }
581
- const error = await response.json();
582
- throw new Error(error.error || "Failed to list activity");
583
- }
584
- return await response.json();
585
- }
586
- async function getActivity(id, includeContent) {
587
- const token = getValidToken();
588
- if (!token) {
589
- throw new Error("Not logged in. Run 'olakai login' first.");
590
- }
591
- const params = new URLSearchParams();
592
- if (includeContent) params.set("includeContent", "true");
593
- const url = `${getBaseUrl()}/api/activity/prompts/${id}${params.toString() ? `?${params}` : ""}`;
594
- const response = await fetch(url, {
595
- headers: {
596
- Authorization: `Bearer ${token}`
597
- }
598
- });
599
- if (!response.ok) {
600
- if (response.status === 401) {
601
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
602
- }
603
- if (response.status === 404) {
604
- throw new Error("Prompt request not found");
605
- }
606
- const error = await response.json();
607
- throw new Error(error.error || "Failed to get activity");
608
- }
609
- return await response.json();
610
- }
611
- async function getActivityKpis(options) {
612
- const token = getValidToken();
613
- if (!token) {
614
- throw new Error("Not logged in. Run 'olakai login' first.");
615
- }
616
- if (!options.agentId && !options.workflowId) {
617
- throw new Error("Either agentId or workflowId is required");
618
- }
619
- const params = new URLSearchParams();
620
- if (options.agentId) params.set("agentId", options.agentId);
621
- if (options.workflowId) params.set("workflowId", options.workflowId);
622
- if (options.since) params.set("since", options.since);
623
- if (options.until) params.set("until", options.until);
624
- if (options.period) params.set("period", options.period);
625
- if (options.includeAtoms) params.set("includeAtoms", "true");
626
- const url = `${getBaseUrl()}/api/activity/kpis${params.toString() ? `?${params}` : ""}`;
627
- const response = await fetch(url, {
628
- headers: {
629
- Authorization: `Bearer ${token}`
630
- }
631
- });
632
- if (!response.ok) {
633
- if (response.status === 401) {
634
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
635
- }
636
- const error = await response.json();
637
- throw new Error(error.error || "Failed to get activity KPIs");
638
- }
639
- return await response.json();
640
- }
641
- async function listSessions(options) {
642
- const token = getValidToken();
643
- if (!token) {
644
- throw new Error("Not logged in. Run 'olakai login' first.");
645
- }
646
- const params = new URLSearchParams();
647
- params.set("agentId", options.agentId);
648
- if (options.since) params.set("since", options.since);
649
- if (options.until) params.set("until", options.until);
650
- if (options.limit !== void 0) params.set("limit", String(options.limit));
651
- if (options.offset !== void 0) params.set("offset", String(options.offset));
652
- const url = `${getBaseUrl()}/api/activity/sessions?${params}`;
653
- const response = await fetch(url, {
654
- headers: {
655
- Authorization: `Bearer ${token}`
656
- }
657
- });
658
- if (!response.ok) {
659
- if (response.status === 401) {
660
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
661
- }
662
- const error = await response.json();
663
- throw new Error(error.error || "Failed to list sessions");
664
- }
665
- return await response.json();
666
- }
667
- async function listCustomDataConfigs(agentId) {
668
- const token = getValidToken();
669
- if (!token) {
670
- throw new Error("Not logged in. Run 'olakai login' first.");
671
- }
672
- const params = new URLSearchParams();
673
- if (agentId) {
674
- params.set("agentId", agentId);
675
- }
676
- const url = `${getBaseUrl()}/api/config/custom-data${params.toString() ? `?${params}` : ""}`;
677
- const response = await fetch(url, {
678
- headers: {
679
- Authorization: `Bearer ${token}`
680
- }
681
- });
682
- if (!response.ok) {
683
- if (response.status === 401) {
684
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
685
- }
686
- const error = await response.json();
687
- throw new Error(error.error || "Failed to list custom data configurations");
688
- }
689
- const data = await response.json();
690
- return data.customDataConfigs;
691
- }
692
- async function getCustomDataConfig(id) {
693
- const token = getValidToken();
694
- if (!token) {
695
- throw new Error("Not logged in. Run 'olakai login' first.");
696
- }
697
- const url = `${getBaseUrl()}/api/config/custom-data/${encodeURIComponent(id)}`;
698
- const response = await fetch(url, {
699
- headers: {
700
- Authorization: `Bearer ${token}`
701
- }
702
- });
703
- if (!response.ok) {
704
- if (response.status === 401) {
705
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
706
- }
707
- if (response.status === 404) {
708
- throw new Error("Custom data configuration not found");
709
- }
710
- const error = await response.json();
711
- throw new Error(error.error || "Failed to get custom data configuration");
712
- }
713
- return await response.json();
714
- }
715
- async function createCustomDataConfig(payload) {
716
- const token = getValidToken();
717
- if (!token) {
718
- throw new Error("Not logged in. Run 'olakai login' first.");
719
- }
720
- const url = `${getBaseUrl()}/api/config/custom-data`;
721
- const response = await fetch(url, {
722
- method: "POST",
723
- headers: {
724
- Authorization: `Bearer ${token}`,
725
- "Content-Type": "application/json"
726
- },
727
- body: JSON.stringify(payload)
728
- });
729
- if (!response.ok) {
730
- if (response.status === 401) {
731
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
732
- }
733
- if (response.status === 409) {
734
- throw new Error("A custom data configuration with this name already exists");
735
- }
736
- const error = await response.json();
737
- throw new Error(error.error || "Failed to create custom data configuration");
738
- }
739
- return await response.json();
740
- }
741
- async function updateCustomDataConfig(id, payload) {
742
- const token = getValidToken();
743
- if (!token) {
744
- throw new Error("Not logged in. Run 'olakai login' first.");
745
- }
746
- const url = `${getBaseUrl()}/api/config/custom-data/${encodeURIComponent(id)}`;
747
- const response = await fetch(url, {
748
- method: "PUT",
749
- headers: {
750
- Authorization: `Bearer ${token}`,
751
- "Content-Type": "application/json"
752
- },
753
- body: JSON.stringify(payload)
754
- });
755
- if (!response.ok) {
756
- if (response.status === 401) {
757
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
758
- }
759
- if (response.status === 404) {
760
- throw new Error("Custom data configuration not found");
761
- }
762
- if (response.status === 409) {
763
- throw new Error("A custom data configuration with this name already exists");
764
- }
765
- const error = await response.json();
766
- throw new Error(error.error || "Failed to update custom data configuration");
767
- }
768
- return await response.json();
769
- }
770
- async function deleteCustomDataConfig(id) {
771
- const token = getValidToken();
772
- if (!token) {
773
- throw new Error("Not logged in. Run 'olakai login' first.");
774
- }
775
- const url = `${getBaseUrl()}/api/config/custom-data/${encodeURIComponent(id)}`;
776
- const response = await fetch(url, {
777
- method: "DELETE",
778
- headers: {
779
- Authorization: `Bearer ${token}`
780
- }
781
- });
782
- if (!response.ok) {
783
- if (response.status === 401) {
784
- throw new Error("Session expired. Run 'olakai login' to authenticate again.");
785
- }
786
- if (response.status === 404) {
787
- throw new Error("Custom data configuration not found");
788
- }
789
- const error = await response.json();
790
- throw new Error(error.error || "Failed to delete custom data configuration");
791
- }
792
- }
793
-
794
- // src/commands/login.ts
795
82
  var POLL_INTERVAL_MS = 5e3;
796
83
  async function loginCommand() {
797
84
  if (isTokenValid()) {
@@ -997,12 +284,7 @@ async function postHandshakeJson(url, body, options = {}) {
997
284
  body: JSON.stringify(body)
998
285
  });
999
286
  } catch (err) {
1000
- return {
1001
- kind: "error",
1002
- code: "network_error",
1003
- message: err instanceof Error ? err.message : "Network request failed",
1004
- status: 0
1005
- };
287
+ return buildNetworkError(err);
1006
288
  }
1007
289
  if (response.ok) {
1008
290
  let data;
@@ -1024,7 +306,13 @@ async function postHandshakeJson(url, body, options = {}) {
1024
306
  } catch {
1025
307
  }
1026
308
  const code = mapErrorCode(response.status, errBody);
1027
- const message = errBody.message || errBody.error || `Request failed with status ${response.status}`;
309
+ let fallbackPath = "";
310
+ try {
311
+ fallbackPath = new URL(url).pathname;
312
+ } catch {
313
+ fallbackPath = url;
314
+ }
315
+ const message = errBody.message || errBody.error || `Request to ${fallbackPath} failed with status ${response.status}`;
1028
316
  const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
1029
317
  const envelope = {
1030
318
  kind: "error",
@@ -1076,6 +364,28 @@ function mapErrorCode(status, body) {
1076
364
  return "unknown_error";
1077
365
  }
1078
366
  }
367
+ function buildNetworkError(err) {
368
+ const base = err instanceof Error ? err.message : "Network request failed";
369
+ let causeCode;
370
+ let causeMessage;
371
+ if (err instanceof Error && err.cause && typeof err.cause === "object") {
372
+ const c = err.cause;
373
+ if (typeof c.code === "string" && c.code.length > 0) {
374
+ causeCode = c.code;
375
+ }
376
+ if (typeof c.message === "string" && c.message.length > 0) {
377
+ causeMessage = c.message;
378
+ }
379
+ }
380
+ const message = causeMessage && causeMessage !== base ? `${base}: ${causeMessage}` : base;
381
+ return {
382
+ kind: "error",
383
+ code: "network_error",
384
+ message,
385
+ status: 0,
386
+ ...causeCode ? { causeCode } : {}
387
+ };
388
+ }
1079
389
  function parseRetryAfter(header) {
1080
390
  if (!header) return void 0;
1081
391
  const asNumber2 = Number(header);
@@ -1110,7 +420,7 @@ function isInteractive() {
1110
420
 
1111
421
  // src/monitor/detect-all.ts
1112
422
  import * as fs15 from "fs";
1113
- import * as path13 from "path";
423
+ import * as path14 from "path";
1114
424
 
1115
425
  // src/monitor/plugins/codex/paths.ts
1116
426
  import * as os from "os";
@@ -1130,6 +440,7 @@ function getCodexSessionsDir() {
1130
440
 
1131
441
  // src/monitor/plugins/claude-code/index.ts
1132
442
  import * as fs4 from "fs";
443
+ import { spawnSync } from "child_process";
1133
444
 
1134
445
  // src/monitor/plugin.ts
1135
446
  var TOOL_IDS = [
@@ -1233,44 +544,98 @@ function shouldReportTurn(existing, currentUserTimestamp) {
1233
544
  // src/monitor/plugins/claude-code/install.ts
1234
545
  import * as fs2 from "fs";
1235
546
 
1236
- // src/monitor/validate-pasted-key.ts
1237
- async function validatePastedApiKey(opts) {
1238
- const validation = await validateMonitoringApiKey(
1239
- opts.monitoringEndpoint,
1240
- opts.apiKey
1241
- );
1242
- if (validation === null) {
547
+ // src/monitor/self-monitor-provision.ts
548
+ import path3 from "path";
549
+ async function provisionSelfMonitorAgent(opts) {
550
+ const me = await getCurrentUser();
551
+ const localPart = (me.email.split("@")[0] ?? "user").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
552
+ const workspaceName = path3.basename(opts.projectRoot).toLowerCase();
553
+ const defaultName = `${workspaceName}-${localPart}`;
554
+ let existing = [];
555
+ try {
556
+ existing = await listMyAgents({
557
+ source: opts.source,
558
+ name: defaultName
559
+ });
560
+ } catch (err) {
561
+ const message = err instanceof Error ? err.message : String(err);
562
+ if (!message.includes("Failed to list your agents")) {
563
+ }
564
+ }
565
+ if (existing.length > 0) {
566
+ const found = existing[0];
1243
567
  console.log(
1244
- "\u26A0 Could not verify the pasted API key (network or endpoint unreachable). Continuing."
568
+ `
569
+ Found your existing ${opts.displayName} agent "${found.name}".`
1245
570
  );
1246
- return;
1247
- }
1248
- if (opts.expectedApiKeyId === null) {
1249
- return;
1250
- }
1251
- if (validation.apiKeyId === opts.expectedApiKeyId) {
1252
- return;
571
+ console.log(
572
+ "Re-using it requires rotating its API key. Any other workspace currently using this agent will start failing on the next monitor request until it re-runs 'olakai monitor init'."
573
+ );
574
+ const ack = await promptUser("Rotate the API key and reuse? [y/N]: ");
575
+ if (ack.trim().toLowerCase() !== "y") {
576
+ console.error(
577
+ "Cancelled. To use this workspace without affecting the other workspace, run 'olakai monitor init' again and pick a different agent name when prompted."
578
+ );
579
+ process.exit(1);
580
+ }
581
+ const rotated = await regenerateAgentApiKey(found.id);
582
+ return {
583
+ id: found.id,
584
+ name: found.name,
585
+ description: found.description,
586
+ role: found.role,
587
+ // `source` on the listMyAgents response is the AgentSource enum
588
+ // string; the Agent type narrows it more loosely. Cast through
589
+ // the shared union.
590
+ source: found.source,
591
+ apiKey: {
592
+ id: rotated.id,
593
+ key: rotated.key,
594
+ keyMasked: rotated.keyMasked,
595
+ isActive: rotated.isActive
596
+ },
597
+ workflowId: null,
598
+ category: opts.category
599
+ };
1253
600
  }
1254
- console.log("");
1255
- console.log(
1256
- `\u26A0 The pasted API key does not belong to "${opts.expectedAgentName}" (${opts.expectedAgentId}).`
1257
- );
1258
- console.log(
1259
- ` It resolves to a different agent on this account, so monitoring events would be`
1260
- );
1261
- console.log(
1262
- ` attributed to that other agent \u2014 not "${opts.expectedAgentName}".`
1263
- );
1264
- console.log("");
1265
- const proceed = await promptUser("Use the pasted key anyway? (y/n) [n]: ");
1266
- if (proceed.trim().toLowerCase() !== "y") {
1267
- console.log("Aborted. Re-run init with the correct key for this agent.");
1268
- process.exit(1);
601
+ const nameInput = await promptUser(`Agent name [${defaultName}]: `);
602
+ const agentName = nameInput.trim() || defaultName;
603
+ try {
604
+ return await createAgent({
605
+ name: agentName,
606
+ description: `${opts.displayName} local agent for ${agentName}`,
607
+ role: "WORKER",
608
+ createApiKey: true,
609
+ category: opts.category,
610
+ source: opts.source
611
+ });
612
+ } catch (err) {
613
+ const message = err instanceof Error ? err.message : String(err);
614
+ if (message.toLowerCase().includes("already exists") || message.toLowerCase().includes("conflict")) {
615
+ console.log(
616
+ `
617
+ An agent named "${agentName}" already exists on this account (created by someone else or an admin).`
618
+ );
619
+ const retry = await promptUser("Try a different name: ");
620
+ if (!retry.trim()) {
621
+ console.error("No name provided. Aborting.");
622
+ process.exit(1);
623
+ }
624
+ return createAgent({
625
+ name: retry.trim(),
626
+ description: `${opts.displayName} local agent for ${retry.trim()}`,
627
+ role: "WORKER",
628
+ createApiKey: true,
629
+ category: opts.category,
630
+ source: opts.source
631
+ });
632
+ }
633
+ throw err;
1269
634
  }
1270
635
  }
1271
636
 
1272
637
  // src/monitor/plugins/claude-code/install.ts
1273
- import path3 from "path";
638
+ import path4 from "path";
1274
639
  var CLAUDE_CODE_SOURCE = "claude-code";
1275
640
  var CLAUDE_CODE_AGENT_SOURCE = "CLAUDE_CODE";
1276
641
  var CLAUDE_CODE_AGENT_CATEGORY = "CODING";
@@ -1282,65 +647,19 @@ async function installClaudeCode(opts) {
1282
647
  process.exit(1);
1283
648
  }
1284
649
  console.log("Setting up Claude Code monitoring for this workspace...\n");
1285
- const dirName = path3.basename(projectRoot);
1286
- let agent;
1287
- const choice = await promptUser(
1288
- "Create a new agent or use an existing one? (new/existing) [new]: "
1289
- );
1290
- if (choice.toLowerCase() === "existing" || choice.toLowerCase() === "e") {
1291
- const agents = await listAgents();
1292
- if (agents.length === 0) {
1293
- console.log("No agents found. Creating a new one instead.\n");
1294
- agent = await createNewAgent(dirName);
1295
- } else {
1296
- console.log("\nAvailable agents:");
1297
- for (let i = 0; i < agents.length; i++) {
1298
- console.log(
1299
- ` ${i + 1}. ${agents[i].name} (${agents[i].id.slice(0, 12)}...)`
1300
- );
1301
- }
1302
- const selection = await promptUser(`
1303
- Select agent (1-${agents.length}): `);
1304
- const idx = parseInt(selection, 10) - 1;
1305
- if (isNaN(idx) || idx < 0 || idx >= agents.length) {
1306
- console.error("Invalid selection.");
1307
- process.exit(1);
1308
- }
1309
- agent = await getAgent(agents[idx].id);
1310
- if (!agent.apiKey?.key && !agent.apiKey?.isActive) {
1311
- console.log(
1312
- "\nThis agent has no active API key. Please create a new agent instead,"
1313
- );
1314
- console.log("or generate an API key via the Olakai dashboard.\n");
1315
- const createNew = await promptUser("Create a new agent? (y/n) [y]: ");
1316
- if (createNew.toLowerCase() !== "n") {
1317
- agent = await createNewAgent(dirName);
1318
- } else {
1319
- process.exit(1);
1320
- }
1321
- }
1322
- }
1323
- } else {
1324
- agent = await createNewAgent(dirName);
1325
- }
650
+ const agent = await provisionSelfMonitorAgent({
651
+ projectRoot,
652
+ source: CLAUDE_CODE_AGENT_SOURCE,
653
+ displayName: "Claude Code",
654
+ category: CLAUDE_CODE_AGENT_CATEGORY
655
+ });
1326
656
  const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
1327
- let apiKey = agent.apiKey?.key;
657
+ const apiKey = agent.apiKey?.key;
1328
658
  if (!apiKey) {
1329
- console.log(
1330
- "\nThe API key for this agent is not available (it is only shown once at creation)."
659
+ console.error(
660
+ "Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
1331
661
  );
1332
- apiKey = await promptUser("Paste the API key for this agent: ");
1333
- if (!apiKey) {
1334
- console.error("API key is required for monitoring.");
1335
- process.exit(1);
1336
- }
1337
- await validatePastedApiKey({
1338
- monitoringEndpoint,
1339
- apiKey,
1340
- expectedAgentId: agent.id,
1341
- expectedAgentName: agent.name,
1342
- expectedApiKeyId: agent.apiKey?.id ?? null
1343
- });
662
+ process.exit(1);
1344
663
  }
1345
664
  const claudeDir = getClaudeDir(projectRoot);
1346
665
  if (!fs2.existsSync(claudeDir)) {
@@ -1364,7 +683,7 @@ Select agent (1-${agents.length}): `);
1364
683
  };
1365
684
  writeClaudeCodeConfig(projectRoot, monitorConfig);
1366
685
  const configPath = getClaudeCodeConfigPath(projectRoot);
1367
- const configRel = path3.relative(projectRoot, configPath);
686
+ const configRel = path4.relative(projectRoot, configPath);
1368
687
  console.log("");
1369
688
  console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
1370
689
  if (agent.apiKey?.key) {
@@ -1393,18 +712,6 @@ Select agent (1-${agents.length}): `);
1393
712
  monitoringEndpoint
1394
713
  };
1395
714
  }
1396
- async function createNewAgent(defaultName) {
1397
- const nameInput = await promptUser(`Agent name [${defaultName}]: `);
1398
- const agentName = nameInput || defaultName;
1399
- return createAgent({
1400
- name: agentName,
1401
- description: `Claude Code local agent for ${agentName}`,
1402
- role: "WORKER",
1403
- createApiKey: true,
1404
- category: CLAUDE_CODE_AGENT_CATEGORY,
1405
- source: CLAUDE_CODE_AGENT_SOURCE
1406
- });
1407
- }
1408
715
  async function uninstallClaudeCode(opts) {
1409
716
  const projectRoot = opts.projectRoot ?? process.cwd();
1410
717
  const settingsPath = getSettingsPath(projectRoot);
@@ -1433,13 +740,13 @@ async function uninstallClaudeCode(opts) {
1433
740
  }
1434
741
  if (!opts.keepConfig) {
1435
742
  const configPath = getClaudeCodeConfigPath(projectRoot);
1436
- const configRel = path3.relative(projectRoot, configPath);
743
+ const configRel = path4.relative(projectRoot, configPath);
1437
744
  if (deleteClaudeCodeConfig(projectRoot)) {
1438
745
  console.log(`\u2713 Monitor config removed (${configRel})`);
1439
746
  }
1440
747
  } else {
1441
748
  const configPath = getClaudeCodeConfigPath(projectRoot);
1442
- const configRel = path3.relative(projectRoot, configPath);
749
+ const configRel = path4.relative(projectRoot, configPath);
1443
750
  console.log(`Monitor config retained at ${configRel}`);
1444
751
  }
1445
752
  console.log("");
@@ -1813,21 +1120,37 @@ var claudeCodePlugin = {
1813
1120
  if (fs4.existsSync(getLegacyClaudeMonitorConfigPath(projectRoot))) {
1814
1121
  return true;
1815
1122
  }
1816
- return false;
1817
1123
  } catch {
1818
- return false;
1819
1124
  }
1125
+ return detectClaudeBinaryOnPath();
1820
1126
  }
1821
1127
  };
1128
+ function detectClaudeBinaryOnPath() {
1129
+ try {
1130
+ const probe = spawnSync(
1131
+ process.platform === "win32" ? "where" : "which",
1132
+ ["claude"],
1133
+ {
1134
+ stdio: ["ignore", "pipe", "ignore"],
1135
+ timeout: 1e3
1136
+ }
1137
+ );
1138
+ if (probe.status === 0 && probe.stdout && probe.stdout.toString().trim()) {
1139
+ return true;
1140
+ }
1141
+ } catch {
1142
+ }
1143
+ return false;
1144
+ }
1822
1145
  registerPlugin(claudeCodePlugin);
1823
1146
 
1824
1147
  // src/monitor/plugins/codex/index.ts
1825
1148
  import * as fs9 from "fs";
1826
- import { spawnSync } from "child_process";
1149
+ import { spawnSync as spawnSync2 } from "child_process";
1827
1150
 
1828
1151
  // src/monitor/plugins/codex/install.ts
1829
1152
  import * as fs6 from "fs";
1830
- import * as path5 from "path";
1153
+ import * as path6 from "path";
1831
1154
  import * as TOML from "@iarna/toml";
1832
1155
 
1833
1156
  // src/monitor/plugins/codex/hooks.ts
@@ -1903,7 +1226,7 @@ function hasOlakaiHooksInstalled(parsed) {
1903
1226
 
1904
1227
  // src/monitor/plugins/codex/config.ts
1905
1228
  import * as fs5 from "fs";
1906
- import * as path4 from "path";
1229
+ import * as path5 from "path";
1907
1230
  function getCodexConfigPath2(projectRoot) {
1908
1231
  return getMonitorConfigPath(projectRoot, "codex");
1909
1232
  }
@@ -1919,7 +1242,7 @@ function loadCodexConfig(projectRoot) {
1919
1242
  }
1920
1243
  function writeCodexConfig(projectRoot, config) {
1921
1244
  const filePath = getCodexConfigPath2(projectRoot);
1922
- const dir = path4.dirname(filePath);
1245
+ const dir = path5.dirname(filePath);
1923
1246
  if (!fs5.existsSync(dir)) {
1924
1247
  fs5.mkdirSync(dir, { recursive: true });
1925
1248
  }
@@ -1952,65 +1275,19 @@ async function installCodex(opts) {
1952
1275
  process.exit(1);
1953
1276
  }
1954
1277
  console.log("Setting up Codex CLI monitoring for this workspace...\n");
1955
- const dirName = path5.basename(projectRoot);
1956
- let agent;
1957
- const choice = await promptUser(
1958
- "Create a new agent or use an existing one? (new/existing) [new]: "
1959
- );
1960
- if (choice.toLowerCase() === "existing" || choice.toLowerCase() === "e") {
1961
- const agents = await listAgents();
1962
- if (agents.length === 0) {
1963
- console.log("No agents found. Creating a new one instead.\n");
1964
- agent = await createNewAgent2(dirName);
1965
- } else {
1966
- console.log("\nAvailable agents:");
1967
- for (let i = 0; i < agents.length; i++) {
1968
- console.log(
1969
- ` ${i + 1}. ${agents[i].name} (${agents[i].id.slice(0, 12)}...)`
1970
- );
1971
- }
1972
- const selection = await promptUser(`
1973
- Select agent (1-${agents.length}): `);
1974
- const idx = parseInt(selection, 10) - 1;
1975
- if (isNaN(idx) || idx < 0 || idx >= agents.length) {
1976
- console.error("Invalid selection.");
1977
- process.exit(1);
1978
- }
1979
- agent = await getAgent(agents[idx].id);
1980
- if (!agent.apiKey?.key && !agent.apiKey?.isActive) {
1981
- console.log(
1982
- "\nThis agent has no active API key. Please create a new agent instead,"
1983
- );
1984
- console.log("or generate an API key via the Olakai dashboard.\n");
1985
- const createNew = await promptUser("Create a new agent? (y/n) [y]: ");
1986
- if (createNew.toLowerCase() !== "n") {
1987
- agent = await createNewAgent2(dirName);
1988
- } else {
1989
- process.exit(1);
1990
- }
1991
- }
1992
- }
1993
- } else {
1994
- agent = await createNewAgent2(dirName);
1995
- }
1278
+ const agent = await provisionSelfMonitorAgent({
1279
+ projectRoot,
1280
+ source: CODEX_AGENT_SOURCE,
1281
+ displayName: "Codex CLI",
1282
+ category: CODEX_AGENT_CATEGORY
1283
+ });
1996
1284
  const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
1997
- let apiKey = agent.apiKey?.key;
1285
+ const apiKey = agent.apiKey?.key;
1998
1286
  if (!apiKey) {
1999
- console.log(
2000
- "\nThe API key for this agent is not available (it is only shown once at creation)."
1287
+ console.error(
1288
+ "Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
2001
1289
  );
2002
- apiKey = await promptUser("Paste the API key for this agent: ");
2003
- if (!apiKey) {
2004
- console.error("API key is required for monitoring.");
2005
- process.exit(1);
2006
- }
2007
- await validatePastedApiKey({
2008
- monitoringEndpoint,
2009
- apiKey,
2010
- expectedAgentId: agent.id,
2011
- expectedAgentName: agent.name,
2012
- expectedApiKeyId: agent.apiKey?.id ?? null
2013
- });
1290
+ process.exit(1);
2014
1291
  }
2015
1292
  const { configExisted: codexConfigExisted } = installCodexHooksConfig();
2016
1293
  const monitorConfig = {
@@ -2023,7 +1300,7 @@ Select agent (1-${agents.length}): `);
2023
1300
  };
2024
1301
  writeCodexConfig(projectRoot, monitorConfig);
2025
1302
  const configPath = getCodexConfigPath2(projectRoot);
2026
- const configRel = path5.relative(projectRoot, configPath);
1303
+ const configRel = path6.relative(projectRoot, configPath);
2027
1304
  console.log("");
2028
1305
  console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
2029
1306
  if (agent.apiKey?.key) {
@@ -2060,18 +1337,6 @@ Select agent (1-${agents.length}): `);
2060
1337
  monitoringEndpoint
2061
1338
  };
2062
1339
  }
2063
- async function createNewAgent2(defaultName) {
2064
- const nameInput = await promptUser(`Agent name [${defaultName}]: `);
2065
- const agentName = nameInput || defaultName;
2066
- return createAgent({
2067
- name: agentName,
2068
- description: `Codex CLI local agent for ${agentName}`,
2069
- role: "WORKER",
2070
- createApiKey: true,
2071
- category: CODEX_AGENT_CATEGORY,
2072
- source: CODEX_AGENT_SOURCE
2073
- });
2074
- }
2075
1340
  function installCodexHooksConfig() {
2076
1341
  const homeDir = getCodexHomeDir();
2077
1342
  const configPath = getCodexConfigPath();
@@ -2099,13 +1364,13 @@ async function uninstallCodex(opts) {
2099
1364
  }
2100
1365
  if (!opts.keepConfig) {
2101
1366
  const configPath = getCodexConfigPath2(projectRoot);
2102
- const configRel = path5.relative(projectRoot, configPath);
1367
+ const configRel = path6.relative(projectRoot, configPath);
2103
1368
  if (deleteCodexConfig(projectRoot)) {
2104
1369
  console.log(`\u2713 Monitor config removed (${configRel})`);
2105
1370
  }
2106
1371
  } else {
2107
1372
  const configPath = getCodexConfigPath2(projectRoot);
2108
- const configRel = path5.relative(projectRoot, configPath);
1373
+ const configRel = path6.relative(projectRoot, configPath);
2109
1374
  console.log(`Monitor config retained at ${configRel}`);
2110
1375
  }
2111
1376
  console.log("");
@@ -2143,7 +1408,7 @@ function readCodexConfigToml(configPath) {
2143
1408
  }
2144
1409
  }
2145
1410
  function writeCodexConfigToml(configPath, data) {
2146
- const dir = path5.dirname(configPath);
1411
+ const dir = path6.dirname(configPath);
2147
1412
  if (!fs6.existsSync(dir)) {
2148
1413
  fs6.mkdirSync(dir, { recursive: true });
2149
1414
  }
@@ -2152,7 +1417,7 @@ function writeCodexConfigToml(configPath, data) {
2152
1417
  }
2153
1418
 
2154
1419
  // src/monitor/plugins/codex/status.ts
2155
- import path6 from "path";
1420
+ import path7 from "path";
2156
1421
  import * as fs7 from "fs";
2157
1422
  async function getCodexStatus(opts) {
2158
1423
  const projectRoot = opts?.projectRoot ?? process.cwd();
@@ -2192,7 +1457,7 @@ function isHooksBlockInstalled() {
2192
1457
 
2193
1458
  // src/monitor/plugins/codex/transcript.ts
2194
1459
  import * as fs8 from "fs";
2195
- import * as path7 from "path";
1460
+ import * as path8 from "path";
2196
1461
  function emptyRollout() {
2197
1462
  return {
2198
1463
  prompt: "",
@@ -2232,7 +1497,7 @@ function findRolloutPathForSession(sessionId, sessionsDir = getCodexSessionsDir(
2232
1497
  }
2233
1498
  for (const entry of entries) {
2234
1499
  if (filesScanned++ > merged.maxFiles) break;
2235
- const full = path7.join(dir, entry.name);
1500
+ const full = path8.join(dir, entry.name);
2236
1501
  if (entry.isDirectory()) {
2237
1502
  queue.push(full);
2238
1503
  continue;
@@ -2526,7 +1791,7 @@ var codexPlugin = {
2526
1791
  };
2527
1792
  function detectCodexBinaryOnPath() {
2528
1793
  try {
2529
- const probe = spawnSync(
1794
+ const probe = spawnSync2(
2530
1795
  process.platform === "win32" ? "where" : "which",
2531
1796
  ["codex"],
2532
1797
  {
@@ -2550,11 +1815,11 @@ import * as os7 from "os";
2550
1815
  // src/monitor/plugins/cursor/install.ts
2551
1816
  import * as fs11 from "fs";
2552
1817
  import * as os4 from "os";
2553
- import * as path10 from "path";
1818
+ import * as path11 from "path";
2554
1819
 
2555
1820
  // src/monitor/plugins/cursor/config.ts
2556
1821
  import * as fs10 from "fs";
2557
- import * as path8 from "path";
1822
+ import * as path9 from "path";
2558
1823
  function getCursorConfigPath(projectRoot) {
2559
1824
  return getMonitorConfigPath(projectRoot, "cursor");
2560
1825
  }
@@ -2570,7 +1835,7 @@ function loadCursorConfig(projectRoot) {
2570
1835
  }
2571
1836
  function writeCursorConfig(projectRoot, config) {
2572
1837
  const filePath = getCursorConfigPath(projectRoot);
2573
- const dir = path8.dirname(filePath);
1838
+ const dir = path9.dirname(filePath);
2574
1839
  if (!fs10.existsSync(dir)) {
2575
1840
  fs10.mkdirSync(dir, { recursive: true });
2576
1841
  }
@@ -2593,14 +1858,14 @@ function deleteCursorConfig(projectRoot) {
2593
1858
 
2594
1859
  // src/monitor/plugins/cursor/paths.ts
2595
1860
  import * as os3 from "os";
2596
- import * as path9 from "path";
1861
+ import * as path10 from "path";
2597
1862
  var CURSOR_DIR_NAME = ".cursor";
2598
1863
  var CURSOR_HOOKS_FILE = "hooks.json";
2599
1864
  function getCursorUserDir(homeDir = os3.homedir()) {
2600
- return path9.join(homeDir, CURSOR_DIR_NAME);
1865
+ return path10.join(homeDir, CURSOR_DIR_NAME);
2601
1866
  }
2602
1867
  function getCursorHooksPath(homeDir = os3.homedir()) {
2603
- return path9.join(getCursorUserDir(homeDir), CURSOR_HOOKS_FILE);
1868
+ return path10.join(getCursorUserDir(homeDir), CURSOR_HOOKS_FILE);
2604
1869
  }
2605
1870
 
2606
1871
  // src/monitor/plugins/cursor/hooks-config.ts
@@ -2698,7 +1963,7 @@ function readJsonFileTolerant(filePath) {
2698
1963
  }
2699
1964
  }
2700
1965
  function writeJsonFileWithDir(filePath, data) {
2701
- const dir = path10.dirname(filePath);
1966
+ const dir = path11.dirname(filePath);
2702
1967
  if (!fs11.existsSync(dir)) {
2703
1968
  fs11.mkdirSync(dir, { recursive: true });
2704
1969
  }
@@ -2720,26 +1985,19 @@ async function installCursor(opts) {
2720
1985
  `Activity from this workspace (${projectRoot}) will be associated with`
2721
1986
  );
2722
1987
  console.log("the agent you select below.\n");
2723
- const dirName = path10.basename(projectRoot);
2724
- const agent = await chooseOrCreateAgent(dirName);
1988
+ const agent = await provisionSelfMonitorAgent({
1989
+ projectRoot,
1990
+ source: CURSOR_AGENT_SOURCE,
1991
+ displayName: "Cursor",
1992
+ category: CURSOR_AGENT_CATEGORY
1993
+ });
2725
1994
  const monitoringEndpoint = `${getBaseUrl()}/api/monitoring/prompt`;
2726
- let apiKey = agent.apiKey?.key;
1995
+ const apiKey = agent.apiKey?.key;
2727
1996
  if (!apiKey) {
2728
- console.log(
2729
- "\nThe API key for this agent is not available (it is only shown once at creation)."
1997
+ console.error(
1998
+ "Internal error: agent provisioned but no API key returned. Please re-run 'olakai monitor init'."
2730
1999
  );
2731
- apiKey = await promptUser("Paste the API key for this agent: ");
2732
- if (!apiKey) {
2733
- console.error("API key is required for monitoring.");
2734
- process.exit(1);
2735
- }
2736
- await validatePastedApiKey({
2737
- monitoringEndpoint,
2738
- apiKey,
2739
- expectedAgentId: agent.id,
2740
- expectedAgentName: agent.name,
2741
- expectedApiKeyId: agent.apiKey?.id ?? null
2742
- });
2000
+ process.exit(1);
2743
2001
  }
2744
2002
  const cursorDir = getCursorUserDir(homeDir);
2745
2003
  if (!fs11.existsSync(cursorDir)) {
@@ -2759,8 +2017,8 @@ async function installCursor(opts) {
2759
2017
  };
2760
2018
  writeCursorConfig(projectRoot, monitorConfig);
2761
2019
  const configPath = getCursorConfigPath(projectRoot);
2762
- const configRel = path10.relative(projectRoot, configPath);
2763
- const hooksDisplay = `~/${path10.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`;
2020
+ const configRel = path11.relative(projectRoot, configPath);
2021
+ const hooksDisplay = `~/${path11.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`;
2764
2022
  console.log("");
2765
2023
  console.log(`\u2713 Agent "${agent.name}" configured (ID: ${agent.id})`);
2766
2024
  if (agent.apiKey?.key) {
@@ -2787,57 +2045,6 @@ async function installCursor(opts) {
2787
2045
  monitoringEndpoint
2788
2046
  };
2789
2047
  }
2790
- async function chooseOrCreateAgent(defaultName) {
2791
- const choice = await promptUser(
2792
- "Create a new agent or use an existing one? (new/existing) [new]: "
2793
- );
2794
- if (choice.toLowerCase() === "existing" || choice.toLowerCase() === "e") {
2795
- const agents = await listAgents();
2796
- if (agents.length === 0) {
2797
- console.log("No agents found. Creating a new one instead.\n");
2798
- return createNewAgent3(defaultName);
2799
- }
2800
- console.log("\nAvailable agents:");
2801
- for (let i = 0; i < agents.length; i++) {
2802
- console.log(
2803
- ` ${i + 1}. ${agents[i].name} (${agents[i].id.slice(0, 12)}...)`
2804
- );
2805
- }
2806
- const selection = await promptUser(`
2807
- Select agent (1-${agents.length}): `);
2808
- const idx = parseInt(selection, 10) - 1;
2809
- if (isNaN(idx) || idx < 0 || idx >= agents.length) {
2810
- console.error("Invalid selection.");
2811
- process.exit(1);
2812
- }
2813
- const fetched = await getAgent(agents[idx].id);
2814
- if (!fetched.apiKey?.key && !fetched.apiKey?.isActive) {
2815
- console.log(
2816
- "\nThis agent has no active API key. Please create a new agent instead,"
2817
- );
2818
- console.log("or generate an API key via the Olakai dashboard.\n");
2819
- const createNew = await promptUser("Create a new agent? (y/n) [y]: ");
2820
- if (createNew.toLowerCase() !== "n") {
2821
- return createNewAgent3(defaultName);
2822
- }
2823
- process.exit(1);
2824
- }
2825
- return fetched;
2826
- }
2827
- return createNewAgent3(defaultName);
2828
- }
2829
- async function createNewAgent3(defaultName) {
2830
- const nameInput = await promptUser(`Agent name [${defaultName}]: `);
2831
- const agentName = nameInput || defaultName;
2832
- return createAgent({
2833
- name: agentName,
2834
- description: `Cursor local agent for ${agentName}`,
2835
- role: "WORKER",
2836
- createApiKey: true,
2837
- category: CURSOR_AGENT_CATEGORY,
2838
- source: CURSOR_AGENT_SOURCE
2839
- });
2840
- }
2841
2048
  async function uninstallCursor(opts) {
2842
2049
  const projectRoot = opts.projectRoot ?? process.cwd();
2843
2050
  const homeDir = opts.homeDir ?? os4.homedir();
@@ -2862,20 +2069,20 @@ async function uninstallCursor(opts) {
2862
2069
  writeJsonFileWithDir(hooksPath, cleaned);
2863
2070
  }
2864
2071
  console.log(
2865
- `\u2713 Olakai hooks removed from ~/${path10.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`
2072
+ `\u2713 Olakai hooks removed from ~/${path11.join(CURSOR_DIR_NAME, CURSOR_HOOKS_FILE)}`
2866
2073
  );
2867
2074
  } else {
2868
2075
  console.log("No Cursor hooks file found.");
2869
2076
  }
2870
2077
  if (!opts.keepConfig) {
2871
2078
  const configPath = getCursorConfigPath(projectRoot);
2872
- const configRel = path10.relative(projectRoot, configPath);
2079
+ const configRel = path11.relative(projectRoot, configPath);
2873
2080
  if (deleteCursorConfig(projectRoot)) {
2874
2081
  console.log(`\u2713 Monitor config removed (${configRel})`);
2875
2082
  }
2876
2083
  } else {
2877
2084
  const configPath = getCursorConfigPath(projectRoot);
2878
- const configRel = path10.relative(projectRoot, configPath);
2085
+ const configRel = path11.relative(projectRoot, configPath);
2879
2086
  console.log(`Monitor config retained at ${configRel}`);
2880
2087
  }
2881
2088
  console.log("");
@@ -2888,7 +2095,7 @@ async function uninstallCursor(opts) {
2888
2095
  // src/monitor/plugins/cursor/status.ts
2889
2096
  import * as fs12 from "fs";
2890
2097
  import * as os5 from "os";
2891
- import * as path11 from "path";
2098
+ import * as path12 from "path";
2892
2099
  async function getCursorStatus(opts) {
2893
2100
  const projectRoot = opts?.projectRoot ?? process.cwd();
2894
2101
  const homeDir = opts?.homeDir ?? os5.homedir();
@@ -3080,10 +2287,10 @@ function extractCursorMeta(payload) {
3080
2287
  // src/monitor/plugins/cursor/pairing-state.ts
3081
2288
  import * as fs13 from "fs";
3082
2289
  import * as os6 from "os";
3083
- import * as path12 from "path";
2290
+ import * as path13 from "path";
3084
2291
  var STATE_DIR_SEGMENTS2 = [".olakai", "cursor-pairings"];
3085
2292
  function getPairingsDir(homeDir) {
3086
- return path12.join(homeDir, ...STATE_DIR_SEGMENTS2);
2293
+ return path13.join(homeDir, ...STATE_DIR_SEGMENTS2);
3087
2294
  }
3088
2295
  function sanitizeKeyFragment(value) {
3089
2296
  return value.replace(/[^A-Za-z0-9._-]/g, "_");
@@ -3094,7 +2301,7 @@ function getPairingKey(conversationId, generationId) {
3094
2301
  return `${conv}__${sanitizeKeyFragment(generationId)}`;
3095
2302
  }
3096
2303
  function getPairingFile(conversationId, generationId, homeDir) {
3097
- return path12.join(
2304
+ return path13.join(
3098
2305
  getPairingsDir(homeDir),
3099
2306
  `${getPairingKey(conversationId, generationId)}.json`
3100
2307
  );
@@ -3160,7 +2367,7 @@ function listPendingPrompts(homeDir = os6.homedir()) {
3160
2367
  const result = [];
3161
2368
  for (const name of files) {
3162
2369
  if (!name.endsWith(".json")) continue;
3163
- const filePath = path12.join(dir, name);
2370
+ const filePath = path13.join(dir, name);
3164
2371
  try {
3165
2372
  const raw = fs13.readFileSync(filePath, "utf-8");
3166
2373
  const parsed = JSON.parse(raw);
@@ -3476,7 +2683,7 @@ function describeDetection(plugin, projectRoot) {
3476
2683
  } catch {
3477
2684
  }
3478
2685
  try {
3479
- const settings = path13.join(projectRoot, ".claude", "settings.json");
2686
+ const settings = path14.join(projectRoot, ".claude", "settings.json");
3480
2687
  if (fs15.existsSync(settings)) {
3481
2688
  return "found .claude/settings.json";
3482
2689
  }
@@ -3564,26 +2771,34 @@ async function initCommand(options) {
3564
2771
  try {
3565
2772
  const resolved = resolveProfileName();
3566
2773
  const profileName = resolved?.name ?? "default";
3567
- const host = await resolveHost(profileName, options, interactive);
3568
- setHostOverride(host);
2774
+ let optedReAuth = false;
3569
2775
  if (isTokenValid()) {
3570
2776
  const file = readProfilesFile();
3571
2777
  const existing = file.profiles[profileName];
3572
2778
  const who = existing?.email ?? "unknown";
2779
+ const where = existing?.host ?? "(unknown host)";
3573
2780
  if (options.nonInteractive) {
3574
2781
  console.log(
3575
- `Already authenticated as ${who} on ${host} (profile: ${profileName}).`
2782
+ `Already authenticated as ${who} on ${where} (profile: ${profileName}).`
3576
2783
  );
3577
2784
  return;
3578
2785
  }
3579
2786
  const answer = await promptUser(
3580
- `Already authenticated as ${who} on ${host} (profile: ${profileName}). Re-authenticate? [y/N]: `
2787
+ `Already authenticated as ${who} on ${where} (profile: ${profileName}). Re-authenticate? [y/N]: `
3581
2788
  );
3582
2789
  if (answer.trim().toLowerCase() !== "y") {
3583
2790
  console.log("Keeping existing credentials. Run 'olakai logout' to sign out.");
3584
2791
  return;
3585
2792
  }
2793
+ optedReAuth = true;
3586
2794
  }
2795
+ const host = await resolveHost(
2796
+ profileName,
2797
+ options,
2798
+ interactive,
2799
+ optedReAuth
2800
+ );
2801
+ setHostOverride(host);
3587
2802
  const email = await resolveEmail(options, interactive);
3588
2803
  console.log(`
3589
2804
  Contacting ${host} for ${email}...`);
@@ -3684,7 +2899,7 @@ function isSafeOpenUrl(raw) {
3684
2899
  return false;
3685
2900
  }
3686
2901
  }
3687
- async function resolveHost(profileName, options, interactive) {
2902
+ async function resolveHost(profileName, options, interactive, forceHostPrompt = false) {
3688
2903
  const fromFlag = options.host?.trim() || process.env.OLAKAI_HOST?.trim();
3689
2904
  if (fromFlag) {
3690
2905
  return getBaseUrl();
@@ -3692,7 +2907,8 @@ async function resolveHost(profileName, options, interactive) {
3692
2907
  const file = readProfilesFile();
3693
2908
  const existing = file.profiles[profileName];
3694
2909
  if (existing?.host) {
3695
- if (!interactive || isTokenValid()) {
2910
+ const shouldOfferSwitch = interactive && (forceHostPrompt || !isTokenValid());
2911
+ if (!shouldOfferSwitch) {
3696
2912
  return existing.host;
3697
2913
  }
3698
2914
  const keep = await promptUser(
@@ -3790,7 +3006,11 @@ We sent a 6-digit code to ${ctx.emailObfuscated} (expires in ${expiryMin} min).`
3790
3006
  console.log("Codes are 6 digits. Try again.");
3791
3007
  continue;
3792
3008
  }
3793
- const result = await postHandshakeVerify({ email, code: trimmed });
3009
+ let result = await postHandshakeVerify({ email, code: trimmed });
3010
+ if (result.kind === "error" && result.code === "network_error") {
3011
+ console.log("(Network error verifying code \u2014 retrying once...)");
3012
+ result = await postHandshakeVerify({ email, code: trimmed });
3013
+ }
3794
3014
  if (result.kind === "ok") {
3795
3015
  const consentToken = result.data.consentToken;
3796
3016
  await runExchange(consentToken, profileName, email);
@@ -3829,7 +3049,9 @@ We sent a 6-digit code to ${ctx.emailObfuscated} (expires in ${expiryMin} min).`
3829
3049
  );
3830
3050
  throw new InitAbortedError("", 1);
3831
3051
  case "network_error":
3832
- console.error(`Network error: ${result.message}`);
3052
+ console.error(
3053
+ `Network error${result.causeCode ? ` (${result.causeCode})` : ""}: ${result.message}`
3054
+ );
3833
3055
  throw new InitAbortedError("", 1);
3834
3056
  default:
3835
3057
  console.error(
@@ -3840,7 +3062,11 @@ We sent a 6-digit code to ${ctx.emailObfuscated} (expires in ${expiryMin} min).`
3840
3062
  }
3841
3063
  }
3842
3064
  async function runExchange(consentToken, profileName, email) {
3843
- const result = await postHandshakeExchange({ consentToken });
3065
+ let result = await postHandshakeExchange({ consentToken });
3066
+ if (result.kind === "error" && result.code === "network_error") {
3067
+ console.log("(Network error exchanging consent \u2014 retrying once...)");
3068
+ result = await postHandshakeExchange({ consentToken });
3069
+ }
3844
3070
  if (result.kind === "error") {
3845
3071
  switch (result.code) {
3846
3072
  case "invalid_consent":
@@ -3863,6 +3089,11 @@ async function runExchange(consentToken, profileName, email) {
3863
3089
  "Olakai is temporarily unavailable. Try again in a minute."
3864
3090
  );
3865
3091
  break;
3092
+ case "network_error":
3093
+ console.error(
3094
+ `Network error exchanging consent${result.causeCode ? ` (${result.causeCode})` : ""}: ${result.message}`
3095
+ );
3096
+ break;
3866
3097
  default:
3867
3098
  console.error(`Exchange failed (${result.code}): ${result.message}`);
3868
3099
  }
@@ -3897,7 +3128,9 @@ function handleHandshakeError(err) {
3897
3128
  console.error("Olakai is temporarily unavailable. Try again in a minute.");
3898
3129
  break;
3899
3130
  case "network_error":
3900
- console.error(`Network error contacting Olakai: ${err.message}`);
3131
+ console.error(
3132
+ `Network error contacting Olakai${err.causeCode ? ` (${err.causeCode})` : ""}: ${err.message}`
3133
+ );
3901
3134
  break;
3902
3135
  default:
3903
3136
  console.error(`Handshake failed (${err.code}): ${err.message}`);
@@ -5303,7 +4536,7 @@ async function statusCommand(options) {
5303
4536
  return;
5304
4537
  }
5305
4538
  if (plugin.id === "claude-code") {
5306
- const { printClaudeCodeStatus } = await import("./status-NK4PX2NS.js");
4539
+ const { printClaudeCodeStatus } = await import("./status-USHUUHK6.js");
5307
4540
  await printClaudeCodeStatus({ projectRoot: process.cwd() });
5308
4541
  return;
5309
4542
  }
@@ -5319,6 +4552,24 @@ async function statusCommand(options) {
5319
4552
  async function disableCommand(options) {
5320
4553
  const toolId = await resolveToolFromOptions(options.tool, "disable");
5321
4554
  const plugin = getPlugin(toolId);
4555
+ let agentIdToDelete;
4556
+ if (options.deleteAgent) {
4557
+ try {
4558
+ const report = await plugin.status({ projectRoot: process.cwd() });
4559
+ if (report.configured && report.agentId) {
4560
+ agentIdToDelete = report.agentId;
4561
+ } else {
4562
+ console.log(
4563
+ "No monitor config found in this workspace \u2014 nothing to delete remotely."
4564
+ );
4565
+ }
4566
+ } catch (err) {
4567
+ const msg = err instanceof Error ? err.message : String(err);
4568
+ console.warn(
4569
+ `Couldn't read local monitor config to identify the agent (${msg}). Proceeding with local-only uninstall.`
4570
+ );
4571
+ }
4572
+ }
5322
4573
  await runPluginAction(
5323
4574
  plugin,
5324
4575
  () => plugin.uninstall({
@@ -5326,6 +4577,22 @@ async function disableCommand(options) {
5326
4577
  keepConfig: options.keepConfig
5327
4578
  })
5328
4579
  );
4580
+ if (agentIdToDelete) {
4581
+ const { deleteAgent: deleteAgent2 } = await import("./api-63UDJX5M.js");
4582
+ try {
4583
+ await deleteAgent2(agentIdToDelete);
4584
+ console.log(`\u2713 Remote agent ${agentIdToDelete} deleted.`);
4585
+ } catch (err) {
4586
+ const msg = err instanceof Error ? err.message : String(err);
4587
+ console.error(
4588
+ `Local uninstall succeeded, but remote agent delete failed: ${msg}`
4589
+ );
4590
+ console.error(
4591
+ `You can retry by visiting the Olakai dashboard, or rerun 'olakai monitor disable --tool ${toolId} --delete-agent' (the local config is gone, so the agent id won't be re-discovered \u2014 use the dashboard).`
4592
+ );
4593
+ process.exit(1);
4594
+ }
4595
+ }
5329
4596
  }
5330
4597
  async function runPluginAction(plugin, fn) {
5331
4598
  try {
@@ -5450,6 +4717,9 @@ function registerMonitorCommand(program2) {
5450
4717
  ).option(
5451
4718
  "--keep-config",
5452
4719
  "Keep the monitor config file (only remove hooks)"
4720
+ ).option(
4721
+ "--delete-agent",
4722
+ "Also delete the remote agent on the Olakai backend. Useful when retiring a workspace permanently. Self-monitor owner or ADMIN only."
5453
4723
  ).action(disableCommand);
5454
4724
  for (const plugin of listPlugins()) {
5455
4725
  plugin.registerCommands?.(monitor);