norn-cli 4.6.2 → 5.0.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 +16 -0
- package/dist/cli.js +796 -505
- package/dist/knowledgeIndexWorker.js +126 -8
- package/package.json +1 -1
- package/schemas/norn.config.schema.json +0 -126
|
@@ -7888,6 +7888,7 @@ function parseNornAgentFile(content, sourcePath) {
|
|
|
7888
7888
|
const models = /* @__PURE__ */ new Map();
|
|
7889
7889
|
const mcpServers = /* @__PURE__ */ new Map();
|
|
7890
7890
|
const knowledgeBases = /* @__PURE__ */ new Map();
|
|
7891
|
+
const databases = /* @__PURE__ */ new Map();
|
|
7891
7892
|
const agents = [];
|
|
7892
7893
|
const errors = [];
|
|
7893
7894
|
const agentNames = /* @__PURE__ */ new Map();
|
|
@@ -7895,8 +7896,10 @@ function parseNornAgentFile(content, sourcePath) {
|
|
|
7895
7896
|
let pendingModel;
|
|
7896
7897
|
let pendingMcp;
|
|
7897
7898
|
let pendingKnowledge;
|
|
7899
|
+
let pendingDatabase;
|
|
7898
7900
|
let sawAgent = false;
|
|
7899
7901
|
let sawMcpDeclaration = false;
|
|
7902
|
+
let sawDatabaseDeclaration = false;
|
|
7900
7903
|
const reportBareMcpAlias = (mcp) => {
|
|
7901
7904
|
errors.push(parseError(
|
|
7902
7905
|
mcp.lineNumber,
|
|
@@ -8051,6 +8054,36 @@ end mcp
|
|
|
8051
8054
|
sourcePath
|
|
8052
8055
|
});
|
|
8053
8056
|
};
|
|
8057
|
+
const finishDatabase = (lineNumber) => {
|
|
8058
|
+
if (!pendingDatabase) {
|
|
8059
|
+
errors.push(parseError(lineNumber, "Unexpected 'end database' without a matching database block.", "agent-database-end-unexpected"));
|
|
8060
|
+
return;
|
|
8061
|
+
}
|
|
8062
|
+
const database = pendingDatabase;
|
|
8063
|
+
pendingDatabase = void 0;
|
|
8064
|
+
for (const required of ["adapter", "connectionString"]) {
|
|
8065
|
+
if (!database.values[required]) {
|
|
8066
|
+
errors.push(parseError(database.lineNumber, `Database '${database.alias}' requires ${required}.`, `agent-database-${required}-missing`));
|
|
8067
|
+
database.invalid = true;
|
|
8068
|
+
}
|
|
8069
|
+
}
|
|
8070
|
+
const access = database.values.access?.value.toLowerCase() ?? "read";
|
|
8071
|
+
if (access !== "read" && access !== "full") {
|
|
8072
|
+
errors.push(parseError(database.values.access.lineNumber, `Database '${database.alias}' access must be read or full.`, "agent-database-access-invalid"));
|
|
8073
|
+
database.invalid = true;
|
|
8074
|
+
}
|
|
8075
|
+
if (!database.invalid) {
|
|
8076
|
+
databases.set(database.alias.toLowerCase(), {
|
|
8077
|
+
alias: database.alias,
|
|
8078
|
+
adapter: database.values.adapter.value,
|
|
8079
|
+
connectionStringTemplate: database.values.connectionString.value,
|
|
8080
|
+
access,
|
|
8081
|
+
lineNumber: database.lineNumber,
|
|
8082
|
+
endLineNumber: lineNumber,
|
|
8083
|
+
sourcePath
|
|
8084
|
+
});
|
|
8085
|
+
}
|
|
8086
|
+
};
|
|
8054
8087
|
const finishModel = (lineNumber) => {
|
|
8055
8088
|
if (!pendingModel) {
|
|
8056
8089
|
errors.push(parseError(lineNumber, "Unexpected 'end model' without a matching model block.", "agent-model-end-unexpected"));
|
|
@@ -8185,8 +8218,10 @@ end mcp
|
|
|
8185
8218
|
...pending.grantedMcpServers.length > 0 ? { grantedMcpServers: pending.grantedMcpServers } : {},
|
|
8186
8219
|
callableAgents: pending.callableAgents,
|
|
8187
8220
|
...pending.knowledge.length > 0 ? { knowledge: pending.knowledge } : {},
|
|
8221
|
+
...pending.databases.length > 0 ? { grantedDatabases: pending.databases } : {},
|
|
8188
8222
|
mcpServers,
|
|
8189
8223
|
knowledgeBases,
|
|
8224
|
+
databases,
|
|
8190
8225
|
accepts: pending.accepts,
|
|
8191
8226
|
returns: pending.returns,
|
|
8192
8227
|
maxTokens: pending.maxTokens?.value,
|
|
@@ -8207,6 +8242,44 @@ end mcp
|
|
|
8207
8242
|
const rawLine = lines[index];
|
|
8208
8243
|
const withoutComment = stripInlineComment(rawLine);
|
|
8209
8244
|
const trimmed = withoutComment.trim();
|
|
8245
|
+
if (pendingDatabase) {
|
|
8246
|
+
if (!trimmed) {
|
|
8247
|
+
continue;
|
|
8248
|
+
}
|
|
8249
|
+
if (/^end\s+database$/i.test(trimmed)) {
|
|
8250
|
+
finishDatabase(index);
|
|
8251
|
+
continue;
|
|
8252
|
+
}
|
|
8253
|
+
const directive = trimmed.match(/^(adapter|connectionString|access)\s+(.+)$/i);
|
|
8254
|
+
if (directive) {
|
|
8255
|
+
const key = ["adapter", "connectionString", "access"].find((value) => value.toLowerCase() === directive[1].toLowerCase());
|
|
8256
|
+
if (pendingDatabase.values[key]) {
|
|
8257
|
+
errors.push(parseError(index, `Database '${pendingDatabase.alias}' declares ${key} more than once.`, "agent-database-directive-duplicate"));
|
|
8258
|
+
pendingDatabase.invalid = true;
|
|
8259
|
+
} else {
|
|
8260
|
+
pendingDatabase.values[key] = { value: normalizeDirectiveValue(directive[2]), lineNumber: index };
|
|
8261
|
+
}
|
|
8262
|
+
continue;
|
|
8263
|
+
}
|
|
8264
|
+
if (/^command\b/i.test(trimmed)) {
|
|
8265
|
+
errors.push(parseError(
|
|
8266
|
+
index,
|
|
8267
|
+
`Database '${pendingDatabase.alias}' uses a script path in adapter; remove command and set adapter to ./path/to/adapter.js.`,
|
|
8268
|
+
"agent-database-command-removed"
|
|
8269
|
+
));
|
|
8270
|
+
pendingDatabase.invalid = true;
|
|
8271
|
+
continue;
|
|
8272
|
+
}
|
|
8273
|
+
if (/^(agent|model|mcp|knowledge|database)\b/i.test(trimmed)) {
|
|
8274
|
+
errors.push(parseError(pendingDatabase.lineNumber, `Database '${pendingDatabase.alias}' is missing 'end database'.`, "agent-database-end-missing"));
|
|
8275
|
+
pendingDatabase = void 0;
|
|
8276
|
+
index--;
|
|
8277
|
+
continue;
|
|
8278
|
+
}
|
|
8279
|
+
errors.push(parseError(index, `Unknown directive in database '${pendingDatabase.alias}': '${trimmed}'.`, "agent-database-directive-unknown"));
|
|
8280
|
+
pendingDatabase.invalid = true;
|
|
8281
|
+
continue;
|
|
8282
|
+
}
|
|
8210
8283
|
if (pendingKnowledge) {
|
|
8211
8284
|
if (!trimmed) {
|
|
8212
8285
|
continue;
|
|
@@ -8259,7 +8332,7 @@ end mcp
|
|
|
8259
8332
|
}
|
|
8260
8333
|
continue;
|
|
8261
8334
|
}
|
|
8262
|
-
if (/^(agent|model|mcp|knowledge)\b/i.test(trimmed)) {
|
|
8335
|
+
if (/^(agent|model|mcp|knowledge|database)\b/i.test(trimmed)) {
|
|
8263
8336
|
errors.push(parseError(
|
|
8264
8337
|
pendingKnowledge.lineNumber,
|
|
8265
8338
|
`Knowledge '${pendingKnowledge.alias}' is missing 'end knowledge'.`,
|
|
@@ -8357,7 +8430,7 @@ end mcp
|
|
|
8357
8430
|
}
|
|
8358
8431
|
if (/^mcp\b/i.test(trimmed)) {
|
|
8359
8432
|
errors.push(parseError(index, `Nested mcp blocks are not allowed (inside '${pendingMcp.alias}').`, "agent-mcp-nested"));
|
|
8360
|
-
} else if (/^(agent|model|knowledge)\b/i.test(trimmed)) {
|
|
8433
|
+
} else if (/^(agent|model|knowledge|database)\b/i.test(trimmed)) {
|
|
8361
8434
|
errors.push(parseError(pendingMcp.lineNumber, `MCP server '${pendingMcp.alias}' is missing 'end mcp'.`, "agent-mcp-unterminated"));
|
|
8362
8435
|
pendingMcp = void 0;
|
|
8363
8436
|
index--;
|
|
@@ -8408,7 +8481,7 @@ end mcp
|
|
|
8408
8481
|
}
|
|
8409
8482
|
if (/^model\b/i.test(trimmed)) {
|
|
8410
8483
|
errors.push(parseError(index, `Nested model blocks are not allowed (inside '${pendingModel.alias}').`, "agent-model-nested"));
|
|
8411
|
-
} else if (/^(agent|knowledge)\b/i.test(trimmed)) {
|
|
8484
|
+
} else if (/^(agent|knowledge|database|mcp)\b/i.test(trimmed)) {
|
|
8412
8485
|
errors.push(parseError(pendingModel.lineNumber, `Model '${pendingModel.alias}' is missing 'end model'.`, "agent-model-end-missing"));
|
|
8413
8486
|
pendingModel = void 0;
|
|
8414
8487
|
index--;
|
|
@@ -8517,6 +8590,20 @@ end model`,
|
|
|
8517
8590
|
};
|
|
8518
8591
|
continue;
|
|
8519
8592
|
}
|
|
8593
|
+
const databaseMatch = trimmed.match(new RegExp(`^database\\s+(${IDENTIFIER})$`, "i"));
|
|
8594
|
+
if (databaseMatch) {
|
|
8595
|
+
sawDatabaseDeclaration = true;
|
|
8596
|
+
const alias = databaseMatch[1];
|
|
8597
|
+
const invalid = sawAgent || databases.has(alias.toLowerCase());
|
|
8598
|
+
if (sawAgent) {
|
|
8599
|
+
errors.push(parseError(index, "Database blocks must be declared before the first agent block.", "agent-database-not-at-top"));
|
|
8600
|
+
}
|
|
8601
|
+
if (databases.has(alias.toLowerCase())) {
|
|
8602
|
+
errors.push(parseError(index, `Duplicate database alias '${alias}'.`, "agent-database-duplicate"));
|
|
8603
|
+
}
|
|
8604
|
+
pendingDatabase = { alias, lineNumber: index, values: {}, invalid };
|
|
8605
|
+
continue;
|
|
8606
|
+
}
|
|
8520
8607
|
if (/^end\s+mcp$/i.test(trimmed)) {
|
|
8521
8608
|
finishMcp(index);
|
|
8522
8609
|
continue;
|
|
@@ -8525,6 +8612,10 @@ end model`,
|
|
|
8525
8612
|
finishKnowledge(index);
|
|
8526
8613
|
continue;
|
|
8527
8614
|
}
|
|
8615
|
+
if (/^end\s+database$/i.test(trimmed)) {
|
|
8616
|
+
finishDatabase(index);
|
|
8617
|
+
continue;
|
|
8618
|
+
}
|
|
8528
8619
|
const agentMatch = trimmed.match(new RegExp(`^agent\\s+(${IDENTIFIER})$`, "i"));
|
|
8529
8620
|
if (agentMatch) {
|
|
8530
8621
|
sawAgent = true;
|
|
@@ -8544,6 +8635,7 @@ end model`,
|
|
|
8544
8635
|
tools: [],
|
|
8545
8636
|
grantedMcpServers: [],
|
|
8546
8637
|
knowledge: [],
|
|
8638
|
+
databases: [],
|
|
8547
8639
|
callableAgents: [],
|
|
8548
8640
|
invalid
|
|
8549
8641
|
};
|
|
@@ -8559,6 +8651,8 @@ end model`,
|
|
|
8559
8651
|
errors.push(parseError(index, "Malformed MCP declaration. Expected: mcp <Alias>, then transport and its directives, then end mcp.", "agent-mcp-malformed"));
|
|
8560
8652
|
} else if (/^knowledge\b/i.test(trimmed)) {
|
|
8561
8653
|
errors.push(parseError(index, "Malformed knowledge declaration. Expected: knowledge <Alias>, then source/exclude directives, then end knowledge.", "agent-knowledge-malformed"));
|
|
8654
|
+
} else if (/^database\b/i.test(trimmed)) {
|
|
8655
|
+
errors.push(parseError(index, "Malformed database declaration. Expected: database <Alias>, then adapter/connectionString/access directives, then end database.", "agent-database-malformed"));
|
|
8562
8656
|
} else if (/^agent\b/i.test(trimmed)) {
|
|
8563
8657
|
errors.push(parseError(index, "Malformed agent declaration. Expected: agent <Name>.", "agent-declaration-malformed"));
|
|
8564
8658
|
} else {
|
|
@@ -8632,6 +8726,27 @@ end model`,
|
|
|
8632
8726
|
}
|
|
8633
8727
|
continue;
|
|
8634
8728
|
}
|
|
8729
|
+
const databaseGrantMatch = trimmed.match(/^database(?:\s+(.*))?$/i);
|
|
8730
|
+
if (databaseGrantMatch) {
|
|
8731
|
+
const aliases = (databaseGrantMatch[1] ?? "").split(",").map((value) => value.trim());
|
|
8732
|
+
if (aliases.some((alias) => !new RegExp(`^${IDENTIFIER}$`).test(alias))) {
|
|
8733
|
+
errors.push(parseError(index, `Invalid database grant '${trimmed}'. Expected: database <Alias>[, <Alias>\u2026].`, "agent-database-grant-invalid"));
|
|
8734
|
+
pending.invalid = true;
|
|
8735
|
+
continue;
|
|
8736
|
+
}
|
|
8737
|
+
for (const alias of aliases) {
|
|
8738
|
+
if (pending.databases.some((grant) => grant.alias.toLowerCase() === alias.toLowerCase())) {
|
|
8739
|
+
errors.push(parseError(index, `Agent '${pending.name}' grants database '${alias}' more than once.`, "agent-database-grant-duplicate"));
|
|
8740
|
+
pending.invalid = true;
|
|
8741
|
+
} else if (!databases.has(alias.toLowerCase())) {
|
|
8742
|
+
errors.push(parseError(index, `Unknown database alias '${alias}' in agent '${pending.name}'.`, "agent-database-alias-unknown"));
|
|
8743
|
+
pending.invalid = true;
|
|
8744
|
+
} else {
|
|
8745
|
+
pending.databases.push({ alias, lineNumber: index });
|
|
8746
|
+
}
|
|
8747
|
+
}
|
|
8748
|
+
continue;
|
|
8749
|
+
}
|
|
8635
8750
|
const returnsMatch = trimmed.match(/^returns\s+(.+)$/i);
|
|
8636
8751
|
if (returnsMatch) {
|
|
8637
8752
|
if (pending.returns) {
|
|
@@ -8830,7 +8945,7 @@ end model`,
|
|
|
8830
8945
|
errors.push(parseError(index, 'Malformed describe block. Expected: describe "..." (multi-line allowed) or describe file <path>.', "agent-describe-malformed"));
|
|
8831
8946
|
} else if (/^system\b/i.test(trimmed)) {
|
|
8832
8947
|
errors.push(parseError(index, 'Malformed system prompt. Expected: system "..." (multi-line allowed) or system file <path>.', "agent-system-malformed"));
|
|
8833
|
-
} else if (new RegExp(`^(accepts|returns|tools|agents|model|knowledge|${Object.keys(LIMIT_DIRECTIVES).join("|")})\\b`, "i").test(trimmed)) {
|
|
8948
|
+
} else if (new RegExp(`^(accepts|returns|tools|agents|model|knowledge|database|${Object.keys(LIMIT_DIRECTIVES).join("|")})\\b`, "i").test(trimmed)) {
|
|
8834
8949
|
errors.push(parseError(index, `Malformed agent directive: '${trimmed}'.`, "agent-directive-malformed"));
|
|
8835
8950
|
} else if (/^agent\b/i.test(trimmed)) {
|
|
8836
8951
|
errors.push(parseError(index, `Nested agent blocks are not allowed (inside '${pending.name}').`, "agent-nested"));
|
|
@@ -8859,10 +8974,13 @@ end model`,
|
|
|
8859
8974
|
"agent-knowledge-end-missing"
|
|
8860
8975
|
));
|
|
8861
8976
|
}
|
|
8862
|
-
if (
|
|
8977
|
+
if (pendingDatabase) {
|
|
8978
|
+
errors.push(parseError(pendingDatabase.lineNumber, `Database '${pendingDatabase.alias}' is missing 'end database'.`, "agent-database-end-missing"));
|
|
8979
|
+
}
|
|
8980
|
+
if (!sawMcpDeclaration && !sawDatabaseDeclaration && models.size === 0) {
|
|
8863
8981
|
errors.push(parseError(0, "An agent sidecar must declare at least one model block.", "agent-model-required"));
|
|
8864
8982
|
}
|
|
8865
|
-
if (!sawMcpDeclaration && agentNames.size === 0) {
|
|
8983
|
+
if (!sawMcpDeclaration && !sawDatabaseDeclaration && agentNames.size === 0) {
|
|
8866
8984
|
errors.push(parseError(0, "An agent sidecar must declare at least one agent.", "agent-required"));
|
|
8867
8985
|
}
|
|
8868
8986
|
const localScope = new Map(agents.map((agent) => [agent.name.toLowerCase(), agent]));
|
|
@@ -8870,7 +8988,7 @@ end model`,
|
|
|
8870
8988
|
const { sourcePath: _sourcePath, ...parseValidationError } = validationError;
|
|
8871
8989
|
errors.push(parseValidationError);
|
|
8872
8990
|
}
|
|
8873
|
-
return { sourcePath, models, mcpServers, knowledgeBases, agents, errors };
|
|
8991
|
+
return { sourcePath, models, mcpServers, knowledgeBases, databases, agents, errors };
|
|
8874
8992
|
}
|
|
8875
8993
|
|
|
8876
8994
|
// src/knowledge/knowledgeBuilder.ts
|
|
@@ -9741,7 +9859,7 @@ function isNornProjectConfig(value) {
|
|
|
9741
9859
|
if (value.version !== 1) {
|
|
9742
9860
|
return false;
|
|
9743
9861
|
}
|
|
9744
|
-
return
|
|
9862
|
+
return value.sql === void 0 && isNornHttpConfig(value.http) && isKnownSection(value.mcp) && isNornContractsConfig(value.contracts) && isNornAgentsConfig(value.agents) && isNornKnowledgeConfig(value.knowledge);
|
|
9745
9863
|
}
|
|
9746
9864
|
function loadNornConfig(startPath) {
|
|
9747
9865
|
const filePath = findNearestConfigFile(startPath, NORN_CONFIG_FILENAME);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "norn-cli",
|
|
3
3
|
"displayName": "Norn",
|
|
4
4
|
"description": "Build version-controlled agentic flows in VS Code and run them from the CLI or CI.",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "5.0.0",
|
|
6
6
|
"publisher": "Norn-PeterKrustanov",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Peter Krastanov"
|
|
@@ -18,29 +18,6 @@
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
|
-
{
|
|
22
|
-
"label": "Norn config with SQL",
|
|
23
|
-
"description": "Create a Norn config with an editable SQL connection and custom adapter example.",
|
|
24
|
-
"body": {
|
|
25
|
-
"version": 1,
|
|
26
|
-
"sql": {
|
|
27
|
-
"connections": {
|
|
28
|
-
"${1:appDb}": {
|
|
29
|
-
"adapter": "${2:postgres}",
|
|
30
|
-
"profile": "${3:appDb}"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"adapters": {
|
|
34
|
-
"${4:custom-adapter}": {
|
|
35
|
-
"command": [
|
|
36
|
-
"${5:node}",
|
|
37
|
-
"${6:./tools/adapters/my-adapter.js}"
|
|
38
|
-
]
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
21
|
{
|
|
45
22
|
"label": "Norn config with MCP",
|
|
46
23
|
"description": "Create a Norn config with editable local and remote MCP server examples.",
|
|
@@ -128,35 +105,6 @@
|
|
|
128
105
|
}
|
|
129
106
|
]
|
|
130
107
|
},
|
|
131
|
-
"sql": {
|
|
132
|
-
"allOf": [
|
|
133
|
-
{
|
|
134
|
-
"$ref": "#/definitions/sqlSection"
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
"defaultSnippets": [
|
|
138
|
-
{
|
|
139
|
-
"label": "SQL section definition",
|
|
140
|
-
"description": "Insert editable SQL connections and custom adapter config.",
|
|
141
|
-
"body": {
|
|
142
|
-
"connections": {
|
|
143
|
-
"${1:appDb}": {
|
|
144
|
-
"adapter": "${2:postgres}",
|
|
145
|
-
"profile": "${3:appDb}"
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
"adapters": {
|
|
149
|
-
"${4:custom-adapter}": {
|
|
150
|
-
"command": [
|
|
151
|
-
"${5:node}",
|
|
152
|
-
"${6:./tools/adapters/my-adapter.js}"
|
|
153
|
-
]
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
]
|
|
159
|
-
},
|
|
160
108
|
"mcp": {
|
|
161
109
|
"allOf": [
|
|
162
110
|
{
|
|
@@ -332,80 +280,6 @@
|
|
|
332
280
|
}
|
|
333
281
|
}
|
|
334
282
|
},
|
|
335
|
-
"sqlSection": {
|
|
336
|
-
"type": "object",
|
|
337
|
-
"additionalProperties": false,
|
|
338
|
-
"description": "SQL connections and optional custom adapter commands available to .nornsql files.",
|
|
339
|
-
"anyOf": [
|
|
340
|
-
{
|
|
341
|
-
"required": [
|
|
342
|
-
"connections"
|
|
343
|
-
]
|
|
344
|
-
},
|
|
345
|
-
{
|
|
346
|
-
"required": [
|
|
347
|
-
"adapters"
|
|
348
|
-
]
|
|
349
|
-
}
|
|
350
|
-
],
|
|
351
|
-
"properties": {
|
|
352
|
-
"_comment": {
|
|
353
|
-
"$ref": "#/definitions/comment",
|
|
354
|
-
"description": "Optional human-readable guidance ignored by Norn at runtime."
|
|
355
|
-
},
|
|
356
|
-
"connections": {
|
|
357
|
-
"type": "object",
|
|
358
|
-
"description": "Named SQL connection aliases referenced from .nornsql files.",
|
|
359
|
-
"minProperties": 1,
|
|
360
|
-
"additionalProperties": {
|
|
361
|
-
"type": "object",
|
|
362
|
-
"additionalProperties": false,
|
|
363
|
-
"required": [
|
|
364
|
-
"adapter",
|
|
365
|
-
"profile"
|
|
366
|
-
],
|
|
367
|
-
"properties": {
|
|
368
|
-
"adapter": {
|
|
369
|
-
"type": "string",
|
|
370
|
-
"description": "Built-in adapter ID or a custom adapter key from sql.adapters.",
|
|
371
|
-
"examples": [
|
|
372
|
-
"postgres",
|
|
373
|
-
"sqlserver",
|
|
374
|
-
"sqlserver-windows"
|
|
375
|
-
]
|
|
376
|
-
},
|
|
377
|
-
"profile": {
|
|
378
|
-
"type": "string",
|
|
379
|
-
"description": "Connection profile prefix resolved from .nornenv connectionString entries."
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
"adapters": {
|
|
385
|
-
"type": "object",
|
|
386
|
-
"description": "Custom SQL adapter commands. Built-in adapters do not need entries here.",
|
|
387
|
-
"minProperties": 1,
|
|
388
|
-
"additionalProperties": {
|
|
389
|
-
"type": "object",
|
|
390
|
-
"additionalProperties": false,
|
|
391
|
-
"required": [
|
|
392
|
-
"command"
|
|
393
|
-
],
|
|
394
|
-
"properties": {
|
|
395
|
-
"command": {
|
|
396
|
-
"type": "array",
|
|
397
|
-
"description": "Command and arguments used to start the custom SQL adapter. Relative paths resolve from norn.config.json.",
|
|
398
|
-
"minItems": 1,
|
|
399
|
-
"items": {
|
|
400
|
-
"type": "string",
|
|
401
|
-
"minLength": 1
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
283
|
"contractsSection": {
|
|
410
284
|
"type": "object",
|
|
411
285
|
"additionalProperties": false,
|