@gmickel/gno 1.7.1 → 1.9.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 +10 -1
- package/assets/skill/SKILL.md +41 -0
- package/assets/skill/cli-reference.md +34 -0
- package/assets/skill/examples.md +37 -0
- package/assets/skill/mcp-reference.md +21 -0
- package/package.json +1 -1
- package/src/cli/commands/capture.ts +282 -0
- package/src/cli/commands/index-cmd.ts +17 -6
- package/src/cli/commands/shared.ts +17 -1
- package/src/cli/commands/update.ts +17 -6
- package/src/cli/options.ts +2 -0
- package/src/cli/program.ts +64 -0
- package/src/config/content-types.ts +140 -0
- package/src/config/defaults.ts +1 -0
- package/src/config/index.ts +14 -0
- package/src/config/loader.ts +11 -2
- package/src/config/types.ts +37 -1
- package/src/core/capture-write.ts +38 -0
- package/src/core/capture.ts +746 -0
- package/src/core/config-mutation.ts +14 -2
- package/src/core/file-ops.ts +21 -2
- package/src/core/note-presets.ts +61 -5
- package/src/ingestion/frontmatter.ts +77 -2
- package/src/ingestion/index.ts +2 -0
- package/src/ingestion/sync-options.ts +29 -0
- package/src/ingestion/sync.ts +104 -16
- package/src/ingestion/types.ts +14 -0
- package/src/mcp/tools/add-collection.ts +8 -5
- package/src/mcp/tools/capture.ts +137 -191
- package/src/mcp/tools/index-cmd.ts +13 -7
- package/src/mcp/tools/index.ts +43 -9
- package/src/mcp/tools/sync.ts +12 -6
- package/src/mcp/tools/workspace-write.ts +16 -10
- package/src/pipeline/hybrid.ts +2 -0
- package/src/pipeline/search.ts +2 -0
- package/src/pipeline/types.ts +2 -0
- package/src/pipeline/vsearch.ts +4 -0
- package/src/sdk/client.ts +156 -20
- package/src/sdk/index.ts +2 -0
- package/src/sdk/types.ts +6 -0
- package/src/serve/background-runtime.ts +18 -4
- package/src/serve/config-sync.ts +9 -2
- package/src/serve/public/components/CaptureModal.tsx +248 -10
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/routes/api.ts +238 -26
- package/src/serve/server.ts +12 -0
- package/src/serve/watch-service.ts +12 -2
- package/src/store/migrations/009-content-type-rule-fingerprint.ts +36 -0
- package/src/store/migrations/index.ts +12 -1
- package/src/store/sqlite/adapter.ts +29 -14
- package/src/store/types.ts +6 -0
package/src/pipeline/vsearch.ts
CHANGED
|
@@ -227,6 +227,8 @@ export async function searchVectorWithEmbedding(
|
|
|
227
227
|
score,
|
|
228
228
|
uri: doc.uri,
|
|
229
229
|
title: doc.title ?? undefined,
|
|
230
|
+
contentType: doc.contentType ?? undefined,
|
|
231
|
+
categories: doc.categories ?? undefined,
|
|
230
232
|
line: chunk.startLine,
|
|
231
233
|
snippet: chunk.text,
|
|
232
234
|
snippetLanguage: chunk.language ?? undefined,
|
|
@@ -281,6 +283,8 @@ export async function searchVectorWithEmbedding(
|
|
|
281
283
|
score,
|
|
282
284
|
uri: doc.uri,
|
|
283
285
|
title: doc.title ?? undefined,
|
|
286
|
+
contentType: doc.contentType ?? undefined,
|
|
287
|
+
categories: doc.categories ?? undefined,
|
|
284
288
|
line: chunk.startLine,
|
|
285
289
|
snippet: fullContent ?? chunk.text,
|
|
286
290
|
snippetLanguage: chunk.language ?? undefined,
|
package/src/sdk/client.ts
CHANGED
|
@@ -15,6 +15,8 @@ import type { IndexStatus, StoreResult } from "../store/types";
|
|
|
15
15
|
import type { VectorIndexPort } from "../store/vector";
|
|
16
16
|
import type {
|
|
17
17
|
GnoAskOptions,
|
|
18
|
+
GnoCaptureOptions,
|
|
19
|
+
GnoCaptureResult,
|
|
18
20
|
GnoClient,
|
|
19
21
|
GnoCreateFolderOptions,
|
|
20
22
|
GnoCreateFolderResult,
|
|
@@ -42,7 +44,18 @@ import {
|
|
|
42
44
|
getIndexDbPath,
|
|
43
45
|
parseUri,
|
|
44
46
|
} from "../app/constants";
|
|
45
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
ConfigSchema,
|
|
49
|
+
loadConfig,
|
|
50
|
+
normalizeConfigContentTypes,
|
|
51
|
+
} from "../config";
|
|
52
|
+
import {
|
|
53
|
+
buildCaptureReceipt,
|
|
54
|
+
type CapturePlan,
|
|
55
|
+
listCaptureDiskRelPaths,
|
|
56
|
+
planCapture,
|
|
57
|
+
} from "../core/capture";
|
|
58
|
+
import { writeCapturePlanFile } from "../core/capture-write";
|
|
46
59
|
import {
|
|
47
60
|
atomicWrite,
|
|
48
61
|
copyFilePath,
|
|
@@ -61,7 +74,11 @@ import { resolveNotePreset } from "../core/note-presets";
|
|
|
61
74
|
import { extractSections } from "../core/sections";
|
|
62
75
|
import { normalizeStructuredQueryInput } from "../core/structured-query";
|
|
63
76
|
import { parseAndValidateTagFilter } from "../core/tags";
|
|
64
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
defaultSyncService,
|
|
79
|
+
type SyncResult,
|
|
80
|
+
withContentTypeRules,
|
|
81
|
+
} from "../ingestion";
|
|
65
82
|
import { updateFrontmatterTags } from "../ingestion/frontmatter";
|
|
66
83
|
import { LlmAdapter } from "../llm/nodeLlamaCpp/adapter";
|
|
67
84
|
import { resolveDownloadPolicy } from "../llm/policy";
|
|
@@ -132,7 +149,7 @@ async function resolveClientState(
|
|
|
132
149
|
parsed.error.issues[0]?.message ?? "Invalid config"
|
|
133
150
|
);
|
|
134
151
|
}
|
|
135
|
-
config = parsed.data;
|
|
152
|
+
config = normalizeConfigContentTypes(parsed.data).config;
|
|
136
153
|
configPath = null;
|
|
137
154
|
configSource = "inline";
|
|
138
155
|
} else {
|
|
@@ -686,10 +703,17 @@ class GnoClientImpl implements GnoClient {
|
|
|
686
703
|
async update(options: GnoUpdateOptions = {}): Promise<SyncResult> {
|
|
687
704
|
this.assertOpen();
|
|
688
705
|
const collections = this.getCollections(options.collection);
|
|
689
|
-
return defaultSyncService.syncAll(
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
706
|
+
return defaultSyncService.syncAll(
|
|
707
|
+
collections,
|
|
708
|
+
this.store,
|
|
709
|
+
withContentTypeRules(
|
|
710
|
+
{
|
|
711
|
+
gitPull: options.gitPull,
|
|
712
|
+
runUpdateCmd: true,
|
|
713
|
+
},
|
|
714
|
+
this.config
|
|
715
|
+
)
|
|
716
|
+
);
|
|
693
717
|
}
|
|
694
718
|
|
|
695
719
|
async embed(options: GnoEmbedOptions = {}): Promise<GnoEmbedResult> {
|
|
@@ -799,10 +823,13 @@ class GnoClientImpl implements GnoClient {
|
|
|
799
823
|
collection,
|
|
800
824
|
this.store,
|
|
801
825
|
[plan.relPath],
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
826
|
+
withContentTypeRules(
|
|
827
|
+
{
|
|
828
|
+
runUpdateCmd: false,
|
|
829
|
+
gitPull: false,
|
|
830
|
+
},
|
|
831
|
+
this.config
|
|
832
|
+
)
|
|
806
833
|
);
|
|
807
834
|
const syncResult = syncResults[0];
|
|
808
835
|
if (!syncResult || syncResult.status === "error") {
|
|
@@ -822,6 +849,109 @@ class GnoClientImpl implements GnoClient {
|
|
|
822
849
|
};
|
|
823
850
|
}
|
|
824
851
|
|
|
852
|
+
async capture(options: GnoCaptureOptions): Promise<GnoCaptureResult> {
|
|
853
|
+
this.assertOpen();
|
|
854
|
+
const collection = this.getCollections(options.collection)[0];
|
|
855
|
+
if (!collection) {
|
|
856
|
+
throw sdkError(
|
|
857
|
+
"VALIDATION",
|
|
858
|
+
`Collection not found: ${options.collection}`
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const existingList = await this.store.listDocuments(collection.name);
|
|
863
|
+
if (!existingList.ok) {
|
|
864
|
+
throw sdkError("STORE", existingList.error.message, {
|
|
865
|
+
cause: existingList.error.cause,
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
const { overwrite: _unsupportedOverwrite, ...captureOptions } =
|
|
869
|
+
options as GnoCaptureOptions & { overwrite?: unknown };
|
|
870
|
+
if (_unsupportedOverwrite !== undefined) {
|
|
871
|
+
throw sdkError(
|
|
872
|
+
"VALIDATION",
|
|
873
|
+
"overwrite is not supported by client.capture(); use collisionPolicy instead"
|
|
874
|
+
);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let plan: CapturePlan;
|
|
878
|
+
try {
|
|
879
|
+
plan = planCapture({
|
|
880
|
+
input: {
|
|
881
|
+
...captureOptions,
|
|
882
|
+
collection: collection.name,
|
|
883
|
+
},
|
|
884
|
+
existingRelPaths: existingList.value.map((doc) => doc.relPath),
|
|
885
|
+
diskRelPaths: await listCaptureDiskRelPaths(collection.path),
|
|
886
|
+
});
|
|
887
|
+
} catch (error) {
|
|
888
|
+
throw sdkError(
|
|
889
|
+
"VALIDATION",
|
|
890
|
+
error instanceof Error ? error.message : String(error)
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const fullPath = `${collection.path}/${plan.relPath}`;
|
|
895
|
+
if (plan.openedExisting) {
|
|
896
|
+
const existingDoc = await this.store.getDocument(
|
|
897
|
+
collection.name,
|
|
898
|
+
plan.relPath
|
|
899
|
+
);
|
|
900
|
+
if (!existingDoc.ok) {
|
|
901
|
+
throw sdkError("STORE", existingDoc.error.message, {
|
|
902
|
+
cause: existingDoc.error.cause,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
return buildCaptureReceipt({
|
|
906
|
+
plan,
|
|
907
|
+
absPath: fullPath,
|
|
908
|
+
docid: existingDoc.value?.docid,
|
|
909
|
+
sync: existingDoc.value
|
|
910
|
+
? { status: "completed" }
|
|
911
|
+
: {
|
|
912
|
+
status: "skipped",
|
|
913
|
+
reason: "Existing file is not indexed yet.",
|
|
914
|
+
},
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
919
|
+
await writeCapturePlanFile(plan, fullPath);
|
|
920
|
+
const syncResults = await defaultSyncService.syncFiles(
|
|
921
|
+
collection,
|
|
922
|
+
this.store,
|
|
923
|
+
[plan.relPath],
|
|
924
|
+
withContentTypeRules(
|
|
925
|
+
{
|
|
926
|
+
runUpdateCmd: false,
|
|
927
|
+
gitPull: false,
|
|
928
|
+
},
|
|
929
|
+
this.config
|
|
930
|
+
)
|
|
931
|
+
);
|
|
932
|
+
const syncResult = syncResults[0];
|
|
933
|
+
const docResult = await this.store.getDocument(
|
|
934
|
+
collection.name,
|
|
935
|
+
plan.relPath
|
|
936
|
+
);
|
|
937
|
+
const docid = docResult.ok ? docResult.value?.docid : undefined;
|
|
938
|
+
return buildCaptureReceipt({
|
|
939
|
+
plan,
|
|
940
|
+
absPath: fullPath,
|
|
941
|
+
docid: syncResult?.docid ?? docid,
|
|
942
|
+
sync:
|
|
943
|
+
syncResult?.status === "error"
|
|
944
|
+
? {
|
|
945
|
+
status: "failed",
|
|
946
|
+
error:
|
|
947
|
+
syncResult.errorMessage ??
|
|
948
|
+
syncResult.errorCode ??
|
|
949
|
+
"Unknown sync error",
|
|
950
|
+
}
|
|
951
|
+
: { status: "completed" },
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
|
|
825
955
|
async createFolder(
|
|
826
956
|
options: GnoCreateFolderOptions
|
|
827
957
|
): Promise<GnoCreateFolderResult> {
|
|
@@ -878,9 +1008,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
878
1008
|
const currentPath = `${collection.path}/${storedDoc.relPath}`;
|
|
879
1009
|
const nextPath = `${collection.path}/${plan.nextRelPath}`;
|
|
880
1010
|
await renameFilePath(currentPath, nextPath);
|
|
881
|
-
await defaultSyncService.syncCollection(
|
|
882
|
-
|
|
883
|
-
|
|
1011
|
+
await defaultSyncService.syncCollection(
|
|
1012
|
+
collection,
|
|
1013
|
+
this.store,
|
|
1014
|
+
withContentTypeRules({ runUpdateCmd: false }, this.config)
|
|
1015
|
+
);
|
|
884
1016
|
const linksResult = await this.store.getLinksForDoc(storedDoc.id);
|
|
885
1017
|
const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
|
|
886
1018
|
if (!linksResult.ok || !backlinksResult.ok) {
|
|
@@ -935,9 +1067,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
935
1067
|
const nextPath = `${collection.path}/${plan.nextRelPath}`;
|
|
936
1068
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
937
1069
|
await renameFilePath(currentPath, nextPath);
|
|
938
|
-
await defaultSyncService.syncCollection(
|
|
939
|
-
|
|
940
|
-
|
|
1070
|
+
await defaultSyncService.syncCollection(
|
|
1071
|
+
collection,
|
|
1072
|
+
this.store,
|
|
1073
|
+
withContentTypeRules({ runUpdateCmd: false }, this.config)
|
|
1074
|
+
);
|
|
941
1075
|
const linksResult = await this.store.getLinksForDoc(storedDoc.id);
|
|
942
1076
|
const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
|
|
943
1077
|
if (!linksResult.ok || !backlinksResult.ok) {
|
|
@@ -1004,9 +1138,11 @@ class GnoClientImpl implements GnoClient {
|
|
|
1004
1138
|
const nextPath = `${collection.path}/${plan.nextRelPath}`;
|
|
1005
1139
|
await mkdir(dirname(nextPath), { recursive: true });
|
|
1006
1140
|
await copyFilePath(currentPath, nextPath);
|
|
1007
|
-
await defaultSyncService.syncCollection(
|
|
1008
|
-
|
|
1009
|
-
|
|
1141
|
+
await defaultSyncService.syncCollection(
|
|
1142
|
+
collection,
|
|
1143
|
+
this.store,
|
|
1144
|
+
withContentTypeRules({ runUpdateCmd: false }, this.config)
|
|
1145
|
+
);
|
|
1010
1146
|
const linksResult = await this.store.getLinksForDoc(storedDoc.id);
|
|
1011
1147
|
const backlinksResult = await this.store.getBacklinksForDoc(storedDoc.id);
|
|
1012
1148
|
if (!linksResult.ok || !backlinksResult.ok) {
|
package/src/sdk/index.ts
CHANGED
package/src/sdk/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Config } from "../config/types";
|
|
8
|
+
import type { CaptureInput, CaptureReceipt } from "../core/capture";
|
|
8
9
|
import type { NoteCollisionPolicy } from "../core/note-creation";
|
|
9
10
|
import type { NotePresetId } from "../core/note-presets";
|
|
10
11
|
import type { DocumentSection } from "../core/sections";
|
|
@@ -130,6 +131,10 @@ export interface GnoCreateNoteResult {
|
|
|
130
131
|
createdWithSuffix?: boolean;
|
|
131
132
|
}
|
|
132
133
|
|
|
134
|
+
export interface GnoCaptureOptions extends Omit<CaptureInput, "overwrite"> {}
|
|
135
|
+
|
|
136
|
+
export type GnoCaptureResult = CaptureReceipt;
|
|
137
|
+
|
|
133
138
|
export interface GnoCreateFolderOptions {
|
|
134
139
|
collection: string;
|
|
135
140
|
name: string;
|
|
@@ -194,6 +199,7 @@ export interface GnoClient {
|
|
|
194
199
|
update(options?: GnoUpdateOptions): Promise<SyncResult>;
|
|
195
200
|
embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
|
|
196
201
|
index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
|
|
202
|
+
capture(options: GnoCaptureOptions): Promise<GnoCaptureResult>;
|
|
197
203
|
createNote(options: GnoCreateNoteOptions): Promise<GnoCreateNoteResult>;
|
|
198
204
|
createFolder(options: GnoCreateFolderOptions): Promise<GnoCreateFolderResult>;
|
|
199
205
|
renameNote(options: GnoRenameNoteOptions): Promise<GnoRefactorNoteResult>;
|
|
@@ -11,11 +11,13 @@ import type {
|
|
|
11
11
|
import { getIndexDbPath } from "../app/constants";
|
|
12
12
|
import {
|
|
13
13
|
ensureDirectories,
|
|
14
|
+
formatConfigWarnings,
|
|
14
15
|
getConfigPaths,
|
|
15
16
|
isInitialized,
|
|
16
17
|
loadConfig,
|
|
17
18
|
} from "../config";
|
|
18
19
|
import { defaultSyncService } from "../ingestion";
|
|
20
|
+
import { withContentTypeRules } from "../ingestion";
|
|
19
21
|
import { getActivePreset } from "../llm/registry";
|
|
20
22
|
import { SqliteAdapter } from "../store/sqlite/adapter";
|
|
21
23
|
import {
|
|
@@ -79,6 +81,7 @@ type BackgroundRuntimeDeps = {
|
|
|
79
81
|
scheduler: EmbedScheduler | null;
|
|
80
82
|
eventBus?: DocumentEventBus | null;
|
|
81
83
|
callbacks?: CollectionWatchCallbacks;
|
|
84
|
+
syncOptions?: Parameters<typeof withContentTypeRules>[0];
|
|
82
85
|
}) => CollectionWatchService;
|
|
83
86
|
};
|
|
84
87
|
|
|
@@ -103,6 +106,9 @@ export async function startBackgroundRuntime(
|
|
|
103
106
|
if (!configResult.ok) {
|
|
104
107
|
return { success: false, error: configResult.error.message };
|
|
105
108
|
}
|
|
109
|
+
for (const warning of formatConfigWarnings(configResult.warnings)) {
|
|
110
|
+
console.warn(warning);
|
|
111
|
+
}
|
|
106
112
|
const config = configResult.value;
|
|
107
113
|
|
|
108
114
|
if (options.requireCollections && config.collections.length === 0) {
|
|
@@ -171,6 +177,7 @@ export async function startBackgroundRuntime(
|
|
|
171
177
|
scheduler,
|
|
172
178
|
eventBus: options.eventBus ?? null,
|
|
173
179
|
callbacks: options.watchCallbacks,
|
|
180
|
+
syncOptions: withContentTypeRules({}, config),
|
|
174
181
|
});
|
|
175
182
|
watchService.start();
|
|
176
183
|
ctxHolder.watchService = watchService;
|
|
@@ -189,10 +196,17 @@ export async function startBackgroundRuntime(
|
|
|
189
196
|
eventBus: options.eventBus ?? null,
|
|
190
197
|
watchService,
|
|
191
198
|
async syncAll(syncOptions = {}) {
|
|
192
|
-
const syncResult = await syncAllService(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
const syncResult = await syncAllService(
|
|
200
|
+
config.collections,
|
|
201
|
+
store,
|
|
202
|
+
withContentTypeRules(
|
|
203
|
+
{
|
|
204
|
+
gitPull: syncOptions.gitPull,
|
|
205
|
+
runUpdateCmd: syncOptions.runUpdateCmd,
|
|
206
|
+
},
|
|
207
|
+
config
|
|
208
|
+
)
|
|
209
|
+
);
|
|
196
210
|
|
|
197
211
|
let embedResult: EmbedResult | null = null;
|
|
198
212
|
if (syncOptions.triggerEmbed !== false) {
|
package/src/serve/config-sync.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
type ApplyConfigResult,
|
|
15
15
|
type MutationResult,
|
|
16
16
|
} from "../core/config-mutation";
|
|
17
|
+
import { withContentTypeRules } from "../ingestion";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Apply a config mutation atomically with serialization.
|
|
@@ -53,7 +54,10 @@ export async function applyConfigChange(
|
|
|
53
54
|
);
|
|
54
55
|
|
|
55
56
|
if (result.ok) {
|
|
56
|
-
ctxHolder.watchService?.updateCollections(
|
|
57
|
+
ctxHolder.watchService?.updateCollections(
|
|
58
|
+
ctxHolder.config.collections,
|
|
59
|
+
withContentTypeRules({}, ctxHolder.config)
|
|
60
|
+
);
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
return result;
|
|
@@ -78,7 +82,10 @@ export async function applyConfigChangeTyped<T>(
|
|
|
78
82
|
);
|
|
79
83
|
|
|
80
84
|
if (result.ok) {
|
|
81
|
-
ctxHolder.watchService?.updateCollections(
|
|
85
|
+
ctxHolder.watchService?.updateCollections(
|
|
86
|
+
ctxHolder.config.collections,
|
|
87
|
+
withContentTypeRules({}, ctxHolder.config)
|
|
88
|
+
);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
return result;
|