@gmickel/gno 1.34.6 → 1.36.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/README.md +12 -1
- package/assets/skill/SKILL.md +26 -3
- package/assets/skill/cli-reference.md +9 -0
- package/assets/skill/mcp-reference.md +3 -2
- package/browser-extension/artifacts/{gno-browser-clipper-v1.34.6.zip → gno-browser-clipper-v1.36.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.36.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +111 -1
- package/spec/mcp.md +60 -1
- package/spec/output-schemas/mcp-job-status.schema.json +6 -2
- package/spec/output-schemas/peek.schema.json +212 -0
- package/spec/output-schemas/search-results.schema.json +1 -1
- package/src/cli/commands/peek.ts +66 -0
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +20 -0
- package/src/config/index.ts +4 -0
- package/src/config/types.ts +14 -0
- package/src/core/path-rules.ts +34 -0
- package/src/core/peek.ts +202 -0
- package/src/ingestion/index.ts +21 -0
- package/src/ingestion/record-container.ts +23 -1
- package/src/ingestion/source-availability/darwin-io.ts +295 -0
- package/src/ingestion/source-availability/darwin-path.ts +58 -0
- package/src/ingestion/source-availability/directory.ts +402 -0
- package/src/ingestion/source-availability/index.ts +74 -0
- package/src/ingestion/source-availability/readers.ts +360 -0
- package/src/ingestion/source-availability/resolve.ts +28 -0
- package/src/ingestion/source-availability/types.ts +170 -0
- package/src/ingestion/sync.ts +197 -24
- package/src/ingestion/types.ts +45 -3
- package/src/ingestion/walker.ts +263 -5
- package/src/mcp/http-egress.ts +1 -0
- package/src/mcp/tools/index.ts +14 -0
- package/src/mcp/tools/peek.ts +78 -0
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/watch-reconciliation-fallback-disk.ts +239 -100
- package/src/serve/watch-reconciliation-fallback.ts +35 -5
- package/src/serve/watch-reconciliation-shared.ts +8 -3
- package/src/serve/watch-reconciliation.ts +7 -0
- package/src/serve/watch-service-flush.ts +10 -0
- package/src/serve/watch-service-lifecycle.ts +2 -0
- package/src/serve/watch-service-snapshot.ts +27 -3
- package/src/serve/watch-service.ts +1 -0
- package/src/serve/watch-snapshot-availability.ts +51 -0
- package/src/serve/watch-snapshot-handles.ts +117 -37
- package/src/serve/watch-snapshot-libc.ts +141 -22
- package/src/serve/watch-snapshot-ops.ts +151 -9
- package/src/serve/watch-snapshot-scan.ts +3 -0
- package/src/serve/watch-snapshot-types.ts +45 -3
- package/browser-extension/artifacts/gno-browser-clipper-v1.34.6.zip.sha256 +0 -1
package/src/ingestion/walker.ts
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File walker implementation.
|
|
3
|
-
* Walks collection directories using Bun.Glob
|
|
3
|
+
* Walks collection directories using Bun.Glob (`any`) or hierarchical
|
|
4
|
+
* availability-aware descent (`local`).
|
|
4
5
|
*
|
|
5
6
|
* @module src/ingestion/walker
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
// node:fs
|
|
9
|
-
import {
|
|
9
|
+
// node:fs - Bun has no synchronous Dirent enumeration for the guarded local walk.
|
|
10
|
+
import { readdirSync } from "node:fs";
|
|
11
|
+
// node:fs/promises - Bun has no realpath equivalent for symlink-safe containment.
|
|
12
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
10
13
|
// node:path - Bun has no path manipulation module
|
|
11
14
|
import {
|
|
12
15
|
extname,
|
|
13
16
|
isAbsolute,
|
|
17
|
+
join,
|
|
14
18
|
normalize as normalizePath,
|
|
15
19
|
relative,
|
|
16
20
|
resolve,
|
|
@@ -20,8 +24,16 @@ import {
|
|
|
20
24
|
import type { SkippedEntry, WalkConfig, WalkEntry, WalkerPort } from "./types";
|
|
21
25
|
|
|
22
26
|
import { SUPPORTED_EXTENSIONS } from "../converters/mime";
|
|
23
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
matchesCollectionExclusion,
|
|
29
|
+
matchesCollectionSubtreeExclusion,
|
|
30
|
+
} from "../core/path-rules";
|
|
24
31
|
import { isRecordVirtualPath } from "./record-path";
|
|
32
|
+
import {
|
|
33
|
+
createDirectoryAvailability,
|
|
34
|
+
type DirectoryAvailabilityPort,
|
|
35
|
+
isUnprovenDirectoryResult,
|
|
36
|
+
} from "./source-availability";
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
39
|
* Regex to detect dangerous patterns with parent directory traversal.
|
|
@@ -218,8 +230,27 @@ export function matchesWalkPath(
|
|
|
218
230
|
);
|
|
219
231
|
}
|
|
220
232
|
|
|
233
|
+
function pushUnprovenDirectorySkip(
|
|
234
|
+
skipped: SkippedEntry[],
|
|
235
|
+
absPath: string,
|
|
236
|
+
relPath: string,
|
|
237
|
+
result: Exclude<
|
|
238
|
+
Awaited<ReturnType<DirectoryAvailabilityPort["classify"]>>,
|
|
239
|
+
{ kind: "available" }
|
|
240
|
+
>
|
|
241
|
+
): void {
|
|
242
|
+
skipped.push({
|
|
243
|
+
absPath,
|
|
244
|
+
relPath,
|
|
245
|
+
reason: result.code,
|
|
246
|
+
unprovenPrefix: true,
|
|
247
|
+
message: result.message,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
|
|
221
251
|
/**
|
|
222
|
-
* File walker implementation using Bun.Glob
|
|
252
|
+
* File walker implementation using Bun.Glob (`any`) or hierarchical local-mode
|
|
253
|
+
* descent that refuses dataless / availability-unknown directories.
|
|
223
254
|
*
|
|
224
255
|
* Security: Validates patterns and ensures all matched files are within
|
|
225
256
|
* the collection root directory. Files outside root are silently ignored.
|
|
@@ -228,6 +259,18 @@ export class FileWalker implements WalkerPort {
|
|
|
228
259
|
async walk(config: WalkConfig): Promise<{
|
|
229
260
|
entries: WalkEntry[];
|
|
230
261
|
skipped: SkippedEntry[];
|
|
262
|
+
}> {
|
|
263
|
+
const mode = config.sourceAvailability ?? "any";
|
|
264
|
+
if (mode === "local") {
|
|
265
|
+
return this.walkLocal(config);
|
|
266
|
+
}
|
|
267
|
+
return this.walkAny(config);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Legacy Bun.Glob traversal — behaviorally unchanged for `any`. */
|
|
271
|
+
private async walkAny(config: WalkConfig): Promise<{
|
|
272
|
+
entries: WalkEntry[];
|
|
273
|
+
skipped: SkippedEntry[];
|
|
231
274
|
}> {
|
|
232
275
|
const entries: WalkEntry[] = [];
|
|
233
276
|
const skipped: SkippedEntry[] = [];
|
|
@@ -316,6 +359,221 @@ export class FileWalker implements WalkerPort {
|
|
|
316
359
|
|
|
317
360
|
return { entries, skipped };
|
|
318
361
|
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Local-mode hierarchical walk: classify each directory before descent.
|
|
365
|
+
* Does not add a per-file availability syscall — content recheck stays at
|
|
366
|
+
* the SourceContentReaderPort boundary.
|
|
367
|
+
*/
|
|
368
|
+
private async walkLocal(config: WalkConfig): Promise<{
|
|
369
|
+
entries: WalkEntry[];
|
|
370
|
+
skipped: SkippedEntry[];
|
|
371
|
+
}> {
|
|
372
|
+
const entries: WalkEntry[] = [];
|
|
373
|
+
const skipped: SkippedEntry[] = [];
|
|
374
|
+
|
|
375
|
+
for (const pattern of scanPatterns(config.pattern)) {
|
|
376
|
+
const patternError = validatePattern(pattern);
|
|
377
|
+
if (patternError) {
|
|
378
|
+
throw new Error(`Invalid glob pattern: ${patternError}`);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rootAbs = resolve(config.root);
|
|
383
|
+
const classifier =
|
|
384
|
+
config.directoryAvailability ??
|
|
385
|
+
createDirectoryAvailability(config.sourceAvailability ?? "local");
|
|
386
|
+
const configuredRootClassified = await classifier.classify(rootAbs);
|
|
387
|
+
if (isUnprovenDirectoryResult(configuredRootClassified)) {
|
|
388
|
+
pushUnprovenDirectorySkip(skipped, rootAbs, "", configuredRootClassified);
|
|
389
|
+
return { entries, skipped };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let rootReal: string;
|
|
393
|
+
try {
|
|
394
|
+
rootReal = await realpath(rootAbs);
|
|
395
|
+
} catch {
|
|
396
|
+
skipped.push({
|
|
397
|
+
absPath: rootAbs,
|
|
398
|
+
relPath: "",
|
|
399
|
+
reason: "SOURCE_AVAILABILITY_UNKNOWN",
|
|
400
|
+
unprovenPrefix: true,
|
|
401
|
+
message:
|
|
402
|
+
"Collection root could not be resolved after availability check",
|
|
403
|
+
});
|
|
404
|
+
return { entries, skipped };
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (rootReal !== rootAbs) {
|
|
408
|
+
const canonicalRootClassified = await classifier.classify(rootReal);
|
|
409
|
+
if (isUnprovenDirectoryResult(canonicalRootClassified)) {
|
|
410
|
+
pushUnprovenDirectorySkip(
|
|
411
|
+
skipped,
|
|
412
|
+
rootReal,
|
|
413
|
+
"",
|
|
414
|
+
canonicalRootClassified
|
|
415
|
+
);
|
|
416
|
+
return { entries, skipped };
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const queue: Array<{ absPath: string; relPath: string }> = [
|
|
421
|
+
{ absPath: rootReal, relPath: "" },
|
|
422
|
+
];
|
|
423
|
+
let head = 0;
|
|
424
|
+
|
|
425
|
+
while (head < queue.length) {
|
|
426
|
+
const dir = queue[head] as { absPath: string; relPath: string };
|
|
427
|
+
head += 1;
|
|
428
|
+
|
|
429
|
+
let dirents;
|
|
430
|
+
try {
|
|
431
|
+
const read = classifier.readDirectory(dir.absPath, () =>
|
|
432
|
+
readdirSync(dir.absPath, { withFileTypes: true })
|
|
433
|
+
);
|
|
434
|
+
if (read.kind !== "available") {
|
|
435
|
+
pushUnprovenDirectorySkip(skipped, dir.absPath, dir.relPath, read);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
dirents = read.value;
|
|
439
|
+
} catch {
|
|
440
|
+
skipped.push({
|
|
441
|
+
absPath: dir.absPath,
|
|
442
|
+
relPath: dir.relPath,
|
|
443
|
+
reason: "SOURCE_AVAILABILITY_UNKNOWN",
|
|
444
|
+
unprovenPrefix: true,
|
|
445
|
+
message: "Failed to enumerate directory after availability check",
|
|
446
|
+
});
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
dirents.sort((left, right) => left.name.localeCompare(right.name));
|
|
451
|
+
for (const dirent of dirents) {
|
|
452
|
+
const name = dirent.name;
|
|
453
|
+
if (name === "" || name === "." || name === "..") {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (name.includes("\0")) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const childAbs = join(dir.absPath, name);
|
|
461
|
+
const childRel =
|
|
462
|
+
dir.relPath === "" ? name : `${dir.relPath}/${toPosixPath(name)}`;
|
|
463
|
+
|
|
464
|
+
// No-follow: symlinks are leaf candidates so the guarded content open
|
|
465
|
+
// can refuse them with a receipt; never descend through them.
|
|
466
|
+
if (dirent.isSymbolicLink()) {
|
|
467
|
+
if (!matchesWalkPath(childRel, config)) {
|
|
468
|
+
skipped.push({
|
|
469
|
+
absPath: childAbs,
|
|
470
|
+
relPath: childRel,
|
|
471
|
+
reason: "EXCLUDED",
|
|
472
|
+
});
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
try {
|
|
476
|
+
const linkStat = await lstat(childAbs);
|
|
477
|
+
entries.push({
|
|
478
|
+
absPath: childAbs,
|
|
479
|
+
relPath: childRel,
|
|
480
|
+
size: linkStat.size,
|
|
481
|
+
mtime: linkStat.mtime.toISOString(),
|
|
482
|
+
ctime: (
|
|
483
|
+
linkStat.birthtime ??
|
|
484
|
+
linkStat.ctime ??
|
|
485
|
+
linkStat.mtime
|
|
486
|
+
).toISOString(),
|
|
487
|
+
});
|
|
488
|
+
} catch {
|
|
489
|
+
skipped.push({
|
|
490
|
+
absPath: childAbs,
|
|
491
|
+
relPath: childRel,
|
|
492
|
+
reason: "SOURCE_AVAILABILITY_UNKNOWN",
|
|
493
|
+
unprovenPrefix: true,
|
|
494
|
+
message: "Symlink metadata changed during local traversal",
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (dirent.isDirectory()) {
|
|
501
|
+
if (matchesCollectionSubtreeExclusion(childRel, config.exclude)) {
|
|
502
|
+
skipped.push({
|
|
503
|
+
absPath: childAbs,
|
|
504
|
+
relPath: childRel,
|
|
505
|
+
reason: "EXCLUDED",
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const classified = await classifier.classify(childAbs);
|
|
510
|
+
if (isUnprovenDirectoryResult(classified)) {
|
|
511
|
+
pushUnprovenDirectorySkip(skipped, childAbs, childRel, classified);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
queue.push({ absPath: childAbs, relPath: childRel });
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (!dirent.isFile()) {
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const safePath = await safeRelPath(rootReal, childAbs);
|
|
523
|
+
if (safePath === null) {
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
const { absPath, relPath } = safePath;
|
|
527
|
+
|
|
528
|
+
if (!matchesWalkPath(relPath, config)) {
|
|
529
|
+
skipped.push({
|
|
530
|
+
absPath,
|
|
531
|
+
relPath,
|
|
532
|
+
reason: "EXCLUDED",
|
|
533
|
+
});
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const file = Bun.file(absPath);
|
|
538
|
+
let fileStat: {
|
|
539
|
+
size: number;
|
|
540
|
+
mtime: Date;
|
|
541
|
+
ctime?: Date;
|
|
542
|
+
birthtime?: Date;
|
|
543
|
+
};
|
|
544
|
+
try {
|
|
545
|
+
fileStat = await file.stat();
|
|
546
|
+
} catch {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
if (fileStat.size > config.maxBytes) {
|
|
551
|
+
skipped.push({
|
|
552
|
+
absPath,
|
|
553
|
+
relPath,
|
|
554
|
+
reason: "TOO_LARGE",
|
|
555
|
+
size: fileStat.size,
|
|
556
|
+
});
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
entries.push({
|
|
561
|
+
absPath,
|
|
562
|
+
relPath,
|
|
563
|
+
size: fileStat.size,
|
|
564
|
+
mtime: fileStat.mtime.toISOString(),
|
|
565
|
+
ctime: (
|
|
566
|
+
fileStat.birthtime ??
|
|
567
|
+
fileStat.ctime ??
|
|
568
|
+
fileStat.mtime
|
|
569
|
+
).toISOString(),
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
entries.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
575
|
+
return { entries, skipped };
|
|
576
|
+
}
|
|
319
577
|
}
|
|
320
578
|
|
|
321
579
|
/**
|
package/src/mcp/http-egress.ts
CHANGED
package/src/mcp/tools/index.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
import { handleListJobs } from "./list-jobs";
|
|
66
66
|
import { handleListTags } from "./list-tags";
|
|
67
67
|
import { handleMultiGet } from "./multi-get";
|
|
68
|
+
import { handlePeek, PEEK_MCP_ANNOTATIONS } from "./peek";
|
|
68
69
|
import { handleQuery, handleQueryDiagnose } from "./query";
|
|
69
70
|
import { handleRemoveCollection } from "./remove-collection";
|
|
70
71
|
import { handleSearch } from "./search";
|
|
@@ -129,6 +130,7 @@ export const MCP_TOOL_DESCRIPTIONS = {
|
|
|
129
130
|
"Retrieve multiple documents by refs array or glob pattern. Use after gno_search/gno_query to batch top result URIs/docids; set maxBytes and lineNumbers to control context size.",
|
|
130
131
|
section:
|
|
131
132
|
"Create or resolve a durable SectionTargetV1 against one indexed document. action=create needs ref plus exactly one of anchor|line; action=resolve needs ref plus target. Exact/recovered include citation (uri, anchor, title, inclusive lines, fingerprint); ambiguous/stale/missing omit citation and are not safe to navigate or cite. Read-only — does not write or persist targets. Follow navigable ranges with gno_get fromLine/lineCount.",
|
|
133
|
+
peek: "Cheap peek@1.0 snapshot: initialized flag, document/collection counts, embedding backlog, recent files, and serve liveness. Model-free — never initializes embeddings or models. Use for counts/backlog/recent/serve questions; use gno_status for full health and activation.",
|
|
132
134
|
status:
|
|
133
135
|
"Get index health: collection count, document count, chunk count, embedding backlog, and per-collection stats. Check first when vector/hybrid results look stale or unavailable.",
|
|
134
136
|
audit:
|
|
@@ -664,6 +666,8 @@ const multiGetInputSchema = z.object({
|
|
|
664
666
|
.describe("Include line numbers in output"),
|
|
665
667
|
});
|
|
666
668
|
|
|
669
|
+
const peekInputSchema = z.object({});
|
|
670
|
+
|
|
667
671
|
const statusInputSchema = z.object({});
|
|
668
672
|
|
|
669
673
|
const jobStatusInputSchema = z.object({
|
|
@@ -1086,6 +1090,16 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
|
|
|
1086
1090
|
(args) => handleMultiGet(args, ctx)
|
|
1087
1091
|
);
|
|
1088
1092
|
|
|
1093
|
+
server.registerTool(
|
|
1094
|
+
"gno_peek",
|
|
1095
|
+
{
|
|
1096
|
+
description: MCP_TOOL_DESCRIPTIONS.peek,
|
|
1097
|
+
inputSchema: peekInputSchema,
|
|
1098
|
+
annotations: PEEK_MCP_ANNOTATIONS,
|
|
1099
|
+
},
|
|
1100
|
+
(args) => handlePeek(args, ctx)
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1089
1103
|
server.tool(
|
|
1090
1104
|
"gno_status",
|
|
1091
1105
|
MCP_TOOL_DESCRIPTIONS.status,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP gno_peek tool — cheap peek@1.0 snapshot, model-free.
|
|
3
|
+
*
|
|
4
|
+
* @module src/mcp/tools/peek
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PeekSnapshot } from "../../core/peek";
|
|
8
|
+
import type { ToolContext } from "../server";
|
|
9
|
+
|
|
10
|
+
import { buildPeekSnapshot } from "../../core/peek";
|
|
11
|
+
import { runTool, type ToolResult } from "./index";
|
|
12
|
+
|
|
13
|
+
type PeekInput = Record<string, never>;
|
|
14
|
+
|
|
15
|
+
export const PEEK_MCP_ANNOTATIONS = {
|
|
16
|
+
readOnlyHint: true,
|
|
17
|
+
destructiveHint: false,
|
|
18
|
+
idempotentHint: true,
|
|
19
|
+
openWorldHint: false,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
function formatPeek(snapshot: PeekSnapshot): string {
|
|
23
|
+
const lines = [
|
|
24
|
+
`schema: ${snapshot.schemaVersion}`,
|
|
25
|
+
`gno: ${snapshot.gnoVersion}`,
|
|
26
|
+
`index: ${snapshot.indexName}`,
|
|
27
|
+
`initialized: ${snapshot.initialized ? "yes" : "no"}`,
|
|
28
|
+
];
|
|
29
|
+
if (snapshot.counts) {
|
|
30
|
+
lines.push(
|
|
31
|
+
`documents: ${snapshot.counts.documents}`,
|
|
32
|
+
`collections: ${snapshot.counts.collections}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (snapshot.backlog) {
|
|
36
|
+
lines.push(
|
|
37
|
+
`backlog: ${snapshot.backlog.pending} pending, ${snapshot.backlog.failed} failed`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (snapshot.lastIndexedAt) {
|
|
41
|
+
lines.push(`lastIndexedAt: ${snapshot.lastIndexedAt}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push(
|
|
44
|
+
snapshot.serve.running && snapshot.serve.url
|
|
45
|
+
? `serve: ${snapshot.serve.url}`
|
|
46
|
+
: "serve: down"
|
|
47
|
+
);
|
|
48
|
+
if (snapshot.recent.length > 0) {
|
|
49
|
+
lines.push("recent:");
|
|
50
|
+
for (const item of snapshot.recent) {
|
|
51
|
+
const label = item.title ?? item.uri;
|
|
52
|
+
lines.push(` ${item.docid} ${label}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle gno_peek tool call.
|
|
60
|
+
*
|
|
61
|
+
* Uninitialized (no config / no store) is a success payload, not an error.
|
|
62
|
+
* Never probes serve over HTTP; the shared builder uses pid-file liveness.
|
|
63
|
+
*/
|
|
64
|
+
export function handlePeek(
|
|
65
|
+
_args: PeekInput,
|
|
66
|
+
ctx: ToolContext
|
|
67
|
+
): Promise<ToolResult> {
|
|
68
|
+
return runTool(
|
|
69
|
+
ctx,
|
|
70
|
+
"gno_peek",
|
|
71
|
+
async () =>
|
|
72
|
+
buildPeekSnapshot({
|
|
73
|
+
configPath: ctx.actualConfigPath,
|
|
74
|
+
indexName: ctx.indexName,
|
|
75
|
+
}),
|
|
76
|
+
formatPeek
|
|
77
|
+
);
|
|
78
|
+
}
|