@shoalrun/cli 0.1.0-alpha.15 → 0.1.0-alpha.17
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/CHANGELOG.md +43 -0
- package/README.md +41 -3
- package/agent-docs/SNAPSHOT.json +5 -5
- package/agent-docs/author/frontmatter-reference.md +15 -11
- package/agent-docs/author/frontmatter-reference.sections/choose-a-publication-and-visibility.md +13 -11
- package/agent-docs/author/frontmatter-reference.sections/control-output-details.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/global-defaults-and-enforced-values.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/identify-and-route-a-page.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/point-agents-to-it.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/project-defined-fields.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/related-reading.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/use-the-local-files-while-authoring.md +1 -1
- package/agent-docs/author/frontmatter-reference.sections/when-the-files-update.md +1 -1
- package/agent-docs/author/recipe-drafts.md +2 -2
- package/agent-docs/author/recipe-drafts.sections/1-write-one-reusable-step.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/2-list-the-steps-for-one-complete-goal.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/3-generate-a-draft-and-review-it.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/4-maintain-shoalrun-s-published-recipe-snapshots.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/related-reading.md +1 -1
- package/agent-docs/author/recipe-drafts.sections/start-an-ingredient-from-an-api-example.md +2 -2
- package/agent-docs/author/recipes.md +3 -3
- package/agent-docs/author/recipes.sections/how-agents-use-this.md +2 -2
- package/agent-docs/author/recipes.sections/one-example-from-shoalrun.md +2 -2
- package/agent-docs/author/recipes.sections/related-reading.md +2 -2
- package/agent-docs/author/recipes.sections/try-a-complete-workflow.md +3 -3
- package/agent-docs/author/recipes.sections/use-recipes-for-task-oriented-how-tos.md +2 -2
- package/agent-docs/author/recipes.sections/what-changing-an-ingredient-updates.md +2 -2
- package/agent-docs/author/recipes.sections/what-each-file-does.md +2 -2
- package/agent-docs/author/recipes.sections/what-happens-and-when.md +2 -2
- package/agent-docs/author/search.md +55 -6
- package/agent-docs/author/search.sections/agent-and-mcp-search-scope.md +48 -0
- package/agent-docs/author/search.sections/choose-a-provider.md +7 -6
- package/agent-docs/author/search.sections/cli-and-mcp-ranking-details.md +1 -1
- package/agent-docs/author/search.sections/enable-included-semantic-search.md +66 -0
- package/agent-docs/author/search.sections/hosted-semantic-search-limits.md +1 -1
- package/agent-docs/author/search.sections/html-markdown-and-agents.md +1 -1
- package/agent-docs/author/search.sections/lexical-search.md +1 -1
- package/agent-docs/author/search.sections/local-semantic-preview.md +1 -1
- package/agent-docs/author/search.sections/read-a-search-result-with-an-agent.md +1 -1
- package/agent-docs/author/search.sections/related-reading.md +1 -1
- package/agent-docs/author/search.sections/reuse-the-lexical-index.md +1 -1
- package/agent-docs/author/search.sections/turn-search-off.md +1 -1
- package/agent-docs/author/search.sections/use-the-search-box.md +1 -1
- package/agent-docs/author/search.sections/write-a-custom-provider.md +1 -1
- package/agent-docs/chat/passage-index.md +16 -12
- package/agent-docs/chat/passage-index.sections/build-the-index.md +3 -3
- package/agent-docs/chat/passage-index.sections/embed-passages.md +3 -3
- package/agent-docs/chat/passage-index.sections/how-the-default-chunker-splits-a-page.md +3 -3
- package/agent-docs/chat/passage-index.sections/limits.md +3 -3
- package/agent-docs/chat/passage-index.sections/passage-ids-and-links.md +14 -10
- package/agent-docs/chat/passage-index.sections/related-reading.md +3 -3
- package/agent-docs/chat/passage-index.sections/what-a-passage-contains.md +5 -5
- package/agent-docs/cookbooks/welcome-email.md +12 -9
- package/agent-docs/documents.json +207 -183
- package/agent-docs/provenance.json +4 -4
- package/agent-docs/recipes/queue-email.md +1 -1
- package/agent-docs/recipes/send-welcome-email.md +3 -3
- package/agent-docs/search-index.json +1 -1
- package/agent-docs/start.md +2 -2
- package/agent-docs/start.sections/add-the-page-to-navigation.md +1 -1
- package/agent-docs/start.sections/build-and-check-both-outputs.md +1 -1
- package/agent-docs/start.sections/build-this-site.md +1 -1
- package/agent-docs/start.sections/check-writing-style.md +1 -1
- package/agent-docs/start.sections/create-a-page.md +1 -1
- package/agent-docs/start.sections/make-it-yours.md +1 -1
- package/agent-docs/start.sections/migrating-from-github-packages.md +1 -1
- package/agent-docs/start.sections/prerequisites.md +1 -1
- package/agent-docs/start.sections/related-reading.md +1 -1
- package/agent-docs/start.sections/start-your-own-project.md +2 -2
- package/assets/search-semantic.js +34 -0
- package/dist/ai-directory.d.ts +1 -0
- package/dist/ai-directory.js +12 -0
- package/dist/answer-cards.js +1 -1
- package/dist/artifact-bundle.d.ts +1 -1
- package/dist/artifact-bundle.js +1 -1
- package/dist/ask-function.d.ts +4 -18
- package/dist/ask-function.js +12 -71
- package/dist/ask-runtime.d.ts +45 -0
- package/dist/ask-runtime.js +96 -0
- package/dist/ask-setup.d.ts +3 -42
- package/dist/ask-setup.js +5 -93
- package/dist/cli.js +6 -2
- package/dist/compiler.js +2 -0
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.js +1 -1
- package/dist/function-package.js +5 -0
- package/dist/function-runtime.d.ts +23 -0
- package/dist/function-runtime.js +45 -0
- package/dist/index-build.d.ts +1 -2
- package/dist/index-build.js +10 -14
- package/dist/publication-render.d.ts +1 -1
- package/dist/retrieval-runtime.d.ts +5 -0
- package/dist/retrieval-runtime.js +21 -0
- package/dist/retrieval.d.ts +4 -0
- package/dist/retrieval.js +28 -8
- package/dist/search-function.d.ts +2 -0
- package/dist/search-function.js +30 -0
- package/dist/search-handler.js +12 -12
- package/dist/search-identifier-core.d.ts +4 -0
- package/dist/search-identifier-core.js +30 -0
- package/dist/search-identifier.d.ts +3 -2
- package/dist/search-identifier.js +7 -24
- package/dist/search-literal.js +1 -1
- package/dist/search-results.d.ts +1 -1
- package/dist/search-results.js +39 -25
- package/dist/search-snippet.d.ts +1 -1
- package/dist/search-snippet.js +2 -14
- package/dist/search-text.d.ts +1 -0
- package/dist/search-text.js +14 -0
- package/dist/vercel-function.js +10 -2
- package/package.json +1 -1
- package/skills/shoalrun-setup/SKILL.md +2 -0
package/dist/search-results.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ import type { Hit, LexicalOptions, Retriever } from './retrieval.js';
|
|
|
2
2
|
type SearchHit = Hit & {
|
|
3
3
|
literalSummary?: string;
|
|
4
4
|
};
|
|
5
|
-
export declare function createSearchResultSelector(retriever: Retriever): (hits: Hit
|
|
5
|
+
export declare function createSearchResultSelector(retriever: Retriever): (hits: Iterable<Hit>, query: string, options: LexicalOptions, rescue?: Hit) => SearchHit[];
|
|
6
6
|
export {};
|
package/dist/search-results.js
CHANGED
|
@@ -1,45 +1,59 @@
|
|
|
1
|
-
import { tokenize } from './retrieval.js';
|
|
1
|
+
import { compareHits, tokenize } from './retrieval.js';
|
|
2
2
|
import { literalSearch } from './search-literal.js';
|
|
3
|
+
import { identifierRescue } from './search-identifier-core.js';
|
|
3
4
|
// Select landing passages for the search box, without changing passage retrieval
|
|
4
5
|
// used by Ask. A gallery's repeated examples must not crowd other pages out.
|
|
5
6
|
export function createSearchResultSelector(retriever) {
|
|
6
7
|
retriever.requirePreparedSearch();
|
|
7
8
|
const normalized = (text) => tokenize(text).join(' ');
|
|
8
|
-
return (hits, query, options) => {
|
|
9
|
+
return (hits, query, options, rescue) => {
|
|
9
10
|
const pages = new Map();
|
|
11
|
+
const normalizedQuery = normalized(query);
|
|
10
12
|
const literal = literalSearch(query);
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (literalPages.has(hit.chunk.docId))
|
|
15
|
-
continue;
|
|
16
|
-
const literalSummary = literal(hit.chunk.search.literalLines);
|
|
17
|
-
if (literalSummary)
|
|
18
|
-
literalPages.set(hit.chunk.docId, { ...hit, literalSummary });
|
|
19
|
-
}
|
|
20
|
-
}
|
|
13
|
+
let exact;
|
|
14
|
+
// Retain at most three candidates per page, independent of its passage
|
|
15
|
+
// count. Input scores need not be sorted or collected into an array.
|
|
21
16
|
for (const hit of hits) {
|
|
22
|
-
|
|
17
|
+
const page = pages.get(hit.chunk.docId) ?? { exactTitle: normalized(hit.chunk.title) === normalizedQuery };
|
|
18
|
+
if (!page.first || compareHits(hit, page.first) < 0)
|
|
19
|
+
page.first = hit;
|
|
20
|
+
if (hit.chunk.search.paragraphs.length && (!page.prose || compareHits(hit, page.prose) < 0))
|
|
21
|
+
page.prose = hit;
|
|
22
|
+
const literalSummary = literal?.(hit.chunk.search.literalLines);
|
|
23
|
+
if (literalSummary && (!page.literal || compareHits(hit, page.literal) < 0))
|
|
24
|
+
page.literal = { ...hit, literalSummary };
|
|
25
|
+
pages.set(hit.chunk.docId, page);
|
|
26
|
+
if (page.exactTitle && (!exact || compareHits(hit, exact) < 0))
|
|
27
|
+
exact = hit;
|
|
28
|
+
}
|
|
29
|
+
const limit = options.limit ?? 8;
|
|
30
|
+
const candidates = [];
|
|
31
|
+
for (const page of pages.values()) {
|
|
32
|
+
const candidate = page.literal ? { rank: page.first, hit: { ...page.literal, score: page.first.score } } : page.prose ? { rank: page.prose, hit: page.prose } : undefined;
|
|
33
|
+
if (!candidate)
|
|
23
34
|
continue;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
const at = candidates.findIndex(other => compareHits(candidate.rank, other.rank) < 0);
|
|
36
|
+
candidates.splice(at < 0 ? candidates.length : at, 0, candidate);
|
|
37
|
+
if (candidates.length > limit)
|
|
38
|
+
candidates.pop();
|
|
39
|
+
}
|
|
40
|
+
let selected = candidates.map(item => item.hit);
|
|
41
|
+
if (rescue) {
|
|
42
|
+
selected = identifierRescue(query, selected, [rescue]);
|
|
43
|
+
const landing = pages.get(rescue.chunk.docId)?.literal;
|
|
44
|
+
if (landing)
|
|
45
|
+
selected = selected.map(hit => hit.chunk.docId === rescue.chunk.docId ? { ...landing, score: hit.score } : hit);
|
|
31
46
|
}
|
|
32
47
|
// An exact page-title query is a request for that topic's entry point.
|
|
33
48
|
// Ordinary semantic queries retain their ranking; incidental words do not qualify.
|
|
34
|
-
const exact = hits.find(hit => normalized(hit.chunk.title) === normalized(query));
|
|
35
49
|
if (exact) {
|
|
36
50
|
const introduction = retriever.pageIntroduction(exact.chunk.docId, options);
|
|
37
|
-
const
|
|
51
|
+
const page = pages.get(exact.chunk.docId);
|
|
52
|
+
const landing = introduction ? { chunk: introduction, score: exact.score } : page?.literal ?? page?.prose;
|
|
38
53
|
if (landing) {
|
|
39
|
-
|
|
40
|
-
return [landing, ...pages.values()].slice(0, options.limit ?? 8);
|
|
54
|
+
return [landing, ...selected.filter(hit => hit.chunk.docId !== exact.chunk.docId)].slice(0, options.limit ?? 8);
|
|
41
55
|
}
|
|
42
56
|
}
|
|
43
|
-
return
|
|
57
|
+
return selected.slice(0, limit);
|
|
44
58
|
};
|
|
45
59
|
}
|
package/dist/search-snippet.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { searchSnippetFromParagraphs } from './search-text.js';
|
|
1
2
|
export declare function searchParagraphs(markdown: string): string[];
|
|
2
3
|
export declare function searchPassageParagraphs(markdown: string): string[];
|
|
3
4
|
export declare function searchLiteralLines(markdown: string): string[];
|
|
@@ -8,4 +9,3 @@ export type PreparedSearchPassage = {
|
|
|
8
9
|
};
|
|
9
10
|
export declare function prepareSearchPassage(markdown: string): PreparedSearchPassage;
|
|
10
11
|
export declare function searchSnippet(markdown: string, query: string, pageSummary?: string): string;
|
|
11
|
-
export declare function searchSnippetFromParagraphs(choices: string[], query: string, pageSummary?: string): string;
|
package/dist/search-snippet.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { searchSnippetFromParagraphs } from './search-text.js';
|
|
2
|
+
export { searchSnippetFromParagraphs } from './search-text.js';
|
|
1
3
|
import { unified } from 'unified';
|
|
2
4
|
import remarkParse from 'remark-parse';
|
|
3
5
|
import remarkGfm from 'remark-gfm';
|
|
@@ -71,23 +73,9 @@ export function prepareSearchPassage(markdown) {
|
|
|
71
73
|
const tree = parser.parse(markdown);
|
|
72
74
|
return { schemaVersion: 1, paragraphs: passageParagraphs(tree), literalLines: cleanLines(literalLines(tree)) };
|
|
73
75
|
}
|
|
74
|
-
const stopWords = new Set('a an the i me my we our you your how do does can could would should will is are was were be to of in on for with please what where when why which and or it this that have has after before'.split(' '));
|
|
75
76
|
// Prefer substantive query matches; Markdown boilerplate and common question words
|
|
76
77
|
// must not hide a useful paragraph. Keep a matching term visible in long paragraphs.
|
|
77
78
|
export function searchSnippet(markdown, query, pageSummary = '') {
|
|
78
79
|
const choices = searchParagraphs(markdown).filter(p => !discovery.test(p));
|
|
79
80
|
return searchSnippetFromParagraphs(choices, query, pageSummary);
|
|
80
81
|
}
|
|
81
|
-
export function searchSnippetFromParagraphs(choices, query, pageSummary = '') {
|
|
82
|
-
const terms = [...new Set((query.toLocaleLowerCase().match(/[\p{L}\p{N}_$.-]+/gu) ?? []).map(t => t.replace(/^[.-]+|[.-]+$/g, '')).filter(t => t && !stopWords.has(t)))];
|
|
83
|
-
const matches = (text) => terms.filter(term => new RegExp(`(?<![\\p{L}\\p{N}_])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\p{L}\\p{N}_])`, 'iu').test(text));
|
|
84
|
-
const ranked = choices.map((text, at) => ({ text, at, terms: matches(text) })).sort((a, b) => b.terms.length - a.terms.length || a.at - b.at);
|
|
85
|
-
const selected = ranked[0];
|
|
86
|
-
const text = (selected?.text ?? pageSummary).replace(/\s+/g, ' ').trim();
|
|
87
|
-
if (text.length <= 180)
|
|
88
|
-
return text;
|
|
89
|
-
const first = selected?.terms.length ? Math.min(...selected.terms.map(t => text.toLocaleLowerCase().indexOf(t))) : 0;
|
|
90
|
-
const start = Math.max(0, first - 50);
|
|
91
|
-
const length = 180 - (start ? 1 : 0) - 1;
|
|
92
|
-
return `${start ? '…' : ''}${text.slice(start, start + length).trim()}…`;
|
|
93
|
-
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function searchSnippetFromParagraphs(choices: string[], query: string, pageSummary?: string): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const stopWords = new Set('a an the i me my we our you your how do does can could would should will is are was were be to of in on for with please what where when why which and or it this that have has after before'.split(' '));
|
|
2
|
+
export function searchSnippetFromParagraphs(choices, query, pageSummary = '') {
|
|
3
|
+
const terms = [...new Set((query.toLocaleLowerCase().match(/[\p{L}\p{N}_$.-]+/gu) ?? []).map(t => t.replace(/^[.-]+|[.-]+$/g, '')).filter(t => t && !stopWords.has(t)))];
|
|
4
|
+
const matches = (text) => terms.filter(term => new RegExp(`(?<![\\p{L}\\p{N}_])${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\p{L}\\p{N}_])`, 'iu').test(text));
|
|
5
|
+
const ranked = choices.map((text, at) => ({ text, at, terms: matches(text) })).sort((a, b) => b.terms.length - a.terms.length || a.at - b.at);
|
|
6
|
+
const selected = ranked[0];
|
|
7
|
+
const text = (selected?.text ?? pageSummary).replace(/\s+/g, ' ').trim();
|
|
8
|
+
if (text.length <= 180)
|
|
9
|
+
return text;
|
|
10
|
+
const first = selected?.terms.length ? Math.min(...selected.terms.map(t => text.toLocaleLowerCase().indexOf(t))) : 0;
|
|
11
|
+
const start = Math.max(0, first - 50);
|
|
12
|
+
const length = 180 - (start ? 1 : 0) - 1;
|
|
13
|
+
return `${start ? '…' : ''}${text.slice(start, start + length).trim()}…`;
|
|
14
|
+
}
|
package/dist/vercel-function.js
CHANGED
|
@@ -100,8 +100,16 @@ export async function addAskFunction(input) {
|
|
|
100
100
|
await writeFile(path.join(func, 'index.js'), `import { createAskFunction, nodeAdapter } from './lib/ask-function.js';\n\nconst directory = new URL('.', import.meta.url).pathname;\nexport default nodeAdapter(() => createAskFunction(directory));\n`);
|
|
101
101
|
// The answer can take about a minute. Hobby allows up to 300 seconds with Fluid compute, so 120 leaves room.
|
|
102
102
|
await writeFile(path.join(func, '.vc-config.json'), JSON.stringify({ runtime: 'nodejs22.x', handler: 'index.js', launcherType: 'Nodejs', shouldAddHelpers: false, maxDuration: 120, memory: 1024 }, null, 2) + '\n');
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// Build Search from its own runtime closure and private retrieval data. It
|
|
104
|
+
// must not inherit chat generation, cards, logging, or compiler dependencies.
|
|
105
|
+
await mkdir(path.join(searchFunc, 'site', 'ai'), { recursive: true });
|
|
106
|
+
for (const name of ['chunks.json', 'embeddings.json', 'embeddings.bin'])
|
|
107
|
+
await cp(path.join(site, 'ai', name), path.join(searchFunc, 'site', 'ai', name));
|
|
108
|
+
const { chat: _chat, ...searchSettings } = runtime.settings;
|
|
109
|
+
await writeFile(path.join(searchFunc, 'runtime.json'), JSON.stringify({ ...runtime, settings: searchSettings, searchStore: chat.budget.store }, null, 2) + '\n');
|
|
110
|
+
await writeFile(path.join(searchFunc, 'package.json'), JSON.stringify({ type: 'module' }) + '\n');
|
|
111
|
+
await copyFunctionRuntime(dist, 'search-function.js', searchFunc);
|
|
112
|
+
await writeFile(path.join(searchFunc, 'index.js'), `import { createHostedSearchFunction } from './lib/search-function.js';\nimport { nodeAdapter } from './lib/function-runtime.js';\n\nconst directory = new URL('.', import.meta.url).pathname;\nexport default nodeAdapter(() => createHostedSearchFunction(directory));\n`);
|
|
105
113
|
await writeFile(path.join(searchFunc, '.vc-config.json'), JSON.stringify({ runtime: 'nodejs22.x', handler: 'index.js', launcherType: 'Nodejs', shouldAddHelpers: false, maxDuration: 30, memory: 1024 }, null, 2) + '\n');
|
|
106
114
|
await readdir(func);
|
|
107
115
|
return { added: true, directory: func, chunks: built.index.chunks.length, embedded: true, embeddingCounts: { embedded: embedded.embedded, reused: embedded.reused } };
|
package/package.json
CHANGED
|
@@ -33,4 +33,6 @@ For a new OpenAPI source, read `guide.api-references` and complete its API-sourc
|
|
|
33
33
|
|
|
34
34
|
Check the generated HTML, Markdown twins, and `documents.json`. Confirm navigation and API links resolve, and inspect assets inside the components that contain them. Run `shoalrun audit docs.config.yaml` when project checks or API audits are configured; documentation behavior tests can use Doc Detective through `audit.tests` or another external CLI.
|
|
35
35
|
|
|
36
|
+
For optional semantic search, use `outputs.html.search.provider: semantic` (available since `0.1.0-alpha.16`) with `retrieval.embeddings`; do not generate a custom browser adapter for Shoalrun's own endpoint. Build the site, run `shoalrun index <output> --embed --config docs.config.yaml`, then `shoalrun serve <output> --search --config docs.config.yaml` with the provider key only in the environment. Confirm the endpoint returns `mode: vector`; a visible result or keyword fallback does not prove semantic retrieval. Selecting the browser provider does not provision production hosting, and profiled Vercel chat-function builds remain unsupported.
|
|
37
|
+
|
|
36
38
|
Report the files created, commands run, and anything still needing an owner decision. Do not deploy, publish a release, or claim factual accuracy from a successful build alone.
|