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.
@@ -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 (!sawMcpDeclaration && models.size === 0) {
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 isNornHttpConfig(value.http) && isKnownSection(value.sql) && isKnownSection(value.mcp) && isNornContractsConfig(value.contracts) && isNornAgentsConfig(value.agents) && isNornKnowledgeConfig(value.knowledge);
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": "4.6.2",
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,