@danypops/pi-lector 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/extension/src/external-search-operations.ts +26 -0
- package/extension/src/external-search-rendering.ts +27 -0
- package/extension/src/index.ts +99 -44
- package/extension/src/repo-cache-evict-operations.ts +19 -0
- package/extension/src/repo-cache-rendering.ts +42 -0
- package/extension/src/repo-fetch-operations.ts +3 -3
- package/package.json +2 -2
- package/extension/src/repo-fetch-rendering.ts +0 -21
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeCandidate } from "@danypops/lector";
|
|
2
|
+
import { lectorClient } from "./lector-client.ts";
|
|
3
|
+
|
|
4
|
+
/** Thin wrapper over search.githubRepos/search.npmPackages/search.sourcegraphCode -- explicit-query discovery inputs shaped for repo_cache/package_source, never open-ended discovery/trending. */
|
|
5
|
+
export interface ExternalSearchOperations {
|
|
6
|
+
githubRepos(query: string, maxResults: number): Promise<GithubRepoSearchResult>;
|
|
7
|
+
npmPackages(query: string, maxResults: number): Promise<{ candidates: readonly NpmPackageCandidate[] }>;
|
|
8
|
+
sourcegraphCode(query: string, maxResults: number): Promise<{ candidates: readonly SourcegraphCodeCandidate[] }>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createExternalSearchOperations(): ExternalSearchOperations {
|
|
12
|
+
return {
|
|
13
|
+
async githubRepos(query, maxResults) {
|
|
14
|
+
const client = await lectorClient();
|
|
15
|
+
return client.call("search.githubRepos", { query, maxResults });
|
|
16
|
+
},
|
|
17
|
+
async npmPackages(query, maxResults) {
|
|
18
|
+
const client = await lectorClient();
|
|
19
|
+
return client.call("search.npmPackages", { query, maxResults });
|
|
20
|
+
},
|
|
21
|
+
async sourcegraphCode(query, maxResults) {
|
|
22
|
+
const client = await lectorClient();
|
|
23
|
+
return client.call("search.sourcegraphCode", { query, maxResults });
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { GithubRepoSearchResult, NpmPackageCandidate, SourcegraphCodeCandidate } from "@danypops/lector";
|
|
2
|
+
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
+
|
|
4
|
+
type ExternalSearchAction = "github_repos" | "npm_packages" | "sourcegraph_code";
|
|
5
|
+
|
|
6
|
+
export function formatExternalSearchCall(action: ExternalSearchAction, args: { query?: unknown }, theme: LectorTheme): string {
|
|
7
|
+
const label = theme.fg("toolTitle", theme.bold("external_search"));
|
|
8
|
+
const query = typeof args.query === "string" ? args.query : "";
|
|
9
|
+
return `${label} ${theme.fg("accent", action)} ${theme.fg("dim", query)}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatGithubRepoSearchResult(result: GithubRepoSearchResult | undefined, theme: LectorTheme): string {
|
|
13
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
14
|
+
const count = result.candidates.length;
|
|
15
|
+
const summary = count === 0 ? theme.fg("dim", "no repositories matched") : theme.fg("success", `${count} repositor${count === 1 ? "y" : "ies"}`);
|
|
16
|
+
return result.authenticated ? summary : `${summary} ${theme.fg("warning", "(unauthenticated -- lower rate limit)")}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function formatNpmPackageSearchResult(result: { candidates: readonly NpmPackageCandidate[] } | undefined, theme: LectorTheme): string {
|
|
20
|
+
const count = result?.candidates.length ?? 0;
|
|
21
|
+
return count === 0 ? theme.fg("dim", "no packages matched") : theme.fg("success", `${count} package${count === 1 ? "" : "s"}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function formatSourcegraphCodeSearchResult(result: { candidates: readonly SourcegraphCodeCandidate[] } | undefined, theme: LectorTheme): string {
|
|
25
|
+
const count = result?.candidates.length ?? 0;
|
|
26
|
+
return count === 0 ? theme.fg("dim", "no code matches") : theme.fg("success", `${count} match${count === 1 ? "" : "es"}`);
|
|
27
|
+
}
|
package/extension/src/index.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
2
|
import type {
|
|
3
|
+
CachedRepositoryPage,
|
|
3
4
|
ContentHash,
|
|
4
5
|
Diagnostic,
|
|
5
6
|
DocumentSymbolEntry,
|
|
6
7
|
EditOutcome,
|
|
7
8
|
FindFilesResult,
|
|
9
|
+
GithubRepoSearchResult,
|
|
8
10
|
Hover,
|
|
9
11
|
IntelligenceProvenance,
|
|
10
12
|
JobSnapshot,
|
|
11
13
|
LineEdit,
|
|
12
14
|
LineEditOutcome,
|
|
13
15
|
MutationHistoryEntry,
|
|
16
|
+
NpmPackageCandidate,
|
|
14
17
|
OperationOutputs,
|
|
15
18
|
PackageSourceOperationResult,
|
|
16
19
|
PopulateSymbolGraphResult,
|
|
17
20
|
RepoFetchResult,
|
|
21
|
+
SourcegraphCodeCandidate,
|
|
18
22
|
SymbolAnnotation,
|
|
19
23
|
SymbolNode,
|
|
20
24
|
SymbolSearchResult,
|
|
@@ -62,6 +66,13 @@ import {
|
|
|
62
66
|
import { createLectorCrossWorkspaceSearchOperations } from "./cross-workspace-search-operations.ts";
|
|
63
67
|
import { formatCrossWorkspaceCall, formatFindSymbolsAcrossProjectsResult, formatSearchTextAcrossProjectsResult } from "./cross-workspace-search-rendering.ts";
|
|
64
68
|
import { createLectorEditOperations } from "./edit-operations.ts";
|
|
69
|
+
import { createExternalSearchOperations } from "./external-search-operations.ts";
|
|
70
|
+
import {
|
|
71
|
+
formatExternalSearchCall,
|
|
72
|
+
formatGithubRepoSearchResult,
|
|
73
|
+
formatNpmPackageSearchResult,
|
|
74
|
+
formatSourcegraphCodeSearchResult,
|
|
75
|
+
} from "./external-search-rendering.ts";
|
|
65
76
|
import { createLectorFindFilesOperations } from "./find-files-operations.ts";
|
|
66
77
|
import { formatFindFilesCall, formatFindFilesResult } from "./find-files-rendering.ts";
|
|
67
78
|
import { createLectorFindSymbolsOperations } from "./find-symbols-operations.ts";
|
|
@@ -78,9 +89,10 @@ import { formatPackageSourceCall, formatPackageSourceResult } from "./package-so
|
|
|
78
89
|
import { createLectorReadOperations } from "./read-operations.ts";
|
|
79
90
|
import { createReferenceBasedRenameOperations } from "./reference-based-rename-operations.ts";
|
|
80
91
|
import { createRenameOperations } from "./rename-operations.ts";
|
|
92
|
+
import { createRepoCacheEvictOperations } from "./repo-cache-evict-operations.ts";
|
|
81
93
|
import { createRepoCacheListOperations } from "./repo-cache-list-operations.ts";
|
|
94
|
+
import { formatRepoCacheCall, formatRepoCacheEvictResult, formatRepoCacheListResult, formatRepoFetchResult } from "./repo-cache-rendering.ts";
|
|
82
95
|
import { createLectorRepoFetchOperations } from "./repo-fetch-operations.ts";
|
|
83
|
-
import { formatRepoFetchCall, formatRepoFetchResult } from "./repo-fetch-rendering.ts";
|
|
84
96
|
import { createLectorSearchOperations } from "./search-operations.ts";
|
|
85
97
|
import { formatSearchCall, formatSearchResult } from "./search-rendering.ts";
|
|
86
98
|
import { type AnnotationAnchorInput, createLectorSymbolAnnotationOperations } from "./symbol-annotation-operations.ts";
|
|
@@ -1445,88 +1457,131 @@ export default function (pi: ExtensionAPI) {
|
|
|
1445
1457
|
},
|
|
1446
1458
|
});
|
|
1447
1459
|
|
|
1460
|
+
type RepoCacheToolDetails =
|
|
1461
|
+
| { readonly action: "fetch"; readonly result: RepoFetchResult & { workspaceId: string } }
|
|
1462
|
+
| { readonly action: "list"; readonly page: CachedRepositoryPage }
|
|
1463
|
+
| { readonly action: "evict"; readonly result: { evicted: boolean } };
|
|
1464
|
+
|
|
1448
1465
|
const repoFetchOperations = createLectorRepoFetchOperations();
|
|
1449
1466
|
const repoCacheListOperations = createRepoCacheListOperations();
|
|
1467
|
+
const repoCacheEvictOperations = createRepoCacheEvictOperations();
|
|
1450
1468
|
pi.registerTool({
|
|
1451
|
-
name: "
|
|
1452
|
-
label: "Repo
|
|
1469
|
+
name: "repo_cache",
|
|
1470
|
+
label: "Repo Cache",
|
|
1453
1471
|
description:
|
|
1454
|
-
"
|
|
1455
|
-
promptSnippet: "Fetch
|
|
1472
|
+
"Fetch, list/query, or evict entries in Lector's external-repo cache. action=fetch shallow-clones an external repository into a disk-bounded cache and registers it as a read-only project -- every other tool (search_code, find_symbols, go_to_definition, ...) then works on it unchanged; explicit owner/repo[@ref] only, no discovery/search (use web_fetch to find candidates first); forceRefresh reclones even when an unexpired cache entry already exists, for a caller that has already positively confirmed the remote moved. action=list queries the cache -- no network call, no mutation -- filtering by any combination of host/owner/repo/ref (exact match) and text (case-insensitive substring), bounded and paginated via cursor; each entry reports whether it's currently a registered workspace or just present on disk. action=evict removes one cache entry from disk by its exact host/owner/repo/ref identity; refuses with a clear error if it is still a currently-registered workspace.",
|
|
1473
|
+
promptSnippet: "Fetch, list, or evict entries in the external-repo cache",
|
|
1456
1474
|
parameters: Type.Object({
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1475
|
+
action: Type.Union([Type.Literal("fetch"), Type.Literal("list"), Type.Literal("evict")]),
|
|
1476
|
+
owner: Type.Optional(Type.String({ description: "Required for action=fetch/evict -- repository owner or organization" })),
|
|
1477
|
+
repo: Type.Optional(Type.String({ description: "Required for action=fetch/evict -- repository name" })),
|
|
1478
|
+
ref: Type.Optional(
|
|
1479
|
+
Type.String({
|
|
1480
|
+
description:
|
|
1481
|
+
"fetch/evict: branch, tag, or commit; defaults to the repository's default branch. list: matches either the requested or the resolved ref",
|
|
1482
|
+
}),
|
|
1483
|
+
),
|
|
1484
|
+
host: Type.Optional(Type.String({ description: "Git host; defaults to github.com for fetch/evict, unfiltered for list" })),
|
|
1485
|
+
forceRefresh: Type.Optional(Type.Boolean({ description: "action=fetch only -- reclone even if an unexpired cache entry already exists" })),
|
|
1486
|
+
text: Type.Optional(Type.String({ description: "action=list only -- case-insensitive substring match across host/owner/repo/refs" })),
|
|
1487
|
+
maxResults: Type.Optional(Type.Number({ description: "Required for action=list -- maximum entries to return in this page" })),
|
|
1488
|
+
cursor: Type.Optional(Type.String({ description: "action=list only -- opaque cursor from a prior call's nextCursor, to fetch the next page" })),
|
|
1461
1489
|
}),
|
|
1462
|
-
async execute(_toolCallId, params) {
|
|
1463
|
-
|
|
1464
|
-
|
|
1490
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<RepoCacheToolDetails>> {
|
|
1491
|
+
if (params.action === "fetch") {
|
|
1492
|
+
if (!params.owner || !params.repo) throw new Error("repo_cache action=fetch requires owner and repo");
|
|
1493
|
+
const result = await repoFetchOperations.fetch(params.host ?? "github.com", params.owner, params.repo, params.ref ?? null, params.forceRefresh);
|
|
1494
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "fetch", result } };
|
|
1495
|
+
}
|
|
1496
|
+
if (params.action === "evict") {
|
|
1497
|
+
if (!params.owner || !params.repo) throw new Error("repo_cache action=evict requires owner and repo");
|
|
1498
|
+
const result = await repoCacheEvictOperations.evict(params.host ?? "github.com", params.owner, params.repo, params.ref ?? null);
|
|
1499
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "evict", result } };
|
|
1500
|
+
}
|
|
1501
|
+
if (params.maxResults === undefined) throw new Error("repo_cache action=list requires maxResults");
|
|
1502
|
+
const page = await repoCacheListOperations.list(
|
|
1503
|
+
{ text: params.text, host: params.host, owner: params.owner, repo: params.repo, ref: params.ref },
|
|
1504
|
+
params.maxResults,
|
|
1505
|
+
params.cursor,
|
|
1506
|
+
);
|
|
1507
|
+
return { content: [{ type: "text", text: JSON.stringify(page) }], details: { action: "list", page } };
|
|
1465
1508
|
},
|
|
1466
1509
|
renderCall(args, theme, context) {
|
|
1510
|
+
const action = args.action === "list" || args.action === "evict" ? args.action : "fetch";
|
|
1467
1511
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1468
|
-
text.setText(
|
|
1512
|
+
text.setText(formatRepoCacheCall(action, args, theme));
|
|
1469
1513
|
return text;
|
|
1470
1514
|
},
|
|
1471
1515
|
renderResult(result, { isPartial }, theme, context) {
|
|
1472
|
-
if (isPartial) return new Text(theme.fg("warning", "
|
|
1516
|
+
if (isPartial) return new Text(theme.fg("warning", "Working on repo cache..."), 0, 0);
|
|
1473
1517
|
if (context.isError) {
|
|
1474
1518
|
const errorText = result.content
|
|
1475
1519
|
.filter((block) => block.type === "text")
|
|
1476
1520
|
.map((block) => block.text)
|
|
1477
1521
|
.join("\n");
|
|
1478
|
-
return new Text(theme.fg("error", errorText || "
|
|
1522
|
+
return new Text(theme.fg("error", errorText || "repo_cache failed"), 0, 0);
|
|
1479
1523
|
}
|
|
1480
|
-
const details = result.details as
|
|
1524
|
+
const details = result.details as RepoCacheToolDetails | undefined;
|
|
1481
1525
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1482
|
-
text.setText(
|
|
1526
|
+
if (details?.action === "list") text.setText(formatRepoCacheListResult(details.page, theme));
|
|
1527
|
+
else if (details?.action === "evict") text.setText(formatRepoCacheEvictResult(details.result, theme));
|
|
1528
|
+
else text.setText(formatRepoFetchResult(details?.action === "fetch" ? details.result : undefined, theme));
|
|
1483
1529
|
return text;
|
|
1484
1530
|
},
|
|
1485
1531
|
});
|
|
1486
1532
|
|
|
1533
|
+
type ExternalSearchToolDetails =
|
|
1534
|
+
| { readonly action: "github_repos"; readonly result: GithubRepoSearchResult }
|
|
1535
|
+
| { readonly action: "npm_packages"; readonly result: { candidates: readonly NpmPackageCandidate[] } }
|
|
1536
|
+
| { readonly action: "sourcegraph_code"; readonly result: { candidates: readonly SourcegraphCodeCandidate[] } };
|
|
1537
|
+
|
|
1538
|
+
const externalSearchOperations = createExternalSearchOperations();
|
|
1487
1539
|
pi.registerTool({
|
|
1488
|
-
name: "
|
|
1489
|
-
label: "
|
|
1540
|
+
name: "external_search",
|
|
1541
|
+
label: "External Search",
|
|
1490
1542
|
description:
|
|
1491
|
-
"
|
|
1492
|
-
promptSnippet: "
|
|
1543
|
+
"Finds real prior art before writing new code -- explicit-query search only, never open-ended discovery/trending. action=github_repos searches GitHub repositories by name/description/topic (GitHub's own relevance ranking); candidates are shaped as direct repo_cache(action=fetch) inputs (host/owner/repo). Unauthenticated is rate-limited to 10 req/min; configure GITHUB_TOKEN on the daemon host for 30/min. action=npm_packages searches the public npm registry; candidates are shaped as direct package_source inputs (name, plus the version already returned). action=sourcegraph_code searches code content across public GitHub via sourcegraph.com -- \"which repos actually contain code matching X\", a genuinely different mode than the other two (which search names/metadata, not file contents); each candidate's repository field feeds repo_cache(action=fetch) once split on '/'. Every source is cached for a short TTL -- an identical query moments apart is served from cache, not re-fetched.",
|
|
1544
|
+
promptSnippet: "Search GitHub repos, npm packages, or code content for prior art",
|
|
1493
1545
|
parameters: Type.Object({
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
maxResults: Type.Number({ description: "Maximum entries to return in this page" }),
|
|
1500
|
-
cursor: Type.Optional(Type.String({ description: "Opaque cursor from a prior call's nextCursor, to fetch the next page" })),
|
|
1546
|
+
action: Type.Union([Type.Literal("github_repos"), Type.Literal("npm_packages"), Type.Literal("sourcegraph_code")]),
|
|
1547
|
+
query: Type.String({
|
|
1548
|
+
description: "The search query -- repo/package name or description keywords for github_repos/npm_packages, code content for sourcegraph_code",
|
|
1549
|
+
}),
|
|
1550
|
+
maxResults: Type.Optional(Type.Number({ description: "Maximum candidates to return (default 20)" })),
|
|
1501
1551
|
}),
|
|
1502
|
-
async execute(_toolCallId, params) {
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
params.maxResults
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1552
|
+
async execute(_toolCallId, params): Promise<AgentToolResult<ExternalSearchToolDetails>> {
|
|
1553
|
+
const maxResults = params.maxResults ?? 20;
|
|
1554
|
+
if (params.action === "github_repos") {
|
|
1555
|
+
const result = await externalSearchOperations.githubRepos(params.query, maxResults);
|
|
1556
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "github_repos", result } };
|
|
1557
|
+
}
|
|
1558
|
+
if (params.action === "npm_packages") {
|
|
1559
|
+
const result = await externalSearchOperations.npmPackages(params.query, maxResults);
|
|
1560
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "npm_packages", result } };
|
|
1561
|
+
}
|
|
1562
|
+
const result = await externalSearchOperations.sourcegraphCode(params.query, maxResults);
|
|
1563
|
+
return { content: [{ type: "text", text: JSON.stringify(result) }], details: { action: "sourcegraph_code", result } };
|
|
1509
1564
|
},
|
|
1510
|
-
renderCall(
|
|
1565
|
+
renderCall(args, theme, context) {
|
|
1566
|
+
const action = args.action === "npm_packages" || args.action === "sourcegraph_code" ? args.action : "github_repos";
|
|
1511
1567
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1512
|
-
text.setText(
|
|
1568
|
+
text.setText(formatExternalSearchCall(action, args, theme));
|
|
1513
1569
|
return text;
|
|
1514
1570
|
},
|
|
1515
1571
|
renderResult(result, { isPartial }, theme, context) {
|
|
1516
|
-
if (isPartial) return new Text(theme.fg("warning", "
|
|
1572
|
+
if (isPartial) return new Text(theme.fg("warning", "Searching..."), 0, 0);
|
|
1517
1573
|
if (context.isError) {
|
|
1518
1574
|
const errorText = result.content
|
|
1519
1575
|
.filter((block) => block.type === "text")
|
|
1520
1576
|
.map((block) => block.text)
|
|
1521
1577
|
.join("\n");
|
|
1522
|
-
return new Text(theme.fg("error", errorText || "
|
|
1578
|
+
return new Text(theme.fg("error", errorText || "external_search failed"), 0, 0);
|
|
1523
1579
|
}
|
|
1524
|
-
const details = result.details as
|
|
1525
|
-
| { page?: { entries: readonly { host: string; owner: string; repo: string }[]; nextCursor: string | null } }
|
|
1526
|
-
| undefined;
|
|
1580
|
+
const details = result.details as ExternalSearchToolDetails | undefined;
|
|
1527
1581
|
const text = context.lastComponent instanceof Text ? context.lastComponent : new Text("", 0, 0);
|
|
1528
|
-
|
|
1529
|
-
|
|
1582
|
+
if (details?.action === "npm_packages") text.setText(formatNpmPackageSearchResult(details.result, theme));
|
|
1583
|
+
else if (details?.action === "sourcegraph_code") text.setText(formatSourcegraphCodeSearchResult(details.result, theme));
|
|
1584
|
+
else text.setText(formatGithubRepoSearchResult(details?.action === "github_repos" ? details.result : undefined, theme));
|
|
1530
1585
|
return text;
|
|
1531
1586
|
},
|
|
1532
1587
|
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { lectorClient } from "./lector-client.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thin wrapper over repo.evictCache -- no `directory`/workspaceForDirectory resolution (matching
|
|
5
|
+
* repo-fetch-operations.ts and repo-cache-list-operations.ts: this targets the daemon-wide fetch
|
|
6
|
+
* cache, not a workspace-scoped concept).
|
|
7
|
+
*/
|
|
8
|
+
export interface RepoCacheEvictOperations {
|
|
9
|
+
evict(host: string, owner: string, repo: string, ref: string | null): Promise<{ evicted: boolean }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createRepoCacheEvictOperations(): RepoCacheEvictOperations {
|
|
13
|
+
return {
|
|
14
|
+
async evict(host, owner, repo, ref) {
|
|
15
|
+
const client = await lectorClient();
|
|
16
|
+
return client.call("repo.evictCache", { host, owner, repo, ref });
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { CachedRepositoryPage, RepoFetchResult } from "@danypops/lector";
|
|
2
|
+
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
+
|
|
4
|
+
type RepoCacheAction = "fetch" | "list" | "evict";
|
|
5
|
+
|
|
6
|
+
export function formatRepoCacheCall(
|
|
7
|
+
action: RepoCacheAction,
|
|
8
|
+
args: { owner?: unknown; repo?: unknown; ref?: unknown; host?: unknown; text?: unknown },
|
|
9
|
+
theme: LectorTheme,
|
|
10
|
+
): string {
|
|
11
|
+
const label = theme.fg("toolTitle", theme.bold("repo_cache"));
|
|
12
|
+
if (action === "list") {
|
|
13
|
+
const filter = typeof args.text === "string" && args.text.length > 0 ? args.text : typeof args.repo === "string" ? args.repo : "";
|
|
14
|
+
return `${label} ${theme.fg("accent", "list")}${filter ? ` ${theme.fg("dim", filter)}` : ""}`;
|
|
15
|
+
}
|
|
16
|
+
const host = typeof args.host === "string" && args.host.length > 0 ? args.host : "github.com";
|
|
17
|
+
const owner = typeof args.owner === "string" ? args.owner : "";
|
|
18
|
+
const repo = typeof args.repo === "string" ? args.repo : "";
|
|
19
|
+
const ref = typeof args.ref === "string" ? `@${args.ref}` : "";
|
|
20
|
+
return `${label} ${theme.fg("accent", action)} ${theme.fg("dim", `${host}/${owner}/${repo}${ref}`)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatRepoFetchResult(result: (RepoFetchResult & { workspaceId: string }) | undefined, theme: LectorTheme): string {
|
|
24
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
25
|
+
const lines = [
|
|
26
|
+
`${theme.fg("accent", result.workspaceId)} ${result.fromCache ? theme.fg("dim", "(from cache)") : theme.fg("toolTitle", "(fetched)")} -- ${result.path}`,
|
|
27
|
+
];
|
|
28
|
+
if (result.refFallbackOccurred) {
|
|
29
|
+
lines.push(theme.fg("warning", `requested ref not found; fell back to the default branch (resolved: ${result.resolvedRef})`));
|
|
30
|
+
}
|
|
31
|
+
return lines.join("\n");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function formatRepoCacheListResult(page: CachedRepositoryPage | undefined, theme: LectorTheme): string {
|
|
35
|
+
const count = page?.entries.length ?? 0;
|
|
36
|
+
return count === 0 ? theme.fg("dim", "no cached repositories") : theme.fg("success", `${count} cached repositor${count === 1 ? "y" : "ies"}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function formatRepoCacheEvictResult(result: { evicted: boolean } | undefined, theme: LectorTheme): string {
|
|
40
|
+
if (!result) return theme.fg("dim", "No result.");
|
|
41
|
+
return result.evicted ? theme.fg("success", "evicted") : theme.fg("dim", "nothing cached for that reference");
|
|
42
|
+
}
|
|
@@ -7,14 +7,14 @@ import { lectorClient } from "./lector-client.ts";
|
|
|
7
7
|
* creates a new registered workspace from a fetched external repo.
|
|
8
8
|
*/
|
|
9
9
|
export interface RepoFetchOperations {
|
|
10
|
-
fetch(host: string, owner: string, repo: string, ref: string | null): Promise<RepoFetchResult & { workspaceId: string }>;
|
|
10
|
+
fetch(host: string, owner: string, repo: string, ref: string | null, forceRefresh?: boolean): Promise<RepoFetchResult & { workspaceId: string }>;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function createLectorRepoFetchOperations(): RepoFetchOperations {
|
|
14
14
|
return {
|
|
15
|
-
async fetch(host, owner, repo, ref) {
|
|
15
|
+
async fetch(host, owner, repo, ref, forceRefresh) {
|
|
16
16
|
const client = await lectorClient();
|
|
17
|
-
return client.call("repo.fetch", { host, owner, repo, ref });
|
|
17
|
+
return client.call("repo.fetch", { host, owner, repo, ref, forceRefresh });
|
|
18
18
|
},
|
|
19
19
|
};
|
|
20
20
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danypops/pi-lector",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@danypops/daemon-kit": "^0.22.1",
|
|
22
|
-
"@danypops/lector": "^0.
|
|
22
|
+
"@danypops/lector": "^0.7.0"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@earendil-works/pi-ai": "^0.81.1",
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { RepoFetchResult } from "@danypops/lector";
|
|
2
|
-
import type { LectorTheme } from "./lector-tui-theme.ts";
|
|
3
|
-
|
|
4
|
-
export function formatRepoFetchCall(args: { owner?: unknown; repo?: unknown; ref?: unknown; host?: unknown }, theme: LectorTheme): string {
|
|
5
|
-
const host = typeof args.host === "string" && args.host.length > 0 ? args.host : "github.com";
|
|
6
|
-
const owner = typeof args.owner === "string" ? args.owner : "";
|
|
7
|
-
const repo = typeof args.repo === "string" ? args.repo : "";
|
|
8
|
-
const ref = typeof args.ref === "string" ? `@${args.ref}` : "";
|
|
9
|
-
return `${theme.fg("toolTitle", theme.bold("repo_fetch"))} ${theme.fg("accent", `${host}/${owner}/${repo}${ref}`)}`;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function formatRepoFetchResult(result: (RepoFetchResult & { workspaceId: string }) | undefined, theme: LectorTheme): string {
|
|
13
|
-
if (!result) return theme.fg("dim", "No result.");
|
|
14
|
-
const lines = [
|
|
15
|
-
`${theme.fg("accent", result.workspaceId)} ${result.fromCache ? theme.fg("dim", "(from cache)") : theme.fg("toolTitle", "(fetched)")} -- ${result.path}`,
|
|
16
|
-
];
|
|
17
|
-
if (result.refFallbackOccurred) {
|
|
18
|
-
lines.push(theme.fg("warning", `requested ref not found; fell back to the default branch (resolved: ${result.resolvedRef})`));
|
|
19
|
-
}
|
|
20
|
-
return lines.join("\n");
|
|
21
|
-
}
|