@gmickel/gno 1.25.1 → 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 +10 -4
- package/assets/skill/SKILL.md +29 -17
- package/browser-extension/artifacts/{gno-browser-clipper-v1.25.1.zip → 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/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +146 -4
- package/spec/db/schema.sql +1 -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/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/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/config-sync.ts +2 -2
- package/src/serve/resident-runtime.ts +1 -0
- package/src/serve/routes/api.ts +1 -0
- package/src/store/migrations/021-multi-context-identity.ts +37 -0
- package/src/store/migrations/index.ts +2 -0
- package/src/store/sqlite/adapter.ts +83 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.25.1.zip.sha256 +0 -1
|
@@ -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
|
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutating project-profile CLI composition.
|
|
3
|
+
*
|
|
4
|
+
* @module src/cli/commands/profile-apply
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// node:fs/promises provides recursive directory creation; Bun has no equivalent structural API.
|
|
8
|
+
import { mkdir } from "node:fs/promises";
|
|
9
|
+
|
|
10
|
+
import type { ProjectProfileApplyReceipt } from "../../core/project-profile-apply";
|
|
11
|
+
import type {
|
|
12
|
+
ProjectProfileDiscoveryDependencies,
|
|
13
|
+
ProjectProfileDiscoverySummary,
|
|
14
|
+
} from "../../core/project-profile-discovery";
|
|
15
|
+
import type { SqliteAdapter as SqliteAdapterType } from "../../store/sqlite/adapter";
|
|
16
|
+
import type { ProjectProfileCommandDiagnostic } from "./profile";
|
|
17
|
+
|
|
18
|
+
import { getIndexDbPath } from "../../app/constants";
|
|
19
|
+
import {
|
|
20
|
+
createDefaultConfig,
|
|
21
|
+
getConfigPaths,
|
|
22
|
+
loadConfig,
|
|
23
|
+
toAbsolutePath,
|
|
24
|
+
} from "../../config";
|
|
25
|
+
import {
|
|
26
|
+
applyProjectProfile,
|
|
27
|
+
validateProjectProfileRuntimePaths,
|
|
28
|
+
} from "../../core/project-profile-apply";
|
|
29
|
+
import { discoverProjectProfile } from "../../core/project-profile-discovery";
|
|
30
|
+
import {
|
|
31
|
+
ProjectProfileFileError,
|
|
32
|
+
readProjectProfileFile,
|
|
33
|
+
} from "../../core/project-profile-file";
|
|
34
|
+
import { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
35
|
+
import {
|
|
36
|
+
compilerDiagnostic,
|
|
37
|
+
discoveryDiagnostic,
|
|
38
|
+
PROJECT_PROFILE_COMMAND_SCHEMA_VERSION,
|
|
39
|
+
sortDiagnostics,
|
|
40
|
+
} from "./profile";
|
|
41
|
+
|
|
42
|
+
export interface ProjectProfileApplyCommandResult {
|
|
43
|
+
schemaVersion: typeof PROJECT_PROFILE_COMMAND_SCHEMA_VERSION;
|
|
44
|
+
command: "apply";
|
|
45
|
+
status:
|
|
46
|
+
| "applied"
|
|
47
|
+
| "unchanged"
|
|
48
|
+
| "invalid"
|
|
49
|
+
| "not_found"
|
|
50
|
+
| "disabled"
|
|
51
|
+
| "failed";
|
|
52
|
+
applied: boolean;
|
|
53
|
+
discovery: ProjectProfileDiscoverySummary;
|
|
54
|
+
receipt: ProjectProfileApplyReceipt | null;
|
|
55
|
+
diagnostics: ProjectProfileCommandDiagnostic[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ProjectProfileApplyCommandOutcome {
|
|
59
|
+
result: ProjectProfileApplyCommandResult;
|
|
60
|
+
exitCode: 0 | 1 | 2;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ProjectProfileApplyCommandOptions {
|
|
64
|
+
path?: string;
|
|
65
|
+
cwd?: string;
|
|
66
|
+
channel?: "local" | "remote";
|
|
67
|
+
configPath?: string;
|
|
68
|
+
dataDir?: string;
|
|
69
|
+
indexName?: string;
|
|
70
|
+
discoveryDependencies?: Partial<ProjectProfileDiscoveryDependencies>;
|
|
71
|
+
readProfileFn?: (path: string) => Promise<string>;
|
|
72
|
+
createStore?: () => SqliteAdapterType;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const failedApplyResult = (
|
|
76
|
+
status: "disabled" | "failed" | "invalid" | "not_found",
|
|
77
|
+
discovery: ProjectProfileDiscoverySummary,
|
|
78
|
+
diagnostics: ProjectProfileCommandDiagnostic[]
|
|
79
|
+
): ProjectProfileApplyCommandResult => ({
|
|
80
|
+
schemaVersion: PROJECT_PROFILE_COMMAND_SCHEMA_VERSION,
|
|
81
|
+
command: "apply",
|
|
82
|
+
status,
|
|
83
|
+
applied: false,
|
|
84
|
+
discovery,
|
|
85
|
+
receipt: null,
|
|
86
|
+
diagnostics: sortDiagnostics(diagnostics),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Apply one trusted local profile through the same discovery/compiler/diff
|
|
91
|
+
* pipeline used by the read commands.
|
|
92
|
+
*/
|
|
93
|
+
export async function runProjectProfileApplyCommand(
|
|
94
|
+
options: ProjectProfileApplyCommandOptions
|
|
95
|
+
): Promise<ProjectProfileApplyCommandOutcome> {
|
|
96
|
+
const discovery = await discoverProjectProfile(
|
|
97
|
+
{
|
|
98
|
+
channel: options.channel ?? "local",
|
|
99
|
+
cwd: options.cwd,
|
|
100
|
+
rootOverride: options.path,
|
|
101
|
+
},
|
|
102
|
+
options.discoveryDependencies
|
|
103
|
+
);
|
|
104
|
+
const discoveryDiagnostics = discovery.diagnostics.map(discoveryDiagnostic);
|
|
105
|
+
if (discovery.summary.status !== "found") {
|
|
106
|
+
const status =
|
|
107
|
+
discovery.summary.status === "not_found"
|
|
108
|
+
? "not_found"
|
|
109
|
+
: discovery.summary.status === "disabled"
|
|
110
|
+
? "disabled"
|
|
111
|
+
: "invalid";
|
|
112
|
+
return {
|
|
113
|
+
result: failedApplyResult(
|
|
114
|
+
status,
|
|
115
|
+
discovery.summary,
|
|
116
|
+
discoveryDiagnostics
|
|
117
|
+
),
|
|
118
|
+
exitCode: discoveryDiagnostics.some(
|
|
119
|
+
(diagnostic) => diagnostic.code === "PROFILE_DISCOVERY_FAILED"
|
|
120
|
+
)
|
|
121
|
+
? 2
|
|
122
|
+
: 1,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (!(discovery.profilePath && discovery.profileRoot)) {
|
|
126
|
+
return {
|
|
127
|
+
result: failedApplyResult("failed", discovery.summary, [
|
|
128
|
+
...discoveryDiagnostics,
|
|
129
|
+
{
|
|
130
|
+
code: "PROFILE_DISCOVERY_FAILED",
|
|
131
|
+
severity: "error",
|
|
132
|
+
path: ".gno/index.yml",
|
|
133
|
+
message: "Discovery returned no trusted local profile identity.",
|
|
134
|
+
remediation: "Rerun with an exact readable project root.",
|
|
135
|
+
},
|
|
136
|
+
]),
|
|
137
|
+
exitCode: 2,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let profileYaml: string;
|
|
142
|
+
try {
|
|
143
|
+
profileYaml = await (options.readProfileFn ?? readProjectProfileFile)(
|
|
144
|
+
discovery.profilePath
|
|
145
|
+
);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
const invalidFile = error instanceof ProjectProfileFileError ? error : null;
|
|
148
|
+
return {
|
|
149
|
+
result: failedApplyResult("failed", discovery.summary, [
|
|
150
|
+
...discoveryDiagnostics,
|
|
151
|
+
{
|
|
152
|
+
code: invalidFile?.code ?? "PROFILE_READ_FAILED",
|
|
153
|
+
severity: "error",
|
|
154
|
+
path: ".gno/index.yml",
|
|
155
|
+
message:
|
|
156
|
+
invalidFile?.message ??
|
|
157
|
+
"The selected project profile could not be read.",
|
|
158
|
+
remediation: invalidFile
|
|
159
|
+
? "Reduce or repair .gno/index.yml and retry."
|
|
160
|
+
: "Repair local file permissions and retry.",
|
|
161
|
+
},
|
|
162
|
+
]),
|
|
163
|
+
exitCode: invalidFile ? 1 : 2,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const paths = getConfigPaths();
|
|
168
|
+
const configPath = toAbsolutePath(options.configPath ?? paths.configFile);
|
|
169
|
+
const dataDir = toAbsolutePath(options.dataDir ?? paths.dataDir);
|
|
170
|
+
const indexName = options.indexName ?? "default";
|
|
171
|
+
const indexPath = getIndexDbPath(indexName, {
|
|
172
|
+
config: paths.configDir,
|
|
173
|
+
data: dataDir,
|
|
174
|
+
cache: paths.cacheDir,
|
|
175
|
+
});
|
|
176
|
+
const overlap = await validateProjectProfileRuntimePaths(
|
|
177
|
+
discovery.profileRoot,
|
|
178
|
+
[
|
|
179
|
+
{ label: "Config", path: configPath },
|
|
180
|
+
{ label: "User data directory", path: dataDir },
|
|
181
|
+
{ label: "Model cache directory", path: paths.cacheDir },
|
|
182
|
+
{ label: "Index database", path: indexPath },
|
|
183
|
+
]
|
|
184
|
+
);
|
|
185
|
+
if (overlap) {
|
|
186
|
+
return {
|
|
187
|
+
result: failedApplyResult("invalid", discovery.summary, [
|
|
188
|
+
...discoveryDiagnostics,
|
|
189
|
+
{
|
|
190
|
+
code: overlap.code,
|
|
191
|
+
severity: "error",
|
|
192
|
+
path: "runtime",
|
|
193
|
+
message: overlap.message,
|
|
194
|
+
remediation: overlap.remediation,
|
|
195
|
+
},
|
|
196
|
+
]),
|
|
197
|
+
exitCode: 1,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const loaded = await loadConfig(configPath);
|
|
201
|
+
if (!loaded.ok && loaded.error.code !== "NOT_FOUND") {
|
|
202
|
+
return {
|
|
203
|
+
result: failedApplyResult("invalid", discovery.summary, [
|
|
204
|
+
...discoveryDiagnostics,
|
|
205
|
+
{
|
|
206
|
+
code: "CONFIG_INVALID",
|
|
207
|
+
severity: "error",
|
|
208
|
+
path: "config",
|
|
209
|
+
message: "The selected local GNO config could not be loaded safely.",
|
|
210
|
+
remediation: "Repair the local config and rerun gno profile apply.",
|
|
211
|
+
},
|
|
212
|
+
]),
|
|
213
|
+
exitCode: 1,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
const startingConfig = loaded.ok ? loaded.value : createDefaultConfig();
|
|
217
|
+
const store = options.createStore?.() ?? new SqliteAdapter();
|
|
218
|
+
try {
|
|
219
|
+
await mkdir(dataDir, { recursive: true });
|
|
220
|
+
store.setConfigPath(configPath);
|
|
221
|
+
const opened = await store.open(indexPath, startingConfig.ftsTokenizer);
|
|
222
|
+
if (!opened.ok) {
|
|
223
|
+
return {
|
|
224
|
+
result: failedApplyResult("failed", discovery.summary, [
|
|
225
|
+
...discoveryDiagnostics,
|
|
226
|
+
{
|
|
227
|
+
code: "INDEX_OPEN_FAILED",
|
|
228
|
+
severity: "error",
|
|
229
|
+
path: "index",
|
|
230
|
+
message: "The selected local index could not be opened.",
|
|
231
|
+
remediation: "Repair the user data directory and rerun apply.",
|
|
232
|
+
},
|
|
233
|
+
]),
|
|
234
|
+
exitCode: 2,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const applied = await applyProjectProfile({
|
|
238
|
+
profileYaml,
|
|
239
|
+
profileRoot: discovery.profileRoot,
|
|
240
|
+
profilePath: discovery.profilePath,
|
|
241
|
+
configPath,
|
|
242
|
+
dataDir,
|
|
243
|
+
store,
|
|
244
|
+
runtimePaths: [
|
|
245
|
+
{ label: "Model cache directory", path: paths.cacheDir },
|
|
246
|
+
{ label: "Index database", path: indexPath },
|
|
247
|
+
],
|
|
248
|
+
});
|
|
249
|
+
if (!applied.ok) {
|
|
250
|
+
const diagnostics = [
|
|
251
|
+
...discoveryDiagnostics,
|
|
252
|
+
...(applied.error.diagnostics ?? []).map(compilerDiagnostic),
|
|
253
|
+
{
|
|
254
|
+
code: applied.error.code,
|
|
255
|
+
severity: "error" as const,
|
|
256
|
+
path:
|
|
257
|
+
applied.error.code === "PROFILE_INVALID"
|
|
258
|
+
? ".gno/index.yml"
|
|
259
|
+
: "runtime",
|
|
260
|
+
message: applied.error.message,
|
|
261
|
+
remediation: applied.error.remediation,
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
const validationFailure =
|
|
265
|
+
applied.error.code === "PROFILE_INVALID" ||
|
|
266
|
+
applied.error.code === "RUNTIME_PATH_OVERLAP";
|
|
267
|
+
return {
|
|
268
|
+
result: failedApplyResult(
|
|
269
|
+
validationFailure ? "invalid" : "failed",
|
|
270
|
+
discovery.summary,
|
|
271
|
+
diagnostics
|
|
272
|
+
),
|
|
273
|
+
exitCode: validationFailure ? 1 : 2,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
result: {
|
|
278
|
+
schemaVersion: PROJECT_PROFILE_COMMAND_SCHEMA_VERSION,
|
|
279
|
+
command: "apply",
|
|
280
|
+
status: applied.receipt.status,
|
|
281
|
+
applied: applied.receipt.status === "applied",
|
|
282
|
+
discovery: discovery.summary,
|
|
283
|
+
receipt: applied.receipt,
|
|
284
|
+
diagnostics: sortDiagnostics([
|
|
285
|
+
...discoveryDiagnostics,
|
|
286
|
+
...applied.receipt.diagnostics,
|
|
287
|
+
]),
|
|
288
|
+
},
|
|
289
|
+
exitCode: 0,
|
|
290
|
+
};
|
|
291
|
+
} catch {
|
|
292
|
+
return {
|
|
293
|
+
result: failedApplyResult("failed", discovery.summary, [
|
|
294
|
+
...discoveryDiagnostics,
|
|
295
|
+
{
|
|
296
|
+
code: "RUNTIME_IO_FAILED",
|
|
297
|
+
severity: "error",
|
|
298
|
+
path: "runtime",
|
|
299
|
+
message: "The selected runtime directories could not be prepared.",
|
|
300
|
+
remediation:
|
|
301
|
+
"Repair user config/data-directory permissions and rerun apply.",
|
|
302
|
+
},
|
|
303
|
+
]),
|
|
304
|
+
exitCode: 2,
|
|
305
|
+
};
|
|
306
|
+
} finally {
|
|
307
|
+
await store.close();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export function formatProjectProfileApplyResult(
|
|
312
|
+
result: ProjectProfileApplyCommandResult,
|
|
313
|
+
options: { json: boolean }
|
|
314
|
+
): string {
|
|
315
|
+
if (options.json) return JSON.stringify(result, null, 2);
|
|
316
|
+
const lines = [
|
|
317
|
+
`Profile apply ${result.status}: ${result.discovery.profile ?? "(not found)"}`,
|
|
318
|
+
];
|
|
319
|
+
if (result.receipt) {
|
|
320
|
+
lines.push(`Fingerprint: ${result.receipt.profile.fingerprint}`);
|
|
321
|
+
lines.push(`Diff: ${result.receipt.diff.status}`);
|
|
322
|
+
for (const resource of result.receipt.resources) {
|
|
323
|
+
lines.push(
|
|
324
|
+
` ${resource.disposition} ${resource.kind} ${resource.id}${resource.pendingIndexing ? " (indexing pending)" : ""}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (const issue of result.diagnostics) {
|
|
329
|
+
lines.push(
|
|
330
|
+
`${issue.severity === "error" ? "Error" : "Warning"} ${issue.code}: ${issue.message} ${issue.remediation}`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
return lines.join("\n");
|
|
334
|
+
}
|