@gmickel/gno 1.24.0 → 1.26.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 +18 -4
- package/assets/skill/SKILL.md +40 -17
- package/assets/skill/recipes/capture-and-file.md +20 -5
- package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip.sha256 +1 -0
- package/browser-extension/dist/PRIVACY.md +55 -0
- package/browser-extension/dist/chunk-vn5f663b.js +50 -0
- package/browser-extension/dist/chunk-ydfx5d7p.css +1 -0
- package/browser-extension/dist/content.js +1 -0
- package/browser-extension/dist/manifest.json +25 -0
- package/browser-extension/dist/preview.html +13 -0
- package/browser-extension/dist/service-worker.js +40 -0
- package/package.json +13 -3
- package/spec/cli.md +196 -4
- package/spec/db/schema.sql +102 -1
- package/spec/mcp.md +10 -0
- package/spec/output-schemas/browser-clip-preview.schema.json +83 -0
- package/spec/output-schemas/browser-clip.schema.json +586 -0
- package/spec/output-schemas/capture-receipt.schema.json +22 -1
- package/spec/output-schemas/clipper-csrf.schema.json +12 -0
- package/spec/output-schemas/clipper-error.schema.json +46 -0
- package/spec/output-schemas/clipper-pair-approval.schema.json +17 -0
- package/spec/output-schemas/clipper-pair-start.schema.json +26 -0
- package/spec/output-schemas/clipper-pair-status.schema.json +46 -0
- package/spec/output-schemas/clipper-revoke.schema.json +28 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +12 -1
- package/spec/output-schemas/project-profile-apply.schema.json +209 -0
- package/spec/output-schemas/project-profile-command.schema.json +160 -0
- package/spec/output-schemas/query-diagnose.schema.json +7 -1
- package/spec/output-schemas/setup-profile-result.schema.json +87 -0
- package/spec/project-profile.schema.json +301 -0
- package/src/cli/commands/collection/add.ts +39 -45
- package/src/cli/commands/collection/remove.ts +28 -28
- package/src/cli/commands/collection/rename.ts +55 -73
- package/src/cli/commands/context/add.ts +37 -26
- package/src/cli/commands/context/rm.ts +47 -20
- package/src/cli/commands/init.ts +55 -125
- package/src/cli/commands/models/use.ts +43 -38
- package/src/cli/commands/profile-apply.ts +334 -0
- package/src/cli/commands/profile.ts +409 -0
- package/src/cli/commands/setup-activation.ts +205 -54
- package/src/cli/commands/setup-profile.ts +223 -0
- package/src/cli/commands/setup.ts +3 -0
- package/src/cli/program.ts +110 -9
- package/src/config/index.ts +3 -0
- package/src/config/project-profile.ts +367 -0
- package/src/config/saver.ts +16 -7
- package/src/config/types.ts +42 -0
- package/src/core/browser-clip-provenance.ts +139 -0
- package/src/core/browser-clip.ts +473 -0
- package/src/core/capture-write.ts +5 -0
- package/src/core/capture.ts +75 -18
- package/src/core/config-mutation.ts +138 -76
- package/src/core/config-write-lock.ts +89 -0
- package/src/core/context-identity.ts +16 -0
- package/src/core/context-resolver.ts +2 -12
- package/src/core/file-lock.ts +20 -6
- package/src/core/folder-setup-planning.ts +6 -21
- package/src/core/folder-setup.ts +30 -2
- package/src/core/path-rules.ts +53 -0
- package/src/core/project-affinity-surface.ts +102 -7
- package/src/core/project-profile-apply-state.ts +268 -0
- package/src/core/project-profile-apply-validation.ts +95 -0
- package/src/core/project-profile-apply.ts +408 -0
- package/src/core/project-profile-canonical.ts +71 -0
- package/src/core/project-profile-diff.ts +302 -0
- package/src/core/project-profile-discovery.ts +519 -0
- package/src/core/project-profile-file.ts +37 -0
- package/src/core/project-profile-parser.ts +98 -0
- package/src/core/project-profile.ts +490 -0
- package/src/ingestion/walker.ts +85 -44
- package/src/llm/cache.ts +21 -0
- package/src/serve/capture-service.ts +420 -0
- package/src/serve/clipper-body.ts +62 -0
- package/src/serve/clipper-capture.ts +248 -0
- package/src/serve/clipper-contract.ts +57 -0
- package/src/serve/clipper-idempotency.ts +35 -0
- package/src/serve/clipper-pairing.ts +297 -0
- package/src/serve/clipper-security-errors.ts +23 -0
- package/src/serve/clipper-security.ts +449 -0
- package/src/serve/config-sync.ts +2 -2
- package/src/serve/public/app.tsx +8 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/index.html +1 -0
- package/src/serve/public/lib/clipper-approval.ts +206 -0
- package/src/serve/public/pages/ClipperPairing.tsx +210 -0
- package/src/serve/resident-runtime.ts +1 -0
- package/src/serve/routes/api.ts +20 -115
- package/src/serve/routes/clipper.ts +394 -0
- package/src/serve/server.ts +22 -0
- package/src/store/migrations/020-browser-clipper-security.ts +128 -0
- package/src/store/migrations/021-multi-context-identity.ts +37 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +83 -0
- package/src/store/sqlite/clipper-store-types.ts +104 -0
- package/src/store/sqlite/clipper-store.ts +496 -0
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* @module src/cli/commands/context/add
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { parseScope } from "../../../config";
|
|
10
|
+
import { applyConfigFileChange } from "../../../core/config-mutation";
|
|
11
|
+
import { normalizePersistedContextText } from "../../../core/context-identity";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Exit codes
|
|
@@ -21,7 +23,11 @@ const EXIT_VALIDATION = 1;
|
|
|
21
23
|
* @param text - Context description text
|
|
22
24
|
* @returns Exit code
|
|
23
25
|
*/
|
|
24
|
-
export async function contextAdd(
|
|
26
|
+
export async function contextAdd(
|
|
27
|
+
scope: string,
|
|
28
|
+
text: string,
|
|
29
|
+
options: { configPath?: string } = {}
|
|
30
|
+
): Promise<number> {
|
|
25
31
|
// Parse scope
|
|
26
32
|
const parsed = parseScope(scope);
|
|
27
33
|
if (!parsed) {
|
|
@@ -32,33 +38,38 @@ export async function contextAdd(scope: string, text: string): Promise<number> {
|
|
|
32
38
|
return EXIT_VALIDATION;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
console.error(`Error: ${configResult.error.message}`);
|
|
41
|
+
const normalizedText = normalizePersistedContextText(text);
|
|
42
|
+
if (!normalizedText) {
|
|
43
|
+
console.error("Error: Context text must not be empty");
|
|
39
44
|
return EXIT_VALIDATION;
|
|
40
45
|
}
|
|
41
46
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
47
|
+
const mutation = await applyConfigFileChange(
|
|
48
|
+
{ configPath: options.configPath },
|
|
49
|
+
(config) => {
|
|
50
|
+
const duplicate = config.contexts.some(
|
|
51
|
+
(context) =>
|
|
52
|
+
context.scopeType === parsed.type &&
|
|
53
|
+
context.scopeKey === parsed.key &&
|
|
54
|
+
normalizePersistedContextText(context.text) === normalizedText
|
|
55
|
+
);
|
|
56
|
+
if (duplicate) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false as const,
|
|
59
|
+
error: `Context for scope "${scope}" with that text already exists`,
|
|
60
|
+
code: "DUPLICATE",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
config.contexts.push({
|
|
64
|
+
scopeType: parsed.type,
|
|
65
|
+
scopeKey: parsed.key,
|
|
66
|
+
text: normalizedText,
|
|
67
|
+
});
|
|
68
|
+
return { ok: true as const, config };
|
|
69
|
+
}
|
|
70
|
+
);
|
|
71
|
+
if (!mutation.ok) {
|
|
72
|
+
console.error(`Error: ${mutation.error}`);
|
|
62
73
|
return EXIT_VALIDATION;
|
|
63
74
|
}
|
|
64
75
|
|
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* @module src/cli/commands/context/rm
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import { parseScope } from "../../../config";
|
|
10
|
+
import { applyConfigFileChange } from "../../../core/config-mutation";
|
|
11
|
+
import { normalizePersistedContextText } from "../../../core/context-identity";
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Exit codes
|
|
@@ -20,30 +22,55 @@ const EXIT_VALIDATION = 1;
|
|
|
20
22
|
* @param scope - Scope key to remove
|
|
21
23
|
* @returns Exit code
|
|
22
24
|
*/
|
|
23
|
-
export async function contextRm(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
export async function contextRm(
|
|
26
|
+
scope: string,
|
|
27
|
+
text?: string,
|
|
28
|
+
options: { configPath?: string } = {}
|
|
29
|
+
): Promise<number> {
|
|
30
|
+
const parsed = parseScope(scope);
|
|
31
|
+
if (!parsed) {
|
|
32
|
+
console.error(`Error: Invalid scope format: ${scope}`);
|
|
28
33
|
return EXIT_VALIDATION;
|
|
29
34
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const index = config.contexts.findIndex((ctx) => ctx.scopeKey === scope);
|
|
35
|
-
if (index === -1) {
|
|
36
|
-
console.error(`Error: Context for scope "${scope}" not found`);
|
|
35
|
+
const normalizedText =
|
|
36
|
+
text === undefined ? undefined : normalizePersistedContextText(text);
|
|
37
|
+
if (normalizedText === "") {
|
|
38
|
+
console.error("Error: Context text must not be empty");
|
|
37
39
|
return EXIT_VALIDATION;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const mutation = await applyConfigFileChange(
|
|
43
|
+
{ configPath: options.configPath },
|
|
44
|
+
(config) => {
|
|
45
|
+
const matches = config.contexts
|
|
46
|
+
.map((context, index) => ({ context, index }))
|
|
47
|
+
.filter(
|
|
48
|
+
({ context }) =>
|
|
49
|
+
context.scopeType === parsed.type &&
|
|
50
|
+
context.scopeKey === parsed.key &&
|
|
51
|
+
(normalizedText === undefined ||
|
|
52
|
+
normalizePersistedContextText(context.text) === normalizedText)
|
|
53
|
+
);
|
|
54
|
+
if (matches.length === 0) {
|
|
55
|
+
return {
|
|
56
|
+
ok: false as const,
|
|
57
|
+
error: `Context for scope "${scope}" not found`,
|
|
58
|
+
code: "NOT_FOUND",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (normalizedText === undefined && matches.length > 1) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false as const,
|
|
64
|
+
error: `Multiple contexts exist for scope "${scope}"; pass the exact text to remove`,
|
|
65
|
+
code: "AMBIGUOUS",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
config.contexts.splice(matches[0]!.index, 1);
|
|
69
|
+
return { ok: true as const, config };
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
if (!mutation.ok) {
|
|
73
|
+
console.error(`Error: ${mutation.error}`);
|
|
47
74
|
return EXIT_VALIDATION;
|
|
48
75
|
}
|
|
49
76
|
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -17,13 +17,12 @@ import {
|
|
|
17
17
|
FTS_TOKENIZERS,
|
|
18
18
|
type FtsTokenizer,
|
|
19
19
|
getConfigPaths,
|
|
20
|
-
isInitialized,
|
|
21
20
|
isValidLanguageHint,
|
|
22
|
-
loadConfigOrNull,
|
|
23
21
|
pathExists,
|
|
24
|
-
saveConfig,
|
|
25
22
|
toAbsolutePath,
|
|
26
23
|
} from "../../config";
|
|
24
|
+
import { applyConfigFileChange } from "../../core/config-mutation";
|
|
25
|
+
import { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
27
26
|
|
|
28
27
|
/** Pattern to replace invalid chars in collection names with hyphens */
|
|
29
28
|
const INVALID_NAME_CHARS = /[^a-z0-9_-]/g;
|
|
@@ -70,166 +69,97 @@ export interface InitResult {
|
|
|
70
69
|
error?: string;
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
/**
|
|
74
|
-
* Handle case when already initialized.
|
|
75
|
-
*/
|
|
76
|
-
async function handleAlreadyInitialized(
|
|
77
|
-
options: InitOptions,
|
|
78
|
-
paths: ReturnType<typeof getConfigPaths>
|
|
79
|
-
): Promise<InitResult> {
|
|
80
|
-
// Ensure directories exist (may have been deleted by reset)
|
|
81
|
-
await ensureDirectories();
|
|
82
|
-
|
|
83
|
-
const config = await loadConfigOrNull(options.configPath);
|
|
84
|
-
const dbPath = getIndexDbPath();
|
|
85
|
-
|
|
86
|
-
if (!options.path) {
|
|
87
|
-
return {
|
|
88
|
-
success: true,
|
|
89
|
-
alreadyInitialized: true,
|
|
90
|
-
configPath: paths.configFile,
|
|
91
|
-
dataDir: paths.dataDir,
|
|
92
|
-
dbPath,
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (!config) {
|
|
97
|
-
return {
|
|
98
|
-
success: false,
|
|
99
|
-
configPath: paths.configFile,
|
|
100
|
-
dataDir: paths.dataDir,
|
|
101
|
-
dbPath,
|
|
102
|
-
error: "Config exists but could not be loaded",
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const collectionResult = await addCollectionToConfig(config, options);
|
|
107
|
-
if (!collectionResult.success) {
|
|
108
|
-
return {
|
|
109
|
-
success: false,
|
|
110
|
-
configPath: paths.configFile,
|
|
111
|
-
dataDir: paths.dataDir,
|
|
112
|
-
dbPath,
|
|
113
|
-
error: collectionResult.error,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const saveResult = await saveConfig(config, options.configPath);
|
|
118
|
-
if (!saveResult.ok) {
|
|
119
|
-
return {
|
|
120
|
-
success: false,
|
|
121
|
-
configPath: paths.configFile,
|
|
122
|
-
dataDir: paths.dataDir,
|
|
123
|
-
dbPath,
|
|
124
|
-
error: saveResult.error.message,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
success: true,
|
|
130
|
-
alreadyInitialized: true,
|
|
131
|
-
configPath: paths.configFile,
|
|
132
|
-
dataDir: paths.dataDir,
|
|
133
|
-
dbPath,
|
|
134
|
-
collectionAdded: collectionResult.collectionName,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
72
|
/**
|
|
139
73
|
* Execute gno init command.
|
|
140
74
|
*/
|
|
141
75
|
export async function init(options: InitOptions = {}): Promise<InitResult> {
|
|
142
76
|
const paths = getConfigPaths();
|
|
77
|
+
const configPath = toAbsolutePath(options.configPath ?? paths.configFile);
|
|
78
|
+
const dbPath = getIndexDbPath();
|
|
143
79
|
|
|
144
|
-
// Check if already initialized
|
|
145
|
-
const initialized = await isInitialized(options.configPath);
|
|
146
|
-
if (initialized) {
|
|
147
|
-
return handleAlreadyInitialized(options, paths);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Create directories
|
|
151
80
|
const dirResult = await ensureDirectories();
|
|
152
81
|
if (!dirResult.ok) {
|
|
153
82
|
return {
|
|
154
83
|
success: false,
|
|
155
|
-
configPath
|
|
84
|
+
configPath,
|
|
156
85
|
dataDir: paths.dataDir,
|
|
157
|
-
dbPath
|
|
86
|
+
dbPath,
|
|
158
87
|
error: dirResult.error.message,
|
|
159
88
|
};
|
|
160
89
|
}
|
|
161
90
|
|
|
162
|
-
// Validate tokenizer option if provided
|
|
163
91
|
if (options.tokenizer && !FTS_TOKENIZERS.includes(options.tokenizer)) {
|
|
164
92
|
return {
|
|
165
93
|
success: false,
|
|
166
|
-
configPath
|
|
94
|
+
configPath,
|
|
167
95
|
dataDir: paths.dataDir,
|
|
168
|
-
dbPath
|
|
96
|
+
dbPath,
|
|
169
97
|
error: `Invalid tokenizer: ${options.tokenizer}. Valid: ${FTS_TOKENIZERS.join(", ")}`,
|
|
170
98
|
};
|
|
171
99
|
}
|
|
172
100
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
config
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
101
|
+
const mutation = await applyConfigFileChange(
|
|
102
|
+
{
|
|
103
|
+
configPath,
|
|
104
|
+
createConfigIfMissing: createDefaultConfig,
|
|
105
|
+
},
|
|
106
|
+
async (config, state) => {
|
|
107
|
+
if (state.created && options.tokenizer) {
|
|
108
|
+
config.ftsTokenizer = options.tokenizer;
|
|
109
|
+
}
|
|
110
|
+
let collectionName: string | undefined;
|
|
111
|
+
if (options.path) {
|
|
112
|
+
const collectionResult = await addCollectionToConfig(config, options);
|
|
113
|
+
if (!collectionResult.success) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false as const,
|
|
116
|
+
error: collectionResult.error,
|
|
117
|
+
code: "VALIDATION",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
collectionName = collectionResult.collectionName;
|
|
121
|
+
}
|
|
186
122
|
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
123
|
+
ok: true as const,
|
|
124
|
+
config,
|
|
125
|
+
skipSave: !state.created && !options.path,
|
|
126
|
+
value: {
|
|
127
|
+
alreadyInitialized: !state.created,
|
|
128
|
+
collectionName,
|
|
129
|
+
},
|
|
192
130
|
};
|
|
193
131
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
// Save config
|
|
198
|
-
const saveResult = await saveConfig(config, options.configPath);
|
|
199
|
-
if (!saveResult.ok) {
|
|
132
|
+
);
|
|
133
|
+
if (!mutation.ok) {
|
|
200
134
|
return {
|
|
201
135
|
success: false,
|
|
202
|
-
configPath
|
|
136
|
+
configPath,
|
|
203
137
|
dataDir: paths.dataDir,
|
|
204
|
-
dbPath
|
|
205
|
-
error:
|
|
138
|
+
dbPath,
|
|
139
|
+
error: mutation.error,
|
|
206
140
|
};
|
|
207
141
|
}
|
|
208
142
|
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
configPath: paths.configFile,
|
|
220
|
-
dataDir: paths.dataDir,
|
|
221
|
-
dbPath,
|
|
222
|
-
error: `Failed to create database file: ${error instanceof Error ? error.message : String(error)}`,
|
|
223
|
-
};
|
|
224
|
-
}
|
|
143
|
+
const store = new SqliteAdapter();
|
|
144
|
+
const opened = await store.open(dbPath, mutation.config.ftsTokenizer);
|
|
145
|
+
if (!opened.ok) {
|
|
146
|
+
return {
|
|
147
|
+
success: false,
|
|
148
|
+
configPath,
|
|
149
|
+
dataDir: paths.dataDir,
|
|
150
|
+
dbPath,
|
|
151
|
+
error: `Failed to initialize database: ${opened.error.message}`,
|
|
152
|
+
};
|
|
225
153
|
}
|
|
154
|
+
await store.close();
|
|
226
155
|
|
|
227
156
|
return {
|
|
228
157
|
success: true,
|
|
229
|
-
|
|
158
|
+
alreadyInitialized: mutation.value?.alreadyInitialized || undefined,
|
|
159
|
+
configPath,
|
|
230
160
|
dataDir: paths.dataDir,
|
|
231
161
|
dbPath,
|
|
232
|
-
collectionAdded: collectionName,
|
|
162
|
+
collectionAdded: mutation.value?.collectionName,
|
|
233
163
|
};
|
|
234
164
|
}
|
|
235
165
|
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* @module src/cli/commands/models/use
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { createDefaultConfig
|
|
9
|
-
import {
|
|
8
|
+
import { createDefaultConfig } from "../../../config";
|
|
9
|
+
import { applyConfigFileChange } from "../../../core/config-mutation";
|
|
10
10
|
import { getPreset, listPresets, resolveModelUri } from "../../../llm/registry";
|
|
11
11
|
|
|
12
12
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -38,51 +38,56 @@ export async function modelsUse(
|
|
|
38
38
|
presetId: string,
|
|
39
39
|
options: ModelsUseOptions = {}
|
|
40
40
|
): Promise<ModelsUseResult> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// Check if preset exists
|
|
47
|
-
const preset = getPreset(config, presetId);
|
|
48
|
-
if (!preset) {
|
|
49
|
-
const available = listPresets(config)
|
|
50
|
-
.map((p) => p.id)
|
|
51
|
-
.join(", ");
|
|
52
|
-
return {
|
|
53
|
-
success: false,
|
|
54
|
-
error: `Unknown preset: ${presetId}. Available: ${available}`,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Update config with new active preset
|
|
59
|
-
const updatedConfig = {
|
|
60
|
-
...config,
|
|
61
|
-
models: {
|
|
62
|
-
activePreset: presetId,
|
|
63
|
-
// Preserve existing presets or use defaults from code
|
|
64
|
-
presets: config.models?.presets ?? [],
|
|
65
|
-
loadTimeout: config.models?.loadTimeout ?? 60_000,
|
|
66
|
-
inferenceTimeout: config.models?.inferenceTimeout ?? 30_000,
|
|
67
|
-
expandContextSize: config.models?.expandContextSize ?? 2_048,
|
|
68
|
-
warmModelTtl: config.models?.warmModelTtl ?? 300_000,
|
|
41
|
+
const mutation = await applyConfigFileChange(
|
|
42
|
+
{
|
|
43
|
+
configPath: options.configPath,
|
|
44
|
+
createConfigIfMissing: createDefaultConfig,
|
|
69
45
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
46
|
+
(config) => {
|
|
47
|
+
const preset = getPreset(config, presetId);
|
|
48
|
+
if (!preset) {
|
|
49
|
+
const available = listPresets(config)
|
|
50
|
+
.map((item) => item.id)
|
|
51
|
+
.join(", ");
|
|
52
|
+
return {
|
|
53
|
+
ok: false as const,
|
|
54
|
+
error: `Unknown preset: ${presetId}. Available: ${available}`,
|
|
55
|
+
code: "UNKNOWN_PRESET",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const previousEmbedModel = resolveModelUri(config, "embed");
|
|
59
|
+
return {
|
|
60
|
+
ok: true as const,
|
|
61
|
+
config: {
|
|
62
|
+
...config,
|
|
63
|
+
models: {
|
|
64
|
+
activePreset: presetId,
|
|
65
|
+
presets: config.models?.presets ?? [],
|
|
66
|
+
loadTimeout: config.models?.loadTimeout ?? 60_000,
|
|
67
|
+
inferenceTimeout: config.models?.inferenceTimeout ?? 30_000,
|
|
68
|
+
expandContextSize: config.models?.expandContextSize ?? 2_048,
|
|
69
|
+
warmModelTtl: config.models?.warmModelTtl ?? 300_000,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
value: {
|
|
73
|
+
name: preset.name,
|
|
74
|
+
embedModelChanged: previousEmbedModel !== preset.embed,
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
if (!mutation.ok) {
|
|
75
80
|
return {
|
|
76
81
|
success: false,
|
|
77
|
-
error:
|
|
82
|
+
error: mutation.error,
|
|
78
83
|
};
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
return {
|
|
82
87
|
success: true,
|
|
83
88
|
preset: presetId,
|
|
84
|
-
name:
|
|
85
|
-
embedModelChanged:
|
|
89
|
+
name: mutation.value!.name,
|
|
90
|
+
embedModelChanged: mutation.value!.embedModelChanged,
|
|
86
91
|
};
|
|
87
92
|
}
|
|
88
93
|
|