@danypops/pi-lector 0.9.2 → 0.9.4
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/extension/src/apply-patch-operations.ts +1 -1
- package/extension/src/code-intelligence-operations.ts +1 -1
- package/extension/src/edit-operations.ts +1 -1
- package/extension/src/index.ts +96 -13
- package/extension/src/lector-client.ts +9 -1
- package/extension/src/line-edit-operations.ts +1 -1
- package/extension/src/mutation-history-operations.ts +1 -1
- package/extension/src/package-source-operations.ts +22 -2
- package/extension/src/package-source-rendering.ts +77 -4
- package/extension/src/reference-based-rename-operations.ts +1 -1
- package/extension/src/rename-operations.ts +1 -1
- package/extension/src/repo-cache-evict-operations.ts +1 -1
- package/extension/src/repo-fetch-operations.ts +1 -1
- package/extension/src/symbol-annotation-operations.ts +6 -6
- package/extension/src/workspace-cache-operations.ts +1 -1
- package/extension/src/write-operations.ts +1 -1
- package/package.json +3 -3
|
@@ -21,7 +21,7 @@ export function createLectorApplyPatchOperations(): ApplyPatchOperations {
|
|
|
21
21
|
async ({ workspaceId, root }) => {
|
|
22
22
|
const client = await lectorClient();
|
|
23
23
|
const relativePath = toWorkspaceRelativePath(root, path);
|
|
24
|
-
return client.
|
|
24
|
+
return client.callOnce("workspace.applyPatch", { workspaceId, path: relativePath, expectedHash, patchText });
|
|
25
25
|
},
|
|
26
26
|
);
|
|
27
27
|
},
|
|
@@ -140,7 +140,7 @@ export function createLectorCodeIntelligenceOperations(): CodeIntelligenceOperat
|
|
|
140
140
|
() => workspaceForPathOrDirectory(path),
|
|
141
141
|
async ({ workspaceId }) => {
|
|
142
142
|
const client = await lectorClient();
|
|
143
|
-
const { job } = await client.
|
|
143
|
+
const { job } = await client.callOnce("job.submit", {
|
|
144
144
|
operation: "workspace.populateSymbolGraph",
|
|
145
145
|
input: { workspaceId, maxFiles, maxSymbolsPerFile },
|
|
146
146
|
waitMs,
|
|
@@ -50,7 +50,7 @@ export function createLectorEditOperations(): EditOperations {
|
|
|
50
50
|
const client = await lectorClient();
|
|
51
51
|
const relativePath = toWorkspaceRelativePath(root, absolutePath);
|
|
52
52
|
try {
|
|
53
|
-
await client.
|
|
53
|
+
await client.callOnce("workspace.exactEdit", { workspaceId, path: relativePath, expectedHash, content });
|
|
54
54
|
} catch (error) {
|
|
55
55
|
if (remoteErrorIs(error, "StaleExpectedHash")) {
|
|
56
56
|
throw new Error(`"${relativePath}" changed on disk since it was last read; re-read the file and retry the edit.`);
|
package/extension/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
MutationHistoryEntry,
|
|
15
15
|
NpmPackageCandidate,
|
|
16
16
|
OperationOutputs,
|
|
17
|
+
PackageEcosystem,
|
|
17
18
|
PackageSourceOperationResult,
|
|
18
19
|
RepoFetchResult,
|
|
19
20
|
SourcegraphCodeCandidate,
|
|
@@ -25,7 +26,7 @@ import type {
|
|
|
25
26
|
WorkspaceMapResult,
|
|
26
27
|
WorkspaceQueryOutcome,
|
|
27
28
|
} from "@danypops/lector";
|
|
28
|
-
import { DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS } from "@danypops/lector";
|
|
29
|
+
import { DEFAULT_EXTERNAL_SEARCH_MAX_RESULTS, PACKAGE_ECOSYSTEMS } from "@danypops/lector";
|
|
29
30
|
import {
|
|
30
31
|
type AgentToolResult,
|
|
31
32
|
createEditToolDefinition,
|
|
@@ -85,8 +86,18 @@ import { createLectorLineEditOperations } from "./line-edit-operations.ts";
|
|
|
85
86
|
import { formatLineEditCall, formatLineEditResult } from "./line-edit-rendering.ts";
|
|
86
87
|
import { createMutationHistoryOperations } from "./mutation-history-operations.ts";
|
|
87
88
|
import { isFilesystemRoot, nearestGitRoot } from "./nearest-workspace-root.ts";
|
|
88
|
-
import { createLectorPackageSourceOperations } from "./package-source-operations.ts";
|
|
89
|
-
import {
|
|
89
|
+
import { createLectorPackageSourceOperations, type PackageSourceListPage } from "./package-source-operations.ts";
|
|
90
|
+
import {
|
|
91
|
+
buildPackageSourceListTableRows,
|
|
92
|
+
formatPackageSourceCall,
|
|
93
|
+
formatPackageSourceCleanResult,
|
|
94
|
+
formatPackageSourceListResult,
|
|
95
|
+
formatPackageSourceRemoveResult,
|
|
96
|
+
formatPackageSourceResult,
|
|
97
|
+
PACKAGE_SOURCE_LIST_TABLE_COLUMNS,
|
|
98
|
+
PACKAGE_SOURCE_LIST_VISIBLE_ROWS,
|
|
99
|
+
packageSourceListMoreLine,
|
|
100
|
+
} from "./package-source-rendering.ts";
|
|
90
101
|
import { createLectorReadOperations } from "./read-operations.ts";
|
|
91
102
|
import { createReferenceBasedRenameOperations } from "./reference-based-rename-operations.ts";
|
|
92
103
|
import { createRenameOperations } from "./rename-operations.ts";
|
|
@@ -1371,23 +1382,78 @@ export default function (pi: ExtensionAPI) {
|
|
|
1371
1382
|
},
|
|
1372
1383
|
});
|
|
1373
1384
|
|
|
1385
|
+
type PackageSourceToolDetails =
|
|
1386
|
+
| { readonly action: "resolve"; readonly result: PackageSourceOperationResult }
|
|
1387
|
+
| { readonly action: "list"; readonly page: PackageSourceListPage }
|
|
1388
|
+
| { readonly action: "remove"; readonly result: { removed: boolean } }
|
|
1389
|
+
| { readonly action: "clean"; readonly result: { removed: number; skipped: number } };
|
|
1390
|
+
|
|
1391
|
+
/** A real type guard, not an assertion -- params.ecosystem arrives as a bare TypeBox string; this is the one place that turns it into PackageEcosystem, with a runtime check backing the narrowing. */
|
|
1392
|
+
function isPackageEcosystem(value: string): value is PackageEcosystem {
|
|
1393
|
+
return (PACKAGE_ECOSYSTEMS as readonly string[]).includes(value);
|
|
1394
|
+
}
|
|
1395
|
+
function optionalPackageEcosystem(value: string | undefined): PackageEcosystem | undefined {
|
|
1396
|
+
if (value === undefined) return undefined;
|
|
1397
|
+
if (!isPackageEcosystem(value)) throw new Error(`ecosystem must be one of ${PACKAGE_ECOSYSTEMS.join(", ")}; got "${value}"`);
|
|
1398
|
+
return value;
|
|
1399
|
+
}
|
|
1400
|
+
function requirePackageEcosystem(value: string | undefined): PackageEcosystem {
|
|
1401
|
+
if (value === undefined || !isPackageEcosystem(value)) {
|
|
1402
|
+
throw new Error(`ecosystem must be one of ${PACKAGE_ECOSYSTEMS.join(", ")}; got "${value ?? ""}"`);
|
|
1403
|
+
}
|
|
1404
|
+
return value;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1374
1407
|
const packageSourceOperations = createLectorPackageSourceOperations();
|
|
1375
1408
|
pi.registerTool({
|
|
1376
1409
|
name: "package_source",
|
|
1377
1410
|
label: "Package Source",
|
|
1378
1411
|
description:
|
|
1379
|
-
"Resolve
|
|
1380
|
-
promptSnippet: "Resolve
|
|
1412
|
+
"Resolve, list, remove, or clean bookkeeping for installed npm packages resolved to verified exact repository source. action=resolve uses the project's lockfile, bounded registry metadata, and an exact Git ref/commit; registers verified source as a read-only workspace for the other Lector tools. action=list reports every package coordinate already resolved this way -- no re-resolution, no network. action=remove drops one bookkeeping entry by its exact ecosystem/name/resolvedVersion; refuses if it is still a currently-registered workspace. action=clean removes every non-in-use entry, optionally scoped to one ecosystem. Neither remove nor clean deletes the underlying repo_cache disk entry -- use repo_cache(action=evict) for that, since a monorepo can share one checkout across several package coordinates.",
|
|
1413
|
+
promptSnippet: "Resolve, list, remove, or clean verified package source bookkeeping",
|
|
1381
1414
|
parameters: Type.Object({
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1415
|
+
action: Type.Optional(Type.Union([Type.Literal("resolve"), Type.Literal("list"), Type.Literal("remove"), Type.Literal("clean")])),
|
|
1416
|
+
directory: Type.Optional(Type.String({ description: "Required for action=resolve -- project directory containing the npm-family lockfile" })),
|
|
1417
|
+
name: Type.Optional(Type.String({ description: "Required for action=resolve/remove -- installed package name, including scope when present" })),
|
|
1418
|
+
version: Type.Optional(
|
|
1419
|
+
Type.String({ description: "action=resolve only -- exact installed version; required when the lockfile contains several versions" }),
|
|
1420
|
+
),
|
|
1385
1421
|
registry: Type.Optional(Type.String({ description: "npm registry URL; defaults to the public npm registry" })),
|
|
1422
|
+
ecosystem: Type.Optional(Type.String({ description: "Required for action=remove; optional filter for action=list/clean" })),
|
|
1423
|
+
resolvedVersion: Type.Optional(Type.String({ description: "Required for action=remove -- the exact resolved version to remove" })),
|
|
1424
|
+
text: Type.Optional(Type.String({ description: "action=list only -- case-insensitive substring match across ecosystem/name/resolvedVersion" })),
|
|
1425
|
+
maxResults: Type.Optional(Type.Number({ description: "Required for action=list -- maximum entries to return in this page" })),
|
|
1426
|
+
cursor: Type.Optional(Type.String({ description: "action=list only -- opaque cursor from a prior call's nextCursor, to fetch the next page" })),
|
|
1386
1427
|
}),
|
|
1387
|
-
async execute(_toolCallId, params) {
|
|
1428
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<PackageSourceToolDetails>> {
|
|
1429
|
+
if (params.action === "list") {
|
|
1430
|
+
if (params.maxResults === undefined) throw new Error("package_source action=list requires maxResults");
|
|
1431
|
+
const page = await packageSourceOperations.list({
|
|
1432
|
+
ecosystem: optionalPackageEcosystem(params.ecosystem),
|
|
1433
|
+
text: params.text,
|
|
1434
|
+
maxResults: params.maxResults,
|
|
1435
|
+
cursor: params.cursor,
|
|
1436
|
+
});
|
|
1437
|
+
return { content: [{ type: "text", text: JSON.stringify(page) }], details: { action: "list", page } };
|
|
1438
|
+
}
|
|
1439
|
+
if (params.action === "remove") {
|
|
1440
|
+
if (!params.name || !params.resolvedVersion) throw new Error("package_source action=remove requires ecosystem, name, and resolvedVersion");
|
|
1441
|
+
const result = await packageSourceOperations.remove(
|
|
1442
|
+
requirePackageEcosystem(params.ecosystem),
|
|
1443
|
+
params.registry ?? null,
|
|
1444
|
+
params.name,
|
|
1445
|
+
params.resolvedVersion,
|
|
1446
|
+
);
|
|
1447
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "remove", result } };
|
|
1448
|
+
}
|
|
1449
|
+
if (params.action === "clean") {
|
|
1450
|
+
const result = await packageSourceOperations.clean(optionalPackageEcosystem(params.ecosystem));
|
|
1451
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "clean", result } };
|
|
1452
|
+
}
|
|
1453
|
+
if (!params.directory || !params.name) throw new Error("package_source action=resolve requires directory and name");
|
|
1388
1454
|
const directory = resolve(cwd, params.directory);
|
|
1389
1455
|
const result = await packageSourceOperations.resolve(directory, params.name, params.version ?? null, params.registry ?? null);
|
|
1390
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { result } };
|
|
1456
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "resolve", result } };
|
|
1391
1457
|
},
|
|
1392
1458
|
renderCall(args, theme, context) {
|
|
1393
1459
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
@@ -1395,7 +1461,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
1395
1461
|
return text;
|
|
1396
1462
|
},
|
|
1397
1463
|
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
1398
|
-
if (isPartial) return new Text(theme.fg("warning", "
|
|
1464
|
+
if (isPartial) return new Text(theme.fg("warning", "Working on package source..."), 0, 0);
|
|
1399
1465
|
if (context.isError) {
|
|
1400
1466
|
const errorText = result.content
|
|
1401
1467
|
.filter((block) => block.type === "text")
|
|
@@ -1403,9 +1469,26 @@ export default function (pi: ExtensionAPI) {
|
|
|
1403
1469
|
.join("\n");
|
|
1404
1470
|
return new Text(theme.fg("error", errorText || "package_source failed"), 0, 0);
|
|
1405
1471
|
}
|
|
1406
|
-
const details = result.details as
|
|
1472
|
+
const details = result.details as PackageSourceToolDetails | undefined;
|
|
1473
|
+
// A non-empty list renders as a real, bounded Table -- the human channel actually shows
|
|
1474
|
+
// package/workspace/path/size, not just a bare count, capped at PACKAGE_SOURCE_LIST_VISIBLE_ROWS
|
|
1475
|
+
// since the index can grow arbitrarily large even though maxResults bounds any one page.
|
|
1476
|
+
if (details?.action === "list" && details.page.entries.length > 0) {
|
|
1477
|
+
return renderBoundedTable({
|
|
1478
|
+
columns: PACKAGE_SOURCE_LIST_TABLE_COLUMNS,
|
|
1479
|
+
rows: buildPackageSourceListTableRows(details.page.entries),
|
|
1480
|
+
expanded,
|
|
1481
|
+
visibleRowCount: PACKAGE_SOURCE_LIST_VISIBLE_ROWS,
|
|
1482
|
+
moreLine: packageSourceListMoreLine(theme),
|
|
1483
|
+
measure: tableMeasure,
|
|
1484
|
+
headerStyle: (s) => theme.fg("muted", theme.bold(s)),
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1407
1487
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1408
|
-
text.setText(
|
|
1488
|
+
if (details?.action === "list") text.setText(formatPackageSourceListResult(details.page, theme));
|
|
1489
|
+
else if (details?.action === "remove") text.setText(formatPackageSourceRemoveResult(details.result, theme));
|
|
1490
|
+
else if (details?.action === "clean") text.setText(formatPackageSourceCleanResult(details.result, theme));
|
|
1491
|
+
else text.setText(formatPackageSourceResult(details?.action === "resolve" ? details.result : undefined, expanded, theme));
|
|
1409
1492
|
return text;
|
|
1410
1493
|
},
|
|
1411
1494
|
});
|
|
@@ -53,7 +53,14 @@ export function setNewWorkspaceObserver(observer: ((root: string) => void) | und
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
export interface RetryingLectorClient {
|
|
56
|
+
/** Transparently retries once on a stale connection -- right for a read-only or genuinely idempotent operation. */
|
|
56
57
|
call<Name extends OperationName>(operation: Name, input: OperationInputs[Name]): Promise<OperationOutputs[Name]>;
|
|
58
|
+
/**
|
|
59
|
+
* Like call(), but never retries the operation itself after a failure -- only the underlying
|
|
60
|
+
* connection resets, so the *next* call()/callOnce() reconnects. Use for a mutating/non-idempotent
|
|
61
|
+
* operation, where transparently re-running it after a transport failure could double the side effect.
|
|
62
|
+
*/
|
|
63
|
+
callOnce<Name extends OperationName>(operation: Name, input: OperationInputs[Name]): Promise<OperationOutputs[Name]>;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
// Kept async even though its own body has no await: every call site across this package does
|
|
@@ -62,6 +69,7 @@ export interface RetryingLectorClient {
|
|
|
62
69
|
export async function lectorClient(): Promise<RetryingLectorClient> {
|
|
63
70
|
return {
|
|
64
71
|
call: (operation, input) => retryingClient.call((client) => client.call(operation, input)),
|
|
72
|
+
callOnce: (operation, input) => retryingClient.callOnce((client) => client.call(operation, input)),
|
|
65
73
|
};
|
|
66
74
|
}
|
|
67
75
|
|
|
@@ -75,7 +83,7 @@ async function workspaceForRoot(root: string): Promise<ResolvedWorkspace> {
|
|
|
75
83
|
const cached = workspaceIdByRoot.get(root);
|
|
76
84
|
if (cached) return { workspaceId: cached, root };
|
|
77
85
|
const client = await lectorClient();
|
|
78
|
-
const { workspaceId } = await client.
|
|
86
|
+
const { workspaceId } = await client.callOnce("workspace.registerPath", { path: root });
|
|
79
87
|
workspaceIdByRoot.set(root, workspaceId);
|
|
80
88
|
onNewWorkspace?.(root);
|
|
81
89
|
return { workspaceId, root };
|
|
@@ -23,7 +23,7 @@ export function createLectorLineEditOperations(): LineEditOperations {
|
|
|
23
23
|
async ({ workspaceId, root }) => {
|
|
24
24
|
const client = await lectorClient();
|
|
25
25
|
const relativePath = toWorkspaceRelativePath(root, path);
|
|
26
|
-
return client.
|
|
26
|
+
return client.callOnce("workspace.lineEdit", { workspaceId, path: relativePath, edits });
|
|
27
27
|
},
|
|
28
28
|
);
|
|
29
29
|
},
|
|
@@ -26,7 +26,7 @@ export function createMutationHistoryOperations(): MutationHistoryOperations {
|
|
|
26
26
|
() => workspaceForPath(absolutePath),
|
|
27
27
|
async ({ workspaceId }) => {
|
|
28
28
|
const client = await lectorClient();
|
|
29
|
-
return client.
|
|
29
|
+
return client.callOnce("workspace.revertMutation", { workspaceId, entryId });
|
|
30
30
|
},
|
|
31
31
|
);
|
|
32
32
|
},
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
-
import { DEFAULT_PACKAGE_SOURCE_BOUNDS, type PackageSourceOperationResult } from "@danypops/lector";
|
|
1
|
+
import { DEFAULT_PACKAGE_SOURCE_BOUNDS, type PackageEcosystem, type PackageSourceListEntry, type PackageSourceOperationResult } from "@danypops/lector";
|
|
2
2
|
import { lectorClient } from "./lector-client.ts";
|
|
3
3
|
|
|
4
|
+
export interface PackageSourceListPage {
|
|
5
|
+
readonly entries: readonly PackageSourceListEntry[];
|
|
6
|
+
readonly nextCursor: string | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
4
9
|
export interface PackageSourceOperations {
|
|
5
10
|
resolve(directory: string, name: string, requestedVersion: string | null, registry: string | null): Promise<PackageSourceOperationResult>;
|
|
11
|
+
list(options: { ecosystem?: PackageEcosystem; text?: string; maxResults: number; cursor?: string }): Promise<PackageSourceListPage>;
|
|
12
|
+
remove(ecosystem: PackageEcosystem, registry: string | null, name: string, resolvedVersion: string): Promise<{ removed: boolean }>;
|
|
13
|
+
clean(ecosystem: PackageEcosystem | undefined): Promise<{ removed: number; skipped: number }>;
|
|
6
14
|
}
|
|
7
15
|
|
|
8
16
|
export function createLectorPackageSourceOperations(): PackageSourceOperations {
|
|
9
17
|
return {
|
|
10
18
|
async resolve(directory, name, requestedVersion, registry) {
|
|
11
19
|
const client = await lectorClient();
|
|
12
|
-
return client.
|
|
20
|
+
return client.callOnce("package.resolveSource", {
|
|
13
21
|
request: {
|
|
14
22
|
projectRoot: directory,
|
|
15
23
|
coordinate: { ecosystem: "npm", registry, name, requestedVersion },
|
|
@@ -17,5 +25,17 @@ export function createLectorPackageSourceOperations(): PackageSourceOperations {
|
|
|
17
25
|
bounds: DEFAULT_PACKAGE_SOURCE_BOUNDS,
|
|
18
26
|
});
|
|
19
27
|
},
|
|
28
|
+
async list(options) {
|
|
29
|
+
const client = await lectorClient();
|
|
30
|
+
return client.call("package.listSources", options);
|
|
31
|
+
},
|
|
32
|
+
async remove(ecosystem, registry, name, resolvedVersion) {
|
|
33
|
+
const client = await lectorClient();
|
|
34
|
+
return client.callOnce("package.removeSource", { ecosystem, registry, name, resolvedVersion });
|
|
35
|
+
},
|
|
36
|
+
async clean(ecosystem) {
|
|
37
|
+
const client = await lectorClient();
|
|
38
|
+
return client.callOnce("package.cleanSources", { ecosystem });
|
|
39
|
+
},
|
|
20
40
|
};
|
|
21
41
|
}
|
|
@@ -1,14 +1,36 @@
|
|
|
1
|
-
import type { PackageSourceOperationResult } from "@danypops/lector";
|
|
2
|
-
import {
|
|
1
|
+
import type { PackageSourceListEntry, PackageSourceOperationResult } from "@danypops/lector";
|
|
2
|
+
import { keyHint } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { renderTruncatedList, type TableColumn } from "malevich-tui-components";
|
|
3
4
|
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
4
5
|
|
|
5
6
|
const DEFAULT_VISIBLE_CANDIDATES = 5;
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
/** Table has no row-count bound of its own; the underlying index can grow arbitrarily large even though package.listSources's own maxResults bounds any one page. Mirrors REPO_CACHE_VISIBLE_ROWS. */
|
|
9
|
+
export const PACKAGE_SOURCE_LIST_VISIBLE_ROWS = 20;
|
|
10
|
+
|
|
11
|
+
type PackageSourceAction = "resolve" | "list" | "remove" | "clean";
|
|
12
|
+
|
|
13
|
+
export function formatPackageSourceCall(
|
|
14
|
+
args: { action?: unknown; directory?: unknown; name?: unknown; version?: unknown; ecosystem?: unknown; resolvedVersion?: unknown; text?: unknown },
|
|
15
|
+
theme: LectorTheme,
|
|
16
|
+
): string {
|
|
17
|
+
const label = theme.fg("toolTitle", theme.bold("package_source"));
|
|
18
|
+
const action: PackageSourceAction = args.action === "list" || args.action === "remove" || args.action === "clean" ? args.action : "resolve";
|
|
19
|
+
if (action === "list") {
|
|
20
|
+
const text = typeof args.text === "string" && args.text.length > 0 ? ` ${theme.fg("dim", args.text)}` : "";
|
|
21
|
+
return `${label} ${theme.fg("accent", "list")}${text}`;
|
|
22
|
+
}
|
|
23
|
+
if (action === "remove" || action === "clean") {
|
|
24
|
+
const name = typeof args.name === "string" ? args.name : "";
|
|
25
|
+
const version = typeof args.resolvedVersion === "string" ? `@${args.resolvedVersion}` : "";
|
|
26
|
+
const ecosystem = typeof args.ecosystem === "string" ? args.ecosystem : "";
|
|
27
|
+
const identity = name ? `${name}${version}` : ecosystem;
|
|
28
|
+
return `${label} ${theme.fg("accent", action)}${identity ? ` ${theme.fg("dim", identity)}` : ""}`;
|
|
29
|
+
}
|
|
8
30
|
const name = typeof args.name === "string" ? args.name : "";
|
|
9
31
|
const version = typeof args.version === "string" ? `@${args.version}` : "";
|
|
10
32
|
const directory = typeof args.directory === "string" ? args.directory : "";
|
|
11
|
-
return `${
|
|
33
|
+
return `${label} ${theme.fg("accent", `${name}${version}`)} ${theme.fg("dim", directory)}`.trim();
|
|
12
34
|
}
|
|
13
35
|
|
|
14
36
|
export function formatPackageSourceResult(result: PackageSourceOperationResult | undefined, expanded: boolean, theme: LectorTheme): string {
|
|
@@ -42,3 +64,54 @@ export function formatPackageSourceResult(result: PackageSourceOperationResult |
|
|
|
42
64
|
if (outcome.status === "mismatched") return theme.fg("error", `Source mismatch (${outcome.code}): expected ${outcome.expected}, got ${outcome.actual}.`);
|
|
43
65
|
return theme.fg("warning", `Source unavailable (${outcome.code}).`);
|
|
44
66
|
}
|
|
67
|
+
|
|
68
|
+
/** Empty-state fallback only -- a non-empty page renders as a real Table (see buildPackageSourceListTableRows) so the human channel actually shows what's resolved, not just a bare count. Mirrors formatRepoCacheListResult. */
|
|
69
|
+
export function formatPackageSourceListResult(
|
|
70
|
+
page: { entries: readonly PackageSourceListEntry[]; nextCursor?: string | null } | undefined,
|
|
71
|
+
theme: LectorTheme,
|
|
72
|
+
): string {
|
|
73
|
+
const count = page?.entries.length ?? 0;
|
|
74
|
+
return count === 0 ? theme.fg("dim", "no resolved package sources") : theme.fg("success", `${count} resolved package source${count === 1 ? "" : "s"}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Powers of 1024, one decimal past the first. Mirrors repo-cache-rendering.ts's own formatCacheSize -- kept as a small local duplicate rather than a shared export, matching that file's own precedent. */
|
|
78
|
+
function formatCacheSize(bytes: number): string {
|
|
79
|
+
const units = ["B", "KB", "MB", "GB", "TB"] as const;
|
|
80
|
+
let value = bytes;
|
|
81
|
+
let unitIndex = 0;
|
|
82
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
83
|
+
value /= 1024;
|
|
84
|
+
unitIndex++;
|
|
85
|
+
}
|
|
86
|
+
return `${unitIndex === 0 ? value : value.toFixed(1)} ${units[unitIndex]}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function buildPackageSourceListTableRows(entries: readonly PackageSourceListEntry[]): Record<string, string>[] {
|
|
90
|
+
return entries.map((entry) => ({
|
|
91
|
+
package: `${entry.name}@${entry.resolvedVersion}`,
|
|
92
|
+
workspace: entry.workspaceId,
|
|
93
|
+
path: entry.cachePath,
|
|
94
|
+
size: entry.cacheSizeBytes === null ? "unknown" : formatCacheSize(entry.cacheSizeBytes),
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const PACKAGE_SOURCE_LIST_TABLE_COLUMNS: TableColumn[] = [
|
|
99
|
+
{ header: "Package", key: "package" },
|
|
100
|
+
{ header: "Workspace", key: "workspace" },
|
|
101
|
+
{ header: "Path", key: "path" },
|
|
102
|
+
{ header: "Size", key: "size" },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
export function packageSourceListMoreLine(theme: LectorTheme): (hiddenCount: number) => string {
|
|
106
|
+
return (hiddenCount) => theme.fg("dim", `... ${hiddenCount} more (${keyHint("app.tools.expand", "to expand")})`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function formatPackageSourceRemoveResult(result: { removed: boolean } | undefined, theme: LectorTheme): string {
|
|
110
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
111
|
+
return result.removed ? theme.fg("success", "removed") : theme.fg("dim", "not recorded for that coordinate");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function formatPackageSourceCleanResult(result: { removed: number; skipped: number } | undefined, theme: LectorTheme): string {
|
|
115
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
116
|
+
return theme.fg("success", `removed ${result.removed}, skipped ${result.skipped} (still in use)`);
|
|
117
|
+
}
|
|
@@ -18,7 +18,7 @@ export function createReferenceBasedRenameOperations(): ReferenceBasedRenameOper
|
|
|
18
18
|
() => workspaceForCodeIntelligencePath(fromPath),
|
|
19
19
|
async ({ workspaceId }) => {
|
|
20
20
|
const client = await lectorClient();
|
|
21
|
-
return client.
|
|
21
|
+
return client.callOnce("workspace.referenceBasedRename", { workspaceId, fromPath, toPath, maxFiles, maxSymbolsPerFile });
|
|
22
22
|
},
|
|
23
23
|
);
|
|
24
24
|
},
|
|
@@ -28,7 +28,7 @@ export function createRenameOperations(): RenameOperations {
|
|
|
28
28
|
() => workspaceForCodeIntelligencePath(path),
|
|
29
29
|
async ({ workspaceId }) => {
|
|
30
30
|
const client = await lectorClient();
|
|
31
|
-
return client.
|
|
31
|
+
return client.callOnce("workspace.rename", { workspaceId, path, line, character, newName });
|
|
32
32
|
},
|
|
33
33
|
);
|
|
34
34
|
},
|
|
@@ -13,7 +13,7 @@ export function createRepoCacheEvictOperations(): RepoCacheEvictOperations {
|
|
|
13
13
|
return {
|
|
14
14
|
async evict(host, owner, repo, ref) {
|
|
15
15
|
const client = await lectorClient();
|
|
16
|
-
return client.
|
|
16
|
+
return client.callOnce("repo.evictCache", { host, owner, repo, ref });
|
|
17
17
|
},
|
|
18
18
|
};
|
|
19
19
|
}
|
|
@@ -14,7 +14,7 @@ export function createLectorRepoFetchOperations(): RepoFetchOperations {
|
|
|
14
14
|
return {
|
|
15
15
|
async fetch(host, owner, repo, ref, forceRefresh) {
|
|
16
16
|
const client = await lectorClient();
|
|
17
|
-
return client.
|
|
17
|
+
return client.callOnce("repo.fetch", { host, owner, repo, ref, forceRefresh });
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
}
|
|
@@ -49,7 +49,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
49
49
|
() => workspaceForCodeIntelligencePath(path),
|
|
50
50
|
async ({ workspaceId }) => {
|
|
51
51
|
const client = await lectorClient();
|
|
52
|
-
return client.
|
|
52
|
+
return client.callOnce("workspace.createAnnotation", { workspaceId, subtype, title, body, anchors });
|
|
53
53
|
},
|
|
54
54
|
);
|
|
55
55
|
},
|
|
@@ -82,7 +82,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
82
82
|
() => workspaceForCodeIntelligencePath(path),
|
|
83
83
|
async ({ workspaceId }) => {
|
|
84
84
|
const client = await lectorClient();
|
|
85
|
-
return client.
|
|
85
|
+
return client.callOnce("workspace.refreshAnnotation", { workspaceId, id, subtype, title, body, anchors });
|
|
86
86
|
},
|
|
87
87
|
);
|
|
88
88
|
},
|
|
@@ -91,7 +91,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
91
91
|
() => workspaceForCodeIntelligencePath(path),
|
|
92
92
|
async ({ workspaceId }) => {
|
|
93
93
|
const client = await lectorClient();
|
|
94
|
-
return client.
|
|
94
|
+
return client.callOnce("workspace.scrubAnnotation", { workspaceId, id });
|
|
95
95
|
},
|
|
96
96
|
);
|
|
97
97
|
},
|
|
@@ -100,7 +100,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
100
100
|
() => workspaceForCodeIntelligencePath(path),
|
|
101
101
|
async ({ workspaceId }) => {
|
|
102
102
|
const client = await lectorClient();
|
|
103
|
-
return client.
|
|
103
|
+
return client.callOnce("workspace.restoreAnnotation", { workspaceId, id });
|
|
104
104
|
},
|
|
105
105
|
);
|
|
106
106
|
},
|
|
@@ -109,7 +109,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
109
109
|
() => workspaceForCodeIntelligencePath(path),
|
|
110
110
|
async ({ workspaceId }) => {
|
|
111
111
|
const client = await lectorClient();
|
|
112
|
-
return client.
|
|
112
|
+
return client.callOnce("workspace.containAnnotation", { workspaceId, parentId, childId });
|
|
113
113
|
},
|
|
114
114
|
);
|
|
115
115
|
},
|
|
@@ -118,7 +118,7 @@ export function createLectorSymbolAnnotationOperations(): SymbolAnnotationOperat
|
|
|
118
118
|
() => workspaceForCodeIntelligencePath(path),
|
|
119
119
|
async ({ workspaceId }) => {
|
|
120
120
|
const client = await lectorClient();
|
|
121
|
-
return client.
|
|
121
|
+
return client.callOnce("workspace.uncontainAnnotation", { workspaceId, parentId, childId });
|
|
122
122
|
},
|
|
123
123
|
);
|
|
124
124
|
},
|
|
@@ -23,7 +23,7 @@ export function createWorkspaceCacheOperations(): WorkspaceCacheOperations {
|
|
|
23
23
|
() => workspaceForDirectory(directory),
|
|
24
24
|
async ({ workspaceId }) => {
|
|
25
25
|
const client = await lectorClient();
|
|
26
|
-
const { job } = await client.
|
|
26
|
+
const { job } = await client.callOnce("job.submit", {
|
|
27
27
|
operation: "workspace.populateSymbolGraph",
|
|
28
28
|
input: { workspaceId, maxFiles, maxSymbolsPerFile },
|
|
29
29
|
waitMs: 0,
|
|
@@ -41,7 +41,7 @@ export function createLectorWriteOperations(): WriteOperations {
|
|
|
41
41
|
|
|
42
42
|
for (let attempt = 0; attempt < MAX_STALE_HASH_RETRIES; attempt++) {
|
|
43
43
|
try {
|
|
44
|
-
await client.
|
|
44
|
+
await client.callOnce("workspace.exactEdit", { workspaceId, path: relativePath, expectedHash, content });
|
|
45
45
|
return;
|
|
46
46
|
} catch (error) {
|
|
47
47
|
if (!remoteErrorIs(error, "StaleExpectedHash") || attempt === MAX_STALE_HASH_RETRIES - 1) throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Pi host adapter for Lector: overrides read/write/edit with a daemon-backed, hash-guarded filesystem",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"typebox": "*"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@danypops/vehicle-client": "^0.
|
|
22
|
-
"@danypops/lector": "^0.
|
|
21
|
+
"@danypops/vehicle-client": "^0.2.0",
|
|
22
|
+
"@danypops/lector": "^0.12.0",
|
|
23
23
|
"malevich-tui-components": "^0.19.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|