@lore-co/cli 0.1.18 → 0.1.19
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 +38 -5
- package/dist/cli.d.ts +29 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +507 -43
- package/dist/cli.js.map +1 -1
- package/dist/generated-assets.d.ts +4 -4
- package/dist/generated-assets.js +4 -4
- package/dist/runtime.d.ts +9 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +74 -3
- package/dist/runtime.js.map +1 -1
- package/dist/self-host.d.ts +15 -2
- package/dist/self-host.d.ts.map +1 -1
- package/dist/self-host.js +55 -8
- package/dist/self-host.js.map +1 -1
- package/dist/update.js +3 -3
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { access, chmod, copyFile, mkdir, open, readFile, readdir, rename, rm, stat, writeFile, } from "node:fs/promises";
|
|
3
3
|
import { constants as fsConstants, realpathSync } from "node:fs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
5
6
|
import { delimiter, dirname, resolve } from "node:path";
|
|
6
|
-
import { homedir, platform } from "node:os";
|
|
7
|
+
import { homedir, hostname, platform } from "node:os";
|
|
8
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
7
9
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
8
|
-
import { CLAUDE_RELIABILITY_INTEGRATION, CODEX_RELIABILITY_INTEGRATION, CURSOR_RELIABILITY_INTEGRATION, NATIVE_CODING_AGENT_NAMES, OPENCODE_RELIABILITY_INTEGRATION, OperationalMetricsResponseSchema, POLYTOKEN_RELIABILITY_INTEGRATION, RELIABILITY_INTEGRATION_CONTRACT_VERSION, ReliabilityInvocationRecordSchema, WorkspaceIdentityResponseSchema, canonicalJson, deriveReliabilityInvocationHealth, isNativeCodingAgent, } from "@lore-co/core";
|
|
10
|
+
import { CLAUDE_RELIABILITY_INTEGRATION, CODEX_RELIABILITY_INTEGRATION, COPILOT_CLI_RELIABILITY_INTEGRATION, COPILOT_CLOUD_MCP_RELIABILITY_INTEGRATION, COPILOT_VSCODE_PREVIEW_RELIABILITY_INTEGRATION, CreateDeviceAuthorizationResponseSchema, CURSOR_RELIABILITY_INTEGRATION, NATIVE_CODING_AGENT_NAMES, OPENCODE_RELIABILITY_INTEGRATION, OperationalMetricsResponseSchema, PollDeviceAuthorizationResponseSchema, POLYTOKEN_RELIABILITY_INTEGRATION, RELIABILITY_INTEGRATION_CONTRACT_VERSION, ReliabilityInvocationRecordSchema, WorkspaceIdentityResponseSchema, canonicalJson, deriveReliabilityInvocationHealth, isNativeCodingAgent, } from "@lore-co/core";
|
|
9
11
|
import { runHook, } from "./runtime.js";
|
|
10
12
|
import { readInvocationHealthWriteInput, runInvocationHealthWrite, } from "./invocation-health-writer.js";
|
|
11
13
|
import { runAskCommand } from "./ask.js";
|
|
@@ -44,9 +46,26 @@ const POLYTOKEN_HOOK_EVENTS = [
|
|
|
44
46
|
"pre_user_prompt",
|
|
45
47
|
"post_model_turn",
|
|
46
48
|
];
|
|
49
|
+
const COPILOT_CLI_HOOK_EVENTS = [
|
|
50
|
+
"userPromptTransformed",
|
|
51
|
+
"preToolUse",
|
|
52
|
+
"agentStop",
|
|
53
|
+
"sessionEnd",
|
|
54
|
+
];
|
|
55
|
+
const EXTERNAL_SURFACES = [
|
|
56
|
+
{
|
|
57
|
+
surface: "copilot-cloud-mcp",
|
|
58
|
+
integrationId: COPILOT_CLOUD_MCP_RELIABILITY_INTEGRATION.integrationId,
|
|
59
|
+
installation: "repository_template",
|
|
60
|
+
locallyInspectable: false,
|
|
61
|
+
capabilities: COPILOT_CLOUD_MCP_RELIABILITY_INTEGRATION.capabilities,
|
|
62
|
+
},
|
|
63
|
+
];
|
|
47
64
|
const AGENT_MANIFESTS = {
|
|
48
65
|
claude: CLAUDE_RELIABILITY_INTEGRATION,
|
|
49
66
|
codex: CODEX_RELIABILITY_INTEGRATION,
|
|
67
|
+
"copilot-cli": COPILOT_CLI_RELIABILITY_INTEGRATION,
|
|
68
|
+
"copilot-vscode": COPILOT_VSCODE_PREVIEW_RELIABILITY_INTEGRATION,
|
|
50
69
|
cursor: CURSOR_RELIABILITY_INTEGRATION,
|
|
51
70
|
opencode: OPENCODE_RELIABILITY_INTEGRATION,
|
|
52
71
|
polytoken: POLYTOKEN_RELIABILITY_INTEGRATION,
|
|
@@ -102,21 +121,26 @@ Examples:
|
|
|
102
121
|
lore devin --help
|
|
103
122
|
`;
|
|
104
123
|
const CONNECT_HELP = `lore connect
|
|
105
|
-
|
|
124
|
+
Authorize this device and idempotently install agent integrations.
|
|
106
125
|
|
|
107
126
|
Usage:
|
|
127
|
+
lore connect --login [options]
|
|
108
128
|
lore connect --token <token> [options]
|
|
109
129
|
|
|
110
130
|
Options:
|
|
111
131
|
--url <url> Lore API base URL (default: https://api.uselore.co)
|
|
112
132
|
--dashboard-url <url> Lore dashboard URL (default: https://uselore.co for hosted Lore)
|
|
133
|
+
--login Approve access securely in the Lore dashboard
|
|
134
|
+
--no-open Print the approval URL without opening a browser
|
|
113
135
|
--token <token> Workspace bearer token (or LORE_WORKSPACE_TOKEN/LORE_TOKEN)
|
|
114
|
-
--agent <name> claude, codex, cursor, opencode, polytoken, or t3code
|
|
136
|
+
--agent <name> claude, codex, copilot, copilot-cli, copilot-vscode, cowork, cursor, opencode, polytoken, or t3code
|
|
115
137
|
--timeout-ms <ms> Hook request timeout, 250-10000 (default: 2500)
|
|
116
138
|
--json Print machine-readable output
|
|
117
139
|
--help Show this command's help
|
|
118
140
|
|
|
119
141
|
Examples:
|
|
142
|
+
lore connect --login --agent claude
|
|
143
|
+
lore connect --login --agent cowork
|
|
120
144
|
lore connect --token "$LORE_WORKSPACE_TOKEN" --agent claude
|
|
121
145
|
lore connect --url http://localhost:3004 --token dev-token --agent codex
|
|
122
146
|
lore connect --token "$LORE_WORKSPACE_TOKEN" --agent cursor
|
|
@@ -167,6 +191,7 @@ export function getLorePaths(home, environment = process.env) {
|
|
|
167
191
|
? resolve(resolvedHome, ".config")
|
|
168
192
|
: resolve(environment.XDG_CONFIG_HOME);
|
|
169
193
|
const t3Home = resolve(environment.T3CODE_HOME?.trim() || resolve(resolvedHome, ".t3"));
|
|
194
|
+
const copilotHome = resolve(environment.COPILOT_HOME?.trim() || resolve(resolvedHome, ".copilot"));
|
|
170
195
|
return {
|
|
171
196
|
home: resolvedHome,
|
|
172
197
|
loreDirectory,
|
|
@@ -183,6 +208,9 @@ export function getLorePaths(home, environment = process.env) {
|
|
|
183
208
|
state: resolve(loreDirectory, "state"),
|
|
184
209
|
queue: resolve(loreDirectory, "queue"),
|
|
185
210
|
codexHooks: resolve(resolvedHome, ".codex", "hooks.json"),
|
|
211
|
+
copilotCliHooks: resolve(copilotHome, "hooks", "lore.json"),
|
|
212
|
+
copilotVscodeHooks: resolve(environment.LORE_COPILOT_VSCODE_HOOKS?.trim() ||
|
|
213
|
+
resolve(process.cwd(), ".github", "hooks", "lore-vscode.json")),
|
|
186
214
|
claudeSettings: resolve(resolvedHome, ".claude", "settings.json"),
|
|
187
215
|
cursorHooks: resolve(resolvedHome, ".cursor", "hooks.json"),
|
|
188
216
|
cursorUserData: platform() === "darwin"
|
|
@@ -200,14 +228,18 @@ function isConfiguredAgent(value) {
|
|
|
200
228
|
return typeof value === "string" && isNativeCodingAgent(value);
|
|
201
229
|
}
|
|
202
230
|
function isConnectAgent(value) {
|
|
203
|
-
return isConfiguredAgent(value) ||
|
|
231
|
+
return (isConfiguredAgent(value) ||
|
|
232
|
+
value === "copilot" ||
|
|
233
|
+
value === "cowork" ||
|
|
234
|
+
value === "t3code");
|
|
204
235
|
}
|
|
205
|
-
function hookCommand(agent, paths) {
|
|
236
|
+
function hookCommand(agent, paths, event) {
|
|
206
237
|
const loreHome = `LORE_HOME=${shellQuote(paths.home)}`;
|
|
238
|
+
const hookEvent = event === undefined ? "" : ` LORE_HOOK_EVENT=${shellQuote(event)}`;
|
|
207
239
|
if (IS_STANDALONE_BINARY) {
|
|
208
|
-
return `env -u BUN_OPTIONS -u BUN_BE_BUN ${loreHome} ${shellQuote(process.execPath)} hook --agent ${agent} ${LORE_OWNER_ARGUMENT}`;
|
|
240
|
+
return `env -u BUN_OPTIONS -u BUN_BE_BUN ${loreHome}${hookEvent} ${shellQuote(process.execPath)} hook --agent ${agent} ${LORE_OWNER_ARGUMENT}`;
|
|
209
241
|
}
|
|
210
|
-
return `env ${loreHome} ${shellQuote(process.execPath)} ${shellQuote(paths.runtime)} --agent ${agent} ${LORE_OWNER_ARGUMENT}`;
|
|
242
|
+
return `env ${loreHome}${hookEvent} ${shellQuote(process.execPath)} ${shellQuote(paths.runtime)} --agent ${agent} ${LORE_OWNER_ARGUMENT}`;
|
|
211
243
|
}
|
|
212
244
|
function isLoreHook(value) {
|
|
213
245
|
if (!isObject(value) || value.type !== "command") {
|
|
@@ -396,6 +428,169 @@ export function countLoreCursorHooks(input) {
|
|
|
396
428
|
return count + hooks[event].filter(isLoreHook).length;
|
|
397
429
|
}, 0);
|
|
398
430
|
}
|
|
431
|
+
function copilotCliEventHandler(event, paths) {
|
|
432
|
+
return {
|
|
433
|
+
type: "command",
|
|
434
|
+
bash: hookCommand("copilot-cli", paths, event),
|
|
435
|
+
timeoutSec: event === "sessionEnd"
|
|
436
|
+
? 2
|
|
437
|
+
: event === "agentStop"
|
|
438
|
+
? 3
|
|
439
|
+
: event === "preToolUse"
|
|
440
|
+
? 10
|
|
441
|
+
: 25,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function isLoreCopilotHook(value) {
|
|
445
|
+
if (!isObject(value) || value.type !== "command") {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
const command = typeof value.bash === "string"
|
|
449
|
+
? value.bash
|
|
450
|
+
: typeof value.command === "string"
|
|
451
|
+
? value.command
|
|
452
|
+
: "";
|
|
453
|
+
return (/(?:^|\s)--owner(?:=|\s+)lore(?:\s|$)/u.test(command) ||
|
|
454
|
+
command.includes("/.lore/bin/lore-hook.mjs"));
|
|
455
|
+
}
|
|
456
|
+
function stripLoreFromCopilotEvent(value) {
|
|
457
|
+
return Array.isArray(value)
|
|
458
|
+
? value.filter((hook) => !isLoreCopilotHook(hook))
|
|
459
|
+
: [];
|
|
460
|
+
}
|
|
461
|
+
export function mergeLoreCopilotCliHooks(input, paths) {
|
|
462
|
+
const result = cloneObject(input);
|
|
463
|
+
if (result.version !== undefined && result.version !== 1) {
|
|
464
|
+
throw new Error('Copilot hook configuration field "version" must be 1');
|
|
465
|
+
}
|
|
466
|
+
if (result.hooks !== undefined && !isObject(result.hooks)) {
|
|
467
|
+
throw new Error('Copilot hook configuration field "hooks" must be an object');
|
|
468
|
+
}
|
|
469
|
+
const hooks = isObject(result.hooks) ? { ...result.hooks } : {};
|
|
470
|
+
for (const event of COPILOT_CLI_HOOK_EVENTS) {
|
|
471
|
+
if (hooks[event] !== undefined && !Array.isArray(hooks[event])) {
|
|
472
|
+
throw new Error(`Copilot hook event "${event}" must be an array`);
|
|
473
|
+
}
|
|
474
|
+
hooks[event] = [
|
|
475
|
+
...stripLoreFromCopilotEvent(hooks[event]),
|
|
476
|
+
copilotCliEventHandler(event, paths),
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
result.version = 1;
|
|
480
|
+
result.hooks = hooks;
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
export function removeLoreCopilotCliHooks(input) {
|
|
484
|
+
const result = cloneObject(input);
|
|
485
|
+
if (!isObject(result.hooks)) {
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
const hooks = { ...result.hooks };
|
|
489
|
+
for (const event of COPILOT_CLI_HOOK_EVENTS) {
|
|
490
|
+
if (!Array.isArray(hooks[event])) {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const remaining = stripLoreFromCopilotEvent(hooks[event]);
|
|
494
|
+
if (remaining.length === 0) {
|
|
495
|
+
delete hooks[event];
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
hooks[event] = remaining;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (Object.keys(hooks).length === 0) {
|
|
502
|
+
delete result.hooks;
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
result.hooks = hooks;
|
|
506
|
+
}
|
|
507
|
+
return result;
|
|
508
|
+
}
|
|
509
|
+
export function countLoreCopilotCliHooks(input) {
|
|
510
|
+
if (!isObject(input.hooks)) {
|
|
511
|
+
return 0;
|
|
512
|
+
}
|
|
513
|
+
return COPILOT_CLI_HOOK_EVENTS.reduce((count, event) => {
|
|
514
|
+
const hooks = input.hooks;
|
|
515
|
+
if (!isObject(hooks) || !Array.isArray(hooks[event])) {
|
|
516
|
+
return count;
|
|
517
|
+
}
|
|
518
|
+
return count + hooks[event].filter(isLoreCopilotHook).length;
|
|
519
|
+
}, 0);
|
|
520
|
+
}
|
|
521
|
+
function copilotVscodeEventHandler(event, paths) {
|
|
522
|
+
return {
|
|
523
|
+
type: "command",
|
|
524
|
+
command: hookCommand("copilot-vscode", paths, event),
|
|
525
|
+
timeout: event === "SessionEnd"
|
|
526
|
+
? 2
|
|
527
|
+
: event === "Stop"
|
|
528
|
+
? 3
|
|
529
|
+
: event === "PreToolUse"
|
|
530
|
+
? 10
|
|
531
|
+
: 25,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
export function mergeLoreCopilotVscodeHooks(input, paths) {
|
|
535
|
+
const result = cloneObject(input);
|
|
536
|
+
if (result.version !== undefined && result.version !== 1) {
|
|
537
|
+
throw new Error('VS Code Copilot hook field "version" must be 1');
|
|
538
|
+
}
|
|
539
|
+
if (result.hooks !== undefined && !isObject(result.hooks)) {
|
|
540
|
+
throw new Error('VS Code Copilot hook field "hooks" must be an object');
|
|
541
|
+
}
|
|
542
|
+
const hooks = isObject(result.hooks) ? { ...result.hooks } : {};
|
|
543
|
+
for (const event of HOOK_EVENTS) {
|
|
544
|
+
if (hooks[event] !== undefined && !Array.isArray(hooks[event])) {
|
|
545
|
+
throw new Error(`VS Code Copilot hook event "${event}" must be an array`);
|
|
546
|
+
}
|
|
547
|
+
hooks[event] = [
|
|
548
|
+
...stripLoreFromCursorEvent(hooks[event]),
|
|
549
|
+
copilotVscodeEventHandler(event, paths),
|
|
550
|
+
];
|
|
551
|
+
}
|
|
552
|
+
result.version = 1;
|
|
553
|
+
result.hooks = hooks;
|
|
554
|
+
return result;
|
|
555
|
+
}
|
|
556
|
+
export function removeLoreCopilotVscodeHooks(input) {
|
|
557
|
+
const result = cloneObject(input);
|
|
558
|
+
if (!isObject(result.hooks)) {
|
|
559
|
+
return result;
|
|
560
|
+
}
|
|
561
|
+
const hooks = { ...result.hooks };
|
|
562
|
+
for (const event of HOOK_EVENTS) {
|
|
563
|
+
if (!Array.isArray(hooks[event])) {
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
const remaining = stripLoreFromCursorEvent(hooks[event]);
|
|
567
|
+
if (remaining.length === 0) {
|
|
568
|
+
delete hooks[event];
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
hooks[event] = remaining;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (Object.keys(hooks).length === 0) {
|
|
575
|
+
delete result.hooks;
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
result.hooks = hooks;
|
|
579
|
+
}
|
|
580
|
+
return result;
|
|
581
|
+
}
|
|
582
|
+
export function countLoreCopilotVscodeHooks(input) {
|
|
583
|
+
if (!isObject(input.hooks)) {
|
|
584
|
+
return 0;
|
|
585
|
+
}
|
|
586
|
+
return HOOK_EVENTS.reduce((count, event) => {
|
|
587
|
+
const hooks = input.hooks;
|
|
588
|
+
if (!isObject(hooks) || !Array.isArray(hooks[event])) {
|
|
589
|
+
return count;
|
|
590
|
+
}
|
|
591
|
+
return count + hooks[event].filter(isLoreHook).length;
|
|
592
|
+
}, 0);
|
|
593
|
+
}
|
|
399
594
|
function polytokenEventHandler(event, paths) {
|
|
400
595
|
return {
|
|
401
596
|
name: `lore-${event.replaceAll("_", "-")}`,
|
|
@@ -536,6 +731,54 @@ function cursorInstallationUnits(input, paths, index, total) {
|
|
|
536
731
|
};
|
|
537
732
|
});
|
|
538
733
|
}
|
|
734
|
+
function copilotCliInstallationUnits(input, paths, index, total) {
|
|
735
|
+
const configuration = objectConfiguration(input, "copilot-cli");
|
|
736
|
+
if (configuration.version !== undefined && configuration.version !== 1) {
|
|
737
|
+
throw new Error('Copilot hook configuration field "version" must be 1');
|
|
738
|
+
}
|
|
739
|
+
if (configuration.hooks !== undefined && !isObject(configuration.hooks)) {
|
|
740
|
+
throw new Error('Copilot hook configuration field "hooks" must be an object');
|
|
741
|
+
}
|
|
742
|
+
const hooks = isObject(configuration.hooks) ? configuration.hooks : {};
|
|
743
|
+
return COPILOT_CLI_HOOK_EVENTS.map((event) => {
|
|
744
|
+
const values = hooks[event];
|
|
745
|
+
if (values !== undefined && !Array.isArray(values)) {
|
|
746
|
+
throw new Error(`Copilot hook event "${event}" must be an array`);
|
|
747
|
+
}
|
|
748
|
+
const candidates = Array.isArray(values) ? values : [];
|
|
749
|
+
const expected = copilotCliEventHandler(event, paths);
|
|
750
|
+
const ownedCount = candidates.filter(isLoreCopilotHook).length;
|
|
751
|
+
const exactCount = candidates.filter((candidate) => exactConfiguration(candidate, expected)).length;
|
|
752
|
+
return {
|
|
753
|
+
id: installationUnitId(event, index, total),
|
|
754
|
+
state: installationUnitState(ownedCount, exactCount),
|
|
755
|
+
};
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
function copilotVscodeInstallationUnits(input, paths, index, total) {
|
|
759
|
+
const configuration = objectConfiguration(input, "copilot-vscode");
|
|
760
|
+
if (configuration.version !== undefined && configuration.version !== 1) {
|
|
761
|
+
throw new Error('VS Code Copilot hook field "version" must be 1');
|
|
762
|
+
}
|
|
763
|
+
if (configuration.hooks !== undefined && !isObject(configuration.hooks)) {
|
|
764
|
+
throw new Error('VS Code Copilot hook field "hooks" must be an object');
|
|
765
|
+
}
|
|
766
|
+
const hooks = isObject(configuration.hooks) ? configuration.hooks : {};
|
|
767
|
+
return HOOK_EVENTS.map((event) => {
|
|
768
|
+
const values = hooks[event];
|
|
769
|
+
if (values !== undefined && !Array.isArray(values)) {
|
|
770
|
+
throw new Error(`VS Code Copilot hook event "${event}" must be an array`);
|
|
771
|
+
}
|
|
772
|
+
const candidates = Array.isArray(values) ? values : [];
|
|
773
|
+
const expected = copilotVscodeEventHandler(event, paths);
|
|
774
|
+
const ownedCount = candidates.filter(isLoreHook).length;
|
|
775
|
+
const exactCount = candidates.filter((candidate) => exactConfiguration(candidate, expected)).length;
|
|
776
|
+
return {
|
|
777
|
+
id: installationUnitId(event, index, total),
|
|
778
|
+
state: installationUnitState(ownedCount, exactCount),
|
|
779
|
+
};
|
|
780
|
+
});
|
|
781
|
+
}
|
|
539
782
|
function polytokenInstallationUnits(input, paths, index, total) {
|
|
540
783
|
if (!Array.isArray(input)) {
|
|
541
784
|
throw new Error("Polytoken hooks must contain a JSON array");
|
|
@@ -574,6 +817,10 @@ function inspectInstallationUnits(input, agent, paths, index, total) {
|
|
|
574
817
|
case "claude":
|
|
575
818
|
case "codex":
|
|
576
819
|
return commandHookInstallationUnits(input, agent, paths, index, total);
|
|
820
|
+
case "copilot-cli":
|
|
821
|
+
return copilotCliInstallationUnits(input, paths, index, total);
|
|
822
|
+
case "copilot-vscode":
|
|
823
|
+
return copilotVscodeInstallationUnits(input, paths, index, total);
|
|
577
824
|
case "cursor":
|
|
578
825
|
return cursorInstallationUnits(input, paths, index, total);
|
|
579
826
|
case "polytoken":
|
|
@@ -883,6 +1130,30 @@ const AGENT_TARGETS = {
|
|
|
883
1130
|
remove: (input) => removeLoreHooks(objectConfiguration(input, "codex")),
|
|
884
1131
|
count: (input) => countLoreHooks(objectConfiguration(input, "codex")),
|
|
885
1132
|
},
|
|
1133
|
+
"copilot-cli": {
|
|
1134
|
+
integration: "hooks",
|
|
1135
|
+
expected: COPILOT_CLI_HOOK_EVENTS.length,
|
|
1136
|
+
runtimeRequired: true,
|
|
1137
|
+
emptyValue: () => ({}),
|
|
1138
|
+
configPath: (paths) => paths.copilotCliHooks,
|
|
1139
|
+
detect: async (paths) => (await commandExists("copilot")) || pathExists(paths.copilotCliHooks),
|
|
1140
|
+
executable: async () => commandExists("copilot"),
|
|
1141
|
+
merge: (input, paths) => mergeLoreCopilotCliHooks(objectConfiguration(input, "copilot-cli"), paths),
|
|
1142
|
+
remove: (input) => removeLoreCopilotCliHooks(objectConfiguration(input, "copilot-cli")),
|
|
1143
|
+
count: (input) => countLoreCopilotCliHooks(objectConfiguration(input, "copilot-cli")),
|
|
1144
|
+
},
|
|
1145
|
+
"copilot-vscode": {
|
|
1146
|
+
integration: "hooks",
|
|
1147
|
+
expected: HOOK_EVENTS.length,
|
|
1148
|
+
runtimeRequired: true,
|
|
1149
|
+
emptyValue: () => ({}),
|
|
1150
|
+
configPath: (paths) => paths.copilotVscodeHooks,
|
|
1151
|
+
detect: async (paths) => pathExists(paths.copilotVscodeHooks),
|
|
1152
|
+
executable: async () => commandExists("code"),
|
|
1153
|
+
merge: (input, paths) => mergeLoreCopilotVscodeHooks(objectConfiguration(input, "copilot-vscode"), paths),
|
|
1154
|
+
remove: (input) => removeLoreCopilotVscodeHooks(objectConfiguration(input, "copilot-vscode")),
|
|
1155
|
+
count: (input) => countLoreCopilotVscodeHooks(objectConfiguration(input, "copilot-vscode")),
|
|
1156
|
+
},
|
|
886
1157
|
cursor: {
|
|
887
1158
|
integration: "hooks",
|
|
888
1159
|
expected: CURSOR_HOOK_EVENTS.length,
|
|
@@ -928,7 +1199,10 @@ async function detectAgents(paths) {
|
|
|
928
1199
|
agent,
|
|
929
1200
|
detected: await targetFor(agent).detect(paths),
|
|
930
1201
|
})));
|
|
931
|
-
|
|
1202
|
+
const agents = detected.flatMap(({ agent, detected: present }) => present ? [agent] : []);
|
|
1203
|
+
return agents.includes("copilot-cli")
|
|
1204
|
+
? agents.filter((agent) => agent !== "copilot-vscode")
|
|
1205
|
+
: agents;
|
|
932
1206
|
}
|
|
933
1207
|
function installationKey(installation) {
|
|
934
1208
|
return `${installation.agent}\0${resolve(installation.configPath)}`;
|
|
@@ -1088,7 +1362,14 @@ async function readT3CodeProviderInstallations(paths) {
|
|
|
1088
1362
|
}
|
|
1089
1363
|
}
|
|
1090
1364
|
async function expandConnectInstallations(requested, paths) {
|
|
1091
|
-
const direct = requested.flatMap((agent) =>
|
|
1365
|
+
const direct = requested.flatMap((agent) => {
|
|
1366
|
+
if (agent === "cowork" || agent === "t3code") {
|
|
1367
|
+
return [];
|
|
1368
|
+
}
|
|
1369
|
+
return [
|
|
1370
|
+
defaultInstallation(agent === "copilot" ? "copilot-cli" : agent, paths),
|
|
1371
|
+
];
|
|
1372
|
+
});
|
|
1092
1373
|
if (!requested.includes("t3code")) {
|
|
1093
1374
|
return { installations: direct, warnings: [] };
|
|
1094
1375
|
}
|
|
@@ -1203,7 +1484,12 @@ function valueAfter(args, index, flag) {
|
|
|
1203
1484
|
return [value, index + 1];
|
|
1204
1485
|
}
|
|
1205
1486
|
function parseConnectArguments(args) {
|
|
1206
|
-
const parsed = {
|
|
1487
|
+
const parsed = {
|
|
1488
|
+
agents: [],
|
|
1489
|
+
json: false,
|
|
1490
|
+
login: false,
|
|
1491
|
+
openBrowser: true,
|
|
1492
|
+
};
|
|
1207
1493
|
for (let index = 0; index < args.length; index += 1) {
|
|
1208
1494
|
const argument = args[index];
|
|
1209
1495
|
if (argument === "--help" || argument === "-h") {
|
|
@@ -1214,6 +1500,14 @@ function parseConnectArguments(args) {
|
|
|
1214
1500
|
parsed.json = true;
|
|
1215
1501
|
continue;
|
|
1216
1502
|
}
|
|
1503
|
+
if (argument === "--login") {
|
|
1504
|
+
parsed.login = true;
|
|
1505
|
+
continue;
|
|
1506
|
+
}
|
|
1507
|
+
if (argument === "--no-open") {
|
|
1508
|
+
parsed.openBrowser = false;
|
|
1509
|
+
continue;
|
|
1510
|
+
}
|
|
1217
1511
|
if (argument !== "--url" &&
|
|
1218
1512
|
argument !== "--dashboard-url" &&
|
|
1219
1513
|
argument !== "--token" &&
|
|
@@ -1243,9 +1537,17 @@ function parseConnectArguments(args) {
|
|
|
1243
1537
|
parsed.agents.push(value);
|
|
1244
1538
|
}
|
|
1245
1539
|
else {
|
|
1246
|
-
throw new Error("--agent must be claude, codex, cursor, opencode, polytoken, or t3code");
|
|
1540
|
+
throw new Error("--agent must be claude, codex, copilot, copilot-cli, copilot-vscode, cowork, cursor, opencode, polytoken, or t3code");
|
|
1247
1541
|
}
|
|
1248
1542
|
}
|
|
1543
|
+
if (parsed.login && parsed.token !== undefined) {
|
|
1544
|
+
throw new Error("--login and --token cannot be used together");
|
|
1545
|
+
}
|
|
1546
|
+
if ((parsed.agents.includes("copilot-cli") ||
|
|
1547
|
+
parsed.agents.includes("copilot")) &&
|
|
1548
|
+
parsed.agents.includes("copilot-vscode")) {
|
|
1549
|
+
throw new Error("Install either copilot-cli or copilot-vscode in one connection. Both hosts load repository hooks and would invoke Lore twice.");
|
|
1550
|
+
}
|
|
1249
1551
|
return parsed;
|
|
1250
1552
|
}
|
|
1251
1553
|
function parseOutputArguments(args, help, command) {
|
|
@@ -1266,38 +1568,26 @@ function parseOutputArguments(args, help, command) {
|
|
|
1266
1568
|
function writeResult(value, json, text) {
|
|
1267
1569
|
process.stdout.write(json ? `${JSON.stringify(value, null, 2)}\n` : text);
|
|
1268
1570
|
}
|
|
1269
|
-
async function
|
|
1270
|
-
const parsed = parseConnectArguments(args);
|
|
1271
|
-
if (parsed === null) {
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1571
|
+
export async function connectWithCredential(options) {
|
|
1274
1572
|
if (platform() !== "darwin" && platform() !== "linux") {
|
|
1275
1573
|
throw new Error("Lore agent integrations currently support macOS and Linux");
|
|
1276
1574
|
}
|
|
1277
1575
|
const paths = getLorePaths();
|
|
1278
1576
|
const existing = await readConnectorConfig(paths);
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
: { dashboardUrl: parsed.dashboardUrl }),
|
|
1287
|
-
environment: process.env,
|
|
1288
|
-
existing,
|
|
1289
|
-
preferHostedDefault: hasProvidedToken,
|
|
1290
|
-
});
|
|
1291
|
-
const token = configuredToken(parsed.token, existing);
|
|
1292
|
-
if (token === undefined || token.trim() === "") {
|
|
1293
|
-
throw new Error("Workspace token is required. Use --token <token>, LORE_WORKSPACE_TOKEN, or LORE_TOKEN.");
|
|
1577
|
+
const apiUrl = normalizeApiUrl(options.apiUrl);
|
|
1578
|
+
const dashboardUrl = options.dashboardUrl === undefined
|
|
1579
|
+
? undefined
|
|
1580
|
+
: normalizeApiUrl(options.dashboardUrl);
|
|
1581
|
+
const token = options.token.trim();
|
|
1582
|
+
if (token === "") {
|
|
1583
|
+
throw new Error("Workspace token cannot be empty");
|
|
1294
1584
|
}
|
|
1295
|
-
const timeoutMs =
|
|
1585
|
+
const timeoutMs = options.timeoutMs ?? existing?.timeoutMs ?? 2_500;
|
|
1296
1586
|
const identity = await authenticatedIdentity(apiUrl, token.trim(), timeoutMs);
|
|
1297
|
-
const requestedExpansion =
|
|
1587
|
+
const requestedExpansion = options.agents.length === 0
|
|
1298
1588
|
? { installations: [], warnings: [] }
|
|
1299
|
-
: await expandConnectInstallations(
|
|
1300
|
-
const detectedAgents =
|
|
1589
|
+
: await expandConnectInstallations(options.agents, paths);
|
|
1590
|
+
const detectedAgents = options.agents.length === 0 ? await detectAgents(paths) : [];
|
|
1301
1591
|
const installations = uniqueInstallations([
|
|
1302
1592
|
...configuredInstallations(existing, paths),
|
|
1303
1593
|
...requestedExpansion.installations,
|
|
@@ -1306,8 +1596,9 @@ async function connectCommand(args) {
|
|
|
1306
1596
|
const agents = [
|
|
1307
1597
|
...new Set(installations.map((installation) => installation.agent)),
|
|
1308
1598
|
].sort();
|
|
1309
|
-
|
|
1310
|
-
|
|
1599
|
+
const credentialOnlyCowork = options.agents.includes("cowork");
|
|
1600
|
+
if (agents.length === 0 && !credentialOnlyCowork) {
|
|
1601
|
+
throw new Error("No Claude, Codex, Copilot CLI, Cursor, OpenCode, or Polytoken installation detected. Use --agent <name>; T3 Code users can use --agent t3code.");
|
|
1311
1602
|
}
|
|
1312
1603
|
const now = new Date();
|
|
1313
1604
|
const documents = new Map();
|
|
@@ -1369,17 +1660,154 @@ async function connectCommand(args) {
|
|
|
1369
1660
|
await credentialStore.transferPendingTo(reliabilityStore);
|
|
1370
1661
|
}
|
|
1371
1662
|
await reliabilityStore.releaseAuthBlocked(now);
|
|
1372
|
-
|
|
1663
|
+
return {
|
|
1373
1664
|
connected: true,
|
|
1374
1665
|
apiUrl: config.apiUrl,
|
|
1375
1666
|
identity,
|
|
1376
|
-
agents,
|
|
1667
|
+
agents: credentialOnlyCowork ? [...agents, "cowork"] : agents,
|
|
1377
1668
|
config: paths.config,
|
|
1378
1669
|
changedHookFiles,
|
|
1379
1670
|
backups,
|
|
1380
1671
|
warnings: requestedExpansion.warnings,
|
|
1381
1672
|
};
|
|
1382
|
-
|
|
1673
|
+
}
|
|
1674
|
+
async function requestDeviceAuthorization(input) {
|
|
1675
|
+
let response;
|
|
1676
|
+
try {
|
|
1677
|
+
response = await fetch(`${input.apiUrl}/v1/device-authorizations`, {
|
|
1678
|
+
method: "POST",
|
|
1679
|
+
headers: {
|
|
1680
|
+
accept: "application/json",
|
|
1681
|
+
"content-type": "application/json",
|
|
1682
|
+
},
|
|
1683
|
+
body: JSON.stringify({
|
|
1684
|
+
clientName: `Lore CLI on ${hostname()}`,
|
|
1685
|
+
agents: input.agents,
|
|
1686
|
+
expiresInDays: 90,
|
|
1687
|
+
}),
|
|
1688
|
+
signal: AbortSignal.timeout(10_000),
|
|
1689
|
+
});
|
|
1690
|
+
}
|
|
1691
|
+
catch {
|
|
1692
|
+
throw new Error("Lore device authorization is unreachable. Verify --url and retry.");
|
|
1693
|
+
}
|
|
1694
|
+
if (!response.ok) {
|
|
1695
|
+
throw new Error(response.status === 404
|
|
1696
|
+
? "This Lore server does not support browser approval. Upgrade the server or use --token."
|
|
1697
|
+
: "Lore could not start browser approval. Retry in a moment.");
|
|
1698
|
+
}
|
|
1699
|
+
const authorization = CreateDeviceAuthorizationResponseSchema.safeParse(await response.json().catch(() => null));
|
|
1700
|
+
if (!authorization.success) {
|
|
1701
|
+
throw new Error("Lore returned an incompatible device authorization response.");
|
|
1702
|
+
}
|
|
1703
|
+
const serverApprovalUrl = new URL(authorization.data.verificationUriComplete);
|
|
1704
|
+
const approvalUrl = input.dashboardUrl === undefined
|
|
1705
|
+
? serverApprovalUrl.toString()
|
|
1706
|
+
: new URL(`${serverApprovalUrl.pathname}${serverApprovalUrl.search}`, `${input.dashboardUrl}/`).toString();
|
|
1707
|
+
process.stderr.write(`Approve Lore access with code ${authorization.data.userCode}:\n${approvalUrl}\n`);
|
|
1708
|
+
if (input.openBrowser) {
|
|
1709
|
+
const command = platform() === "darwin" ? "open" : "xdg-open";
|
|
1710
|
+
const child = spawn(command, [approvalUrl], {
|
|
1711
|
+
detached: true,
|
|
1712
|
+
stdio: "ignore",
|
|
1713
|
+
});
|
|
1714
|
+
child.once("error", () => {
|
|
1715
|
+
// The printed URL remains the safe fallback.
|
|
1716
|
+
});
|
|
1717
|
+
child.unref();
|
|
1718
|
+
}
|
|
1719
|
+
const expiresAt = Date.parse(authorization.data.expiresAt);
|
|
1720
|
+
const intervalMs = authorization.data.intervalSeconds * 1_000;
|
|
1721
|
+
while (Date.now() < expiresAt) {
|
|
1722
|
+
await delay(intervalMs);
|
|
1723
|
+
let pollResponse;
|
|
1724
|
+
try {
|
|
1725
|
+
pollResponse = await fetch(`${input.apiUrl}/v1/device-authorizations/poll`, {
|
|
1726
|
+
method: "POST",
|
|
1727
|
+
headers: {
|
|
1728
|
+
accept: "application/json",
|
|
1729
|
+
"content-type": "application/json",
|
|
1730
|
+
},
|
|
1731
|
+
body: JSON.stringify({
|
|
1732
|
+
deviceCode: authorization.data.deviceCode,
|
|
1733
|
+
}),
|
|
1734
|
+
signal: AbortSignal.timeout(10_000),
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
catch {
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
if (pollResponse.status === 429) {
|
|
1741
|
+
const retryAfter = Number(pollResponse.headers.get("retry-after"));
|
|
1742
|
+
if (Number.isFinite(retryAfter) && retryAfter > 0) {
|
|
1743
|
+
await delay(retryAfter * 1_000);
|
|
1744
|
+
}
|
|
1745
|
+
continue;
|
|
1746
|
+
}
|
|
1747
|
+
if (!pollResponse.ok) {
|
|
1748
|
+
throw new Error("Lore browser approval failed. Run connect --login again.");
|
|
1749
|
+
}
|
|
1750
|
+
const result = PollDeviceAuthorizationResponseSchema.safeParse(await pollResponse.json().catch(() => null));
|
|
1751
|
+
if (!result.success) {
|
|
1752
|
+
throw new Error("Lore returned an incompatible device authorization response.");
|
|
1753
|
+
}
|
|
1754
|
+
if (result.data.status === "pending") {
|
|
1755
|
+
continue;
|
|
1756
|
+
}
|
|
1757
|
+
if (result.data.status === "approved") {
|
|
1758
|
+
return result.data.token;
|
|
1759
|
+
}
|
|
1760
|
+
if (result.data.status === "denied") {
|
|
1761
|
+
throw new Error("Lore access was denied in the dashboard.");
|
|
1762
|
+
}
|
|
1763
|
+
throw new Error("Lore browser approval expired. Run connect --login again.");
|
|
1764
|
+
}
|
|
1765
|
+
throw new Error("Lore browser approval expired. Run connect --login again.");
|
|
1766
|
+
}
|
|
1767
|
+
async function connectCommand(args) {
|
|
1768
|
+
const parsed = parseConnectArguments(args);
|
|
1769
|
+
if (parsed === null) {
|
|
1770
|
+
return;
|
|
1771
|
+
}
|
|
1772
|
+
const paths = getLorePaths();
|
|
1773
|
+
const existing = await readConnectorConfig(paths);
|
|
1774
|
+
const hasProvidedToken = parsed.token !== undefined ||
|
|
1775
|
+
(process.env.LORE_WORKSPACE_TOKEN?.trim() ?? "") !== "" ||
|
|
1776
|
+
(process.env.LORE_TOKEN?.trim() ?? "") !== "";
|
|
1777
|
+
const { apiUrl, dashboardUrl } = resolveConnectEndpoints({
|
|
1778
|
+
...(parsed.apiUrl === undefined ? {} : { apiUrl: parsed.apiUrl }),
|
|
1779
|
+
...(parsed.dashboardUrl === undefined
|
|
1780
|
+
? {}
|
|
1781
|
+
: { dashboardUrl: parsed.dashboardUrl }),
|
|
1782
|
+
environment: process.env,
|
|
1783
|
+
existing,
|
|
1784
|
+
preferHostedDefault: parsed.login || hasProvidedToken,
|
|
1785
|
+
});
|
|
1786
|
+
const configured = parsed.login
|
|
1787
|
+
? undefined
|
|
1788
|
+
: configuredToken(parsed.token, existing);
|
|
1789
|
+
if (parsed.login && hasProvidedToken) {
|
|
1790
|
+
throw new Error("--login cannot be combined with an explicit or environment workspace token. Unset it or use the token directly.");
|
|
1791
|
+
}
|
|
1792
|
+
const token = parsed.login
|
|
1793
|
+
? await requestDeviceAuthorization({
|
|
1794
|
+
apiUrl,
|
|
1795
|
+
...(dashboardUrl === undefined ? {} : { dashboardUrl }),
|
|
1796
|
+
agents: parsed.agents,
|
|
1797
|
+
openBrowser: parsed.openBrowser,
|
|
1798
|
+
})
|
|
1799
|
+
: configured;
|
|
1800
|
+
if (token === undefined || token.trim() === "") {
|
|
1801
|
+
throw new Error("Workspace token is required. Use --login, --token <token>, LORE_WORKSPACE_TOKEN, or LORE_TOKEN.");
|
|
1802
|
+
}
|
|
1803
|
+
const result = await connectWithCredential({
|
|
1804
|
+
apiUrl,
|
|
1805
|
+
...(dashboardUrl === undefined ? {} : { dashboardUrl }),
|
|
1806
|
+
token,
|
|
1807
|
+
agents: parsed.agents,
|
|
1808
|
+
...(parsed.timeoutMs === undefined ? {} : { timeoutMs: parsed.timeoutMs }),
|
|
1809
|
+
});
|
|
1810
|
+
writeResult(result, parsed.json, `connected: ${result.agents.join(", ")}\napi_url: ${result.apiUrl}\nconfig: ${result.config}\n${result.warnings.map((warning) => `warning: ${warning}\n`).join("")}`);
|
|
1383
1811
|
}
|
|
1384
1812
|
async function queueCount(paths) {
|
|
1385
1813
|
try {
|
|
@@ -1665,6 +2093,7 @@ async function statusData(paths) {
|
|
|
1665
2093
|
reliability: reliability.inspection,
|
|
1666
2094
|
reliabilityError: reliability.error,
|
|
1667
2095
|
agents,
|
|
2096
|
+
externalSurfaces: EXTERNAL_SURFACES,
|
|
1668
2097
|
};
|
|
1669
2098
|
}
|
|
1670
2099
|
async function statusCommand(args) {
|
|
@@ -1676,10 +2105,13 @@ async function statusCommand(args) {
|
|
|
1676
2105
|
const agentLines = data.agents
|
|
1677
2106
|
.map((agent) => `${agent.agent}: ${agent.configured ? "configured" : "not configured"}, installation ${agent.installation.state}, invocation ${agent.invocation.state}${agent.invocationError === null ? "" : ` (${agent.invocationError})`}, capture ${agent.capabilities.captureDurability}, guard ${agent.capabilities.guardBoundaryCoverage}/${agent.capabilities.enforcement}`)
|
|
1678
2107
|
.join("\n");
|
|
2108
|
+
const externalSurfaceLines = data.externalSurfaces
|
|
2109
|
+
.map((surface) => `${surface.surface}: repository template, locally inspectable no, capture ${surface.capabilities.captureDurability}, guard ${surface.capabilities.guardBoundaryCoverage}/${surface.capabilities.enforcement}`)
|
|
2110
|
+
.join("\n");
|
|
1679
2111
|
const metricRate = (value) => value === null || value === undefined
|
|
1680
2112
|
? "-"
|
|
1681
2113
|
: `${(value * 100).toFixed(1)}%`;
|
|
1682
|
-
writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? `installed (${data.runtimeVersion})` : data.runtimeVersion === null ? "missing" : `stale (${data.runtimeVersion})`) : "not required"}\ndurable_outbox: ${data.reliability?.outbox.total ?? 0} ready=${data.reliability?.outbox.ready ?? 0} auth_blocked=${data.reliability?.outbox.authBlocked ?? 0} dead=${data.reliability?.outbox.dead ?? 0}\noldest_capture: ${data.reliability?.outbox.oldestEnqueuedAt ?? "-"}\ncontext_snapshots: ${data.reliability?.cache.context ?? 0}\noldest_context_snapshot: ${data.reliability?.cache.oldestContextWrittenAt ?? "-"}\npolicy_snapshots: ${data.reliability?.cache.policy ?? 0}\nlocal_context_fallback_rate: ${metricRate(data.reliability?.operational.retrieval.fallbackRate)}\nlocal_guard_coverage: ${metricRate(data.reliability?.operational.guard.coverageRate)}\nlocal_guard_failures: ${data.reliability?.operational.guard.failures ?? 0}\nlocal_retrieval_failures: ${data.reliability?.operational.retrieval.failed ?? 0}\n${data.reliabilityError === null ? "" : `reliability_error: ${data.reliabilityError}\n`}${agentLines}\n`);
|
|
2114
|
+
writeResult(data, parsed.json, `connected: ${data.connected ? "yes" : "no"}\napi_url: ${data.apiUrl ?? "-"}\nconfig_mode: ${data.configMode ?? "-"}\nruntime: ${data.runtimeRequired ? (data.runtimeInstalled ? `installed (${data.runtimeVersion})` : data.runtimeVersion === null ? "missing" : `stale (${data.runtimeVersion})`) : "not required"}\ndurable_outbox: ${data.reliability?.outbox.total ?? 0} ready=${data.reliability?.outbox.ready ?? 0} auth_blocked=${data.reliability?.outbox.authBlocked ?? 0} dead=${data.reliability?.outbox.dead ?? 0}\noldest_capture: ${data.reliability?.outbox.oldestEnqueuedAt ?? "-"}\ncontext_snapshots: ${data.reliability?.cache.context ?? 0}\noldest_context_snapshot: ${data.reliability?.cache.oldestContextWrittenAt ?? "-"}\npolicy_snapshots: ${data.reliability?.cache.policy ?? 0}\nlocal_context_fallback_rate: ${metricRate(data.reliability?.operational.retrieval.fallbackRate)}\nlocal_guard_coverage: ${metricRate(data.reliability?.operational.guard.coverageRate)}\nlocal_guard_failures: ${data.reliability?.operational.guard.failures ?? 0}\nlocal_retrieval_failures: ${data.reliability?.operational.retrieval.failed ?? 0}\n${data.reliabilityError === null ? "" : `reliability_error: ${data.reliabilityError}\n`}${agentLines}\n${externalSurfaceLines}\n`);
|
|
1683
2115
|
}
|
|
1684
2116
|
async function disconnectCommand(args) {
|
|
1685
2117
|
const parsed = parseOutputArguments(args, DISCONNECT_HELP, "disconnect");
|
|
@@ -1821,6 +2253,19 @@ async function apiChecks(config) {
|
|
|
1821
2253
|
}
|
|
1822
2254
|
return checks;
|
|
1823
2255
|
}
|
|
2256
|
+
async function verifyConnectedInstallation() {
|
|
2257
|
+
const paths = getLorePaths();
|
|
2258
|
+
const config = await readConnectorConfig(paths);
|
|
2259
|
+
if (config === null) {
|
|
2260
|
+
return false;
|
|
2261
|
+
}
|
|
2262
|
+
const status = await statusData(paths);
|
|
2263
|
+
if (status.configMode !== "600" ||
|
|
2264
|
+
status.agents.some((agent) => agent.configured && agent.installation.state !== "complete")) {
|
|
2265
|
+
return false;
|
|
2266
|
+
}
|
|
2267
|
+
return (await apiChecks(config)).every((check) => check.status !== "error");
|
|
2268
|
+
}
|
|
1824
2269
|
async function doctorCommand(args) {
|
|
1825
2270
|
const parsed = parseOutputArguments(args, DOCTOR_HELP, "doctor");
|
|
1826
2271
|
if (parsed === null) {
|
|
@@ -1899,6 +2344,13 @@ async function doctorCommand(args) {
|
|
|
1899
2344
|
: "invocation health is unavailable",
|
|
1900
2345
|
});
|
|
1901
2346
|
}
|
|
2347
|
+
for (const surface of status.externalSurfaces) {
|
|
2348
|
+
checks.push({
|
|
2349
|
+
name: `${surface.surface}-capability`,
|
|
2350
|
+
status: "ok",
|
|
2351
|
+
detail: "surface-specific repository configuration; local installation is not claimed",
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
1902
2354
|
checks.push({
|
|
1903
2355
|
name: "reliability-store",
|
|
1904
2356
|
status: status.reliabilityError !== null
|
|
@@ -2016,7 +2468,19 @@ export async function runCli(args = process.argv.slice(2)) {
|
|
|
2016
2468
|
await doctorCommand(commandArgs);
|
|
2017
2469
|
return;
|
|
2018
2470
|
case "self-host":
|
|
2019
|
-
await runSelfHostCommand(commandArgs
|
|
2471
|
+
await runSelfHostCommand(commandArgs, process.env, {
|
|
2472
|
+
connect: async (request) => {
|
|
2473
|
+
const connected = await connectWithCredential({
|
|
2474
|
+
apiUrl: request.apiUrl,
|
|
2475
|
+
token: request.token,
|
|
2476
|
+
agents: request.agents,
|
|
2477
|
+
});
|
|
2478
|
+
return {
|
|
2479
|
+
agents: connected.agents,
|
|
2480
|
+
verified: await verifyConnectedInstallation(),
|
|
2481
|
+
};
|
|
2482
|
+
},
|
|
2483
|
+
});
|
|
2020
2484
|
return;
|
|
2021
2485
|
case "demo":
|
|
2022
2486
|
await runDemoCommand(commandArgs, await readConnectorConfig(getLorePaths()));
|