@rahmantech/knl-view 0.1.0 → 0.2.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.
Files changed (96) hide show
  1. package/README.md +117 -32
  2. package/bin/knl-view.js +435 -58
  3. package/dist/assets/{abnfDiagram-WEYOIA3C-B1gaBaSI.js → abnfDiagram-WEYOIA3C-hoSbsMOz.js} +1 -1
  4. package/dist/assets/{arc-NZ9uRaCl.js → arc-g87Np6E5.js} +1 -1
  5. package/dist/assets/{architecture-WOLXFQ4H-8MalLwTd.js → architecture-WOLXFQ4H-DOIq-0IA.js} +1 -1
  6. package/dist/assets/{architectureDiagram-ZHVYKT53-eZ8i5cl4.js → architectureDiagram-ZHVYKT53-Dxwe8n7m.js} +1 -1
  7. package/dist/assets/{blockDiagram-RJAE7QA3-BavOE8BM.js → blockDiagram-RJAE7QA3-BnGng59Z.js} +1 -1
  8. package/dist/assets/{c4Diagram-XDPPPE54-DqMaTAzx.js → c4Diagram-XDPPPE54-C5LjqIIo.js} +1 -1
  9. package/dist/assets/channel-BVWkRFBw.js +1 -0
  10. package/dist/assets/{chunk-2E7WHVMD-B1Hj2BjJ.js → chunk-2E7WHVMD-BJ1dyGLq.js} +1 -1
  11. package/dist/assets/{chunk-4EMNKR2T-HDscrCiq.js → chunk-4EMNKR2T-DXF02Sgw.js} +1 -1
  12. package/dist/assets/{chunk-55XCU76K-QR_ARj8I.js → chunk-55XCU76K-CM1akAbu.js} +1 -1
  13. package/dist/assets/{chunk-7S6XSTFO-B0p3o547.js → chunk-7S6XSTFO-BOUjqldL.js} +1 -1
  14. package/dist/assets/{chunk-AQDTSKZB-CCxwNyyA.js → chunk-AQDTSKZB-C4isdNLA.js} +1 -1
  15. package/dist/assets/{chunk-DSNLSHJZ-BM5PBFmz.js → chunk-DSNLSHJZ-CbOTkSVM.js} +1 -1
  16. package/dist/assets/{chunk-HAGFOTED-UCDVFuN_.js → chunk-HAGFOTED-CRfnketR.js} +1 -1
  17. package/dist/assets/{chunk-HNE4Q36P-C_1sakp2.js → chunk-HNE4Q36P-rxlpGUU8.js} +1 -1
  18. package/dist/assets/{chunk-NQOKRVIF-b2D1Z4oK.js → chunk-NQOKRVIF-BOYCgTmV.js} +1 -1
  19. package/dist/assets/{chunk-UHW7PFBP-CJs0VwBY.js → chunk-UHW7PFBP-Cnogp1Fb.js} +1 -1
  20. package/dist/assets/{chunk-VQ5W62QI-DQxjgE4_.js → chunk-VQ5W62QI-Cu1WY5Uo.js} +1 -1
  21. package/dist/assets/{chunk-WQQL4CDK-DcjPeAfQ.js → chunk-WQQL4CDK-BJc3FPE2.js} +1 -1
  22. package/dist/assets/{chunk-XXDRQBXY-glA0kB5s.js → chunk-XXDRQBXY-BlPj4H7s.js} +1 -1
  23. package/dist/assets/{chunk-YZO67HFG-DJqACL5V.js → chunk-YZO67HFG-V85H31x4.js} +1 -1
  24. package/dist/assets/{chunk-ZAUX2LTX-CWfaYtwb.js → chunk-ZAUX2LTX-C3XKXW09.js} +1 -1
  25. package/dist/assets/{chunk-ZCHMQSOR-BGX73Hho.js → chunk-ZCHMQSOR-B0mmpEgW.js} +1 -1
  26. package/dist/assets/{classDiagram-v2-4MYWR3UA-BpYXPz-e.js → classDiagram-v2-4MYWR3UA-oMd7YaHP.js} +1 -1
  27. package/dist/assets/{cose-bilkent-JH36ORCC-e1quKm0Q.js → cose-bilkent-JH36ORCC-J0kSVsDf.js} +1 -1
  28. package/dist/assets/{cynefin-EF2NZ3EQ-hb3oUmFO.js → cynefin-EF2NZ3EQ-DK2TNhel.js} +1 -1
  29. package/dist/assets/{cynefinDiagram-WF24UDRP-CnMQvLqi.js → cynefinDiagram-WF24UDRP-DDvKgieA.js} +1 -1
  30. package/dist/assets/{dagre-NK4GIZVX-BH_Y8y3F.js → dagre-NK4GIZVX-Ca2Fe-i-.js} +1 -1
  31. package/dist/assets/{diagram-CBK3MWTC-Dh79VetZ.js → diagram-CBK3MWTC-L6dmeQ2V.js} +1 -1
  32. package/dist/assets/{diagram-I5XIIV2D-D3u-o1RV.js → diagram-I5XIIV2D-DhG8R-v5.js} +1 -1
  33. package/dist/assets/{diagram-ODIL2UCW-Bfuo-OKS.js → diagram-ODIL2UCW-BpYYbYBT.js} +1 -1
  34. package/dist/assets/{diagram-PFBEJUXE-CwHasIbs.js → diagram-PFBEJUXE-DKQpHSsC.js} +1 -1
  35. package/dist/assets/{diagram-RJA3I4VV-D8ttmuE8.js → diagram-RJA3I4VV-DP7Dsnya.js} +1 -1
  36. package/dist/assets/{diagram-TYUNFSNR-CzC-8RZ0.js → diagram-TYUNFSNR-CfSFewUS.js} +1 -1
  37. package/dist/assets/{dist-CUJYGnnI.js → dist-CQNjnrld.js} +1 -1
  38. package/dist/assets/{ebnfDiagram-V4P3TTPT-9AAhU7In.js → ebnfDiagram-V4P3TTPT-DE3MZoJO.js} +1 -1
  39. package/dist/assets/{elk-QONTBCTQ-BgvnD1v_.js → elk-QONTBCTQ-p_aAQPHU.js} +1 -1
  40. package/dist/assets/{erDiagram-KYQIGQAA-RLtbiLlP.js → erDiagram-KYQIGQAA-DZO3vQrk.js} +1 -1
  41. package/dist/assets/{eventmodeling-K75KTNOO-DVY22vNZ.js → eventmodeling-K75KTNOO-BII9EPl5.js} +1 -1
  42. package/dist/assets/flowDiagram-IUGLLWBU-D9uPno39.js +1 -0
  43. package/dist/assets/{ganttDiagram-R7GPNTJY-hHiBGa9W.js → ganttDiagram-R7GPNTJY-BKiH3v2r.js} +1 -1
  44. package/dist/assets/{gitGraph-VSP46ZUC-Cvxay1wh.js → gitGraph-VSP46ZUC-oHL6cr73.js} +1 -1
  45. package/dist/assets/{gitGraphDiagram-PDYL7XHY-AOhlpSqg.js → gitGraphDiagram-PDYL7XHY-CqPVIEeo.js} +1 -1
  46. package/dist/assets/{index-CBLNQvK3.js → index-CSpirV16.js} +3 -3
  47. package/dist/assets/{info-OHQRW6UA-CJ8EUO1m.js → info-OHQRW6UA-Drrbnhpd.js} +1 -1
  48. package/dist/assets/{infoDiagram-MVDRKAX5-DOQu7oYI.js → infoDiagram-MVDRKAX5-DnQQFMAd.js} +1 -1
  49. package/dist/assets/{ishikawaDiagram-HPEHV2GE-DcVMvGmx.js → ishikawaDiagram-HPEHV2GE-DIpQlxRh.js} +1 -1
  50. package/dist/assets/{journeyDiagram-BOSHW7O7-luMeqEqz.js → journeyDiagram-BOSHW7O7-EBR9I5h1.js} +1 -1
  51. package/dist/assets/{kanban-definition-FNSA4HC5-KTZbmrW2.js → kanban-definition-FNSA4HC5-CdvNsjik.js} +1 -1
  52. package/dist/assets/{line-CmPHiOVm.js → line-CDLzgxDc.js} +1 -1
  53. package/dist/assets/{linear-DHYlKXBA.js → linear-DfXKql9u.js} +1 -1
  54. package/dist/assets/{mermaid-parser.core-BRDI-WhC.js → mermaid-parser.core-DnXGphT2.js} +3 -3
  55. package/dist/assets/{mermaid.core-DPTRaYLy.js → mermaid.core-h6fiCaSZ.js} +4 -4
  56. package/dist/assets/{mindmap-definition-6OY57UG3-70EN5Kw_.js → mindmap-definition-6OY57UG3-BiNfio-j.js} +1 -1
  57. package/dist/assets/{packet-JDAUHWVQ-CXWz83oS.js → packet-JDAUHWVQ-D76byv1S.js} +1 -1
  58. package/dist/assets/{pegDiagram-ECIUJNBX-z5UXKmp4.js → pegDiagram-ECIUJNBX-Drs8Pxdt.js} +1 -1
  59. package/dist/assets/{pie-XZMESJXO-rWI1HIQ7.js → pie-XZMESJXO-D_83z1lM.js} +1 -1
  60. package/dist/assets/{pieDiagram-GNMPY74T-Bh4Kv3NQ.js → pieDiagram-GNMPY74T-wvzIphSf.js} +1 -1
  61. package/dist/assets/{quadrantDiagram-D57K4MC3-wGM4Ckig.js → quadrantDiagram-D57K4MC3-B7EfMRWb.js} +1 -1
  62. package/dist/assets/{radar-ABXABTNO-CIJ92p5N.js → radar-ABXABTNO-Bs6dVdAR.js} +1 -1
  63. package/dist/assets/{railroad-I3PHUGI6-8bxua-Hf.js → railroad-I3PHUGI6-lKaN4miw.js} +1 -1
  64. package/dist/assets/{railroad-abnf-LNEFI6M7-B9KVI10m.js → railroad-abnf-LNEFI6M7-Bhh16b7Z.js} +1 -1
  65. package/dist/assets/{railroad-ebnf-DWYD2UWJ-Bt73rnyJ.js → railroad-ebnf-DWYD2UWJ-m6ybW-Y_.js} +1 -1
  66. package/dist/assets/{railroad-peg-JR7BVNR7-Cie82UPT.js → railroad-peg-JR7BVNR7-DNn72e7r.js} +1 -1
  67. package/dist/assets/{railroadDiagram-Y72NRBL7-C1nmi_tl.js → railroadDiagram-Y72NRBL7-DNeop1iZ.js} +1 -1
  68. package/dist/assets/{requirementDiagram-EQYSW5RA-BSianW40.js → requirementDiagram-EQYSW5RA-BVYZ2XYJ.js} +1 -1
  69. package/dist/assets/{sankeyDiagram-SY24JHO5-DIa11-h7.js → sankeyDiagram-SY24JHO5-B2MP51on.js} +1 -1
  70. package/dist/assets/{sequenceDiagram-BLEHNHQU-Df5rZKz2.js → sequenceDiagram-BLEHNHQU-BUvxstXG.js} +1 -1
  71. package/dist/assets/{src-dClg85lX.js → src-BrR0lqlG.js} +1 -1
  72. package/dist/assets/{stateDiagram-v2-A6IESKWZ-B15PTSvX.js → stateDiagram-v2-A6IESKWZ-Ba84OGKE.js} +1 -1
  73. package/dist/assets/{swimlanes-B3ENRKFQ-C9mgRCMi.js → swimlanes-B3ENRKFQ-BMSl2PNA.js} +1 -1
  74. package/dist/assets/swimlanesDiagram-AYQRV4AK-M1dmvq7m.js +8 -0
  75. package/dist/assets/{timeline-definition-JXYKGLGB-BDVf_mIv.js → timeline-definition-JXYKGLGB-DOdW-dxq.js} +1 -1
  76. package/dist/assets/{treeView-D4JQ5SDB-BbBK-25J.js → treeView-D4JQ5SDB-DZ9KQyn5.js} +1 -1
  77. package/dist/assets/{treemap-SAJKECNS-CEDX8apk.js → treemap-SAJKECNS-WEC5wtDV.js} +1 -1
  78. package/dist/assets/{usecaseDiagram-WPOLBNNX-D1QkwFF8.js → usecaseDiagram-WPOLBNNX-cldTztFL.js} +1 -1
  79. package/dist/assets/{vennDiagram-Q3Y3IDCW-5PeGZvEO.js → vennDiagram-Q3Y3IDCW-c41KySoY.js} +1 -1
  80. package/dist/assets/{wardley-7MLQ67FV-BatN94pO.js → wardley-7MLQ67FV-Bf9_aSSZ.js} +1 -1
  81. package/dist/assets/{wardleyDiagram-IVAIDKWA-DyCcDS3Y.js → wardleyDiagram-IVAIDKWA-BeKEvEBG.js} +1 -1
  82. package/dist/assets/{xychartDiagram-4URNHDU4-CbCXcRlg.js → xychartDiagram-4URNHDU4-tNkAxTxG.js} +1 -1
  83. package/dist/index.html +1 -1
  84. package/package.json +5 -1
  85. package/server/config.js +140 -14
  86. package/server/consensus.js +18 -13
  87. package/server/graph/parser.js +3 -2
  88. package/server/graph/sources.js +165 -24
  89. package/server/graph/syntax.js +196 -16
  90. package/server/index.js +124 -61
  91. package/server/install.js +350 -10
  92. package/server/models_registry.js +34 -9
  93. package/server/scaffold.js +99 -9
  94. package/dist/assets/channel-BZ2_TTTJ.js +0 -1
  95. package/dist/assets/flowDiagram-IUGLLWBU-l7L5DTb5.js +0 -1
  96. package/dist/assets/swimlanesDiagram-AYQRV4AK-BGFq4t7O.js +0 -8
package/bin/knl-view.js CHANGED
@@ -1,17 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs } from 'node:util'
2
+ import { parseArgs, promisify } from 'node:util'
3
3
  import path from 'node:path'
4
4
  import fs from 'node:fs'
5
5
  import readline from 'node:readline/promises'
6
6
  import { execFile, spawn } from 'node:child_process'
7
- import { promisify } from 'node:util'
8
7
  import os from 'node:os'
9
8
  import { fileURLToPath } from 'node:url'
10
9
  import { createKnowledgeServer, attachAnchorDB } from '../server/index.js'
11
- import { scaffoldBase, seedSkill, ensureGitignore } from '../server/scaffold.js'
10
+ import { scaffoldBase, seedSkill, ensureGitignore, seedAgentInstructions } from '../server/scaffold.js'
12
11
  import { buildGraphFromRoot } from '../server/graph/parser.js'
13
12
  import {
14
- claimId,
15
13
  deriveStatus,
16
14
  recordClaim,
17
15
  readClaimsIndex,
@@ -21,7 +19,7 @@ import {
21
19
  outboundConsensus,
22
20
  CONSENSUS_RELATIONS
23
21
  } from '../server/consensus.js'
24
- import { readProjectConfig, readGlobalConfig, projectSlug, anchordbClientOptions, resolveModel, resolveSettings, DEFAULT_MODEL } from '../server/config.js'
22
+ import { readProjectConfig, readGlobalConfig, projectSlug, anchordbClientOptions, resolveModel, resolveSettings, knlGlobalDir, globalConfigPath } from '../server/config.js'
25
23
  import { createAnchorWireClient, mirrorStillValid } from '../server/graph/anchordb_client.js'
26
24
  import { syncSourcesToAnchorDB, traceSources, purgeArchivedRecords } from '../server/graph/sources.js'
27
25
  import { readModelRegistry, registerModel, generateModelName, modelRegistryPath } from '../server/models_registry.js'
@@ -32,7 +30,15 @@ import {
32
30
  downloadFile,
33
31
  verifySha256,
34
32
  mergeGlobalAnchordb,
35
- detectContainerRuntime,
33
+ availableContainerRuntimes,
34
+ detectRunningDaemon,
35
+ inspectContainerStatus,
36
+ startContainer,
37
+ removeContainer,
38
+ detectGraphify,
39
+ checkDecisionSkillsStatus,
40
+ installDecisionSkills,
41
+ resolveAnchorPorts,
36
42
  GHCR_IMAGE,
37
43
  HTTP_PORT_DEFAULT,
38
44
  WIRE_PORT_DEFAULT,
@@ -91,6 +97,7 @@ Commands:
91
97
  "sources" model via the REST collector
92
98
  sources trace --target=<path> File lineage over the sources graph
93
99
  (--hops, --direction in|out|both)
100
+ setup Bootstrap project in one command (scaffold base, skill, and models)
94
101
 
95
102
  Options:
96
103
  --port, -p <number> HTTP port (default: 8085, or knl-config.json)
@@ -99,6 +106,7 @@ Options:
99
106
  --anchordb <url> AnchorWire URL for this session, overrides the
100
107
  "anchordb" block in knl-config.json
101
108
  --method <method> Method for "install anchordb": binary | container
109
+ --runtime <engine> Container runtime for "install anchordb": docker | podman
102
110
  --token <text> Auth token for "install anchordb" (default: random)
103
111
  --direction <dir> Lineage direction for "graph trace": in | out | both
104
112
  (default: both; see README "Two-way lineage")
@@ -133,6 +141,7 @@ try {
133
141
  host: { type: 'string' },
134
142
  anchordb: { type: 'string' },
135
143
  method: { type: 'string' },
144
+ runtime: { type: 'string' },
136
145
  token: { type: 'string' },
137
146
  target: { type: 'string' },
138
147
  direction: { type: 'string' },
@@ -191,6 +200,12 @@ if (values.anchordb !== undefined && !settings.anchordb) {
191
200
  fail('--anchordb must be a wire:// or tcp:// URL, example: --anchordb=wire://127.0.0.1:7448?token=xxx')
192
201
  }
193
202
 
203
+ // The "setup" command: one-shot bootstrap for the project
204
+ if (positionals?.[0] === 'setup') {
205
+ await runSetupCommand({ values, settings, projectConfig, globalConfig })
206
+ process.exit(0)
207
+ }
208
+
194
209
  // The "model" command: manage the role → AnchorDB model name map. No other
195
210
  // code ever guesses names; everything resolves through the role in this map.
196
211
  const modelCommand = positionals?.[0] === 'model' ? positionals?.[1] : null
@@ -280,6 +295,10 @@ if (values.init || values['seed-skill'] !== undefined) {
280
295
  process.exit(0)
281
296
  }
282
297
 
298
+ if (positionals && positionals.length > 0) {
299
+ fail(`Unknown command "${positionals[0]}".\n\nRun "knl-view --help" for available commands.`)
300
+ }
301
+
283
302
  const port = Number(settings.port)
284
303
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
285
304
  fail(`--port must be a number 1-65535, got "${settings.port}".`)
@@ -305,7 +324,9 @@ const server = createKnowledgeServer({
305
324
  port,
306
325
  anchordb: settings.anchordb,
307
326
  consensusModel: resolveModel(settings.models, 'consensus'),
308
- sourcesModel: resolveModel(settings.models, 'sources')
327
+ sourcesModel: resolveModel(settings.models, 'sources'),
328
+ include: settings.include,
329
+ exclude: settings.exclude
309
330
  })
310
331
 
311
332
  server.on('error', (err) => {
@@ -359,6 +380,8 @@ server.listen(port, settings.host, () => {
359
380
  } else {
360
381
  process.stdout.write(` anchordb failed: ${result.reason} (viewer runs local only)\n`)
361
382
  }
383
+ }).catch((err) => {
384
+ process.stdout.write(` anchordb error: ${err.message} (viewer runs local only)\n`)
362
385
  })
363
386
  }
364
387
  }
@@ -428,6 +451,271 @@ async function runSetup() {
428
451
  }
429
452
  }
430
453
 
454
+ async function runSetupCommand({ values, settings, projectConfig, globalConfig }) {
455
+ const root = path.resolve(process.cwd(), values.path || settings.path || '.knowledges')
456
+ const interactive = Boolean(process.stdin.isTTY) && !values.yes
457
+ const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null
458
+
459
+ try {
460
+ process.stdout.write('knl-view setup: bootstrapping project ...\n')
461
+
462
+ // Scaffold base
463
+ const actions = await scaffoldBase({
464
+ root,
465
+ structure: values.structure || 'standard',
466
+ folders: values.folders ? values.folders.split(',').map((s) => s.trim()).filter(Boolean) : []
467
+ })
468
+ const createdCount = actions.filter((a) => a.status === 'created').length
469
+ const relRoot = path.relative(process.cwd(), root) || root
470
+ process.stdout.write(` [1/5] knowledge base: ${relRoot} (${createdCount > 0 ? 'created' : 'already present'})\n`)
471
+
472
+ // Audit and seed skill
473
+ const skillPath = path.join(process.cwd(), '.agents', 'skills', 'knl-view', 'SKILL.md')
474
+ let skillPresent = false
475
+ try {
476
+ await fs.promises.stat(skillPath)
477
+ skillPresent = true
478
+ } catch {}
479
+
480
+ if (skillPresent) {
481
+ process.stdout.write(` [2/5] agent skill: ${skillPath} (already present)\n`)
482
+ } else {
483
+ let wantSkill = true
484
+ if (interactive) {
485
+ wantSkill = await askYesNo(rl, 'Install knl-view agent skill to .agents/skills/knl-view?', true)
486
+ }
487
+ if (wantSkill) {
488
+ const skillResult = await seedSkill({ scope: values.dir || 'project', cwd: process.cwd() })
489
+ process.stdout.write(` [2/5] agent skill: ${skillResult.path} (${skillResult.status})\n`)
490
+ } else {
491
+ process.stdout.write(` [2/5] agent skill: skipped by user\n`)
492
+ }
493
+ }
494
+
495
+ // Audit and seed decision-history skill
496
+ const home = process.env.HOME || process.cwd()
497
+ const decisionStatus = await checkDecisionSkillsStatus({ cwd: process.cwd(), home })
498
+ const statusLabel =
499
+ decisionStatus.status === 'not_installed'
500
+ ? 'not installed'
501
+ : decisionStatus.status === 'needs_update'
502
+ ? `update available (current: ${decisionStatus.currentVersion || 'unknown'}, latest: ${decisionStatus.latestVersion})`
503
+ : 'already installed'
504
+
505
+ process.stdout.write(` [skill] decision-history: ${statusLabel}\n`)
506
+
507
+ if (decisionStatus.status === 'not_installed' || decisionStatus.status === 'needs_update') {
508
+ let wantDecisionSkill = true
509
+ if (interactive) {
510
+ wantDecisionSkill = await askYesNo(
511
+ rl,
512
+ 'Would you like to install/update the decision-history skill (rachmanzz/decisionskills)?',
513
+ true
514
+ )
515
+ }
516
+ if (wantDecisionSkill) {
517
+ let scope = 'project'
518
+ if (interactive) {
519
+ scope = await askChoice(
520
+ rl,
521
+ 'Scope for decision-history skill?',
522
+ [
523
+ ['project', 'per-project (.agents/skills/decision-history)'],
524
+ ['global', 'global (all agent terminals: agy, codex, opencode, claude)']
525
+ ],
526
+ 'project'
527
+ )
528
+ }
529
+ process.stdout.write(`Installing decision-history skill (${scope}) ...\n`)
530
+ await installDecisionSkills({ scope, cwd: process.cwd(), home, exec })
531
+ process.stdout.write(` [skill] decision-history: installed (${scope})\n`)
532
+ } else {
533
+ process.stdout.write(` [skill] decision-history: skipped by user\n`)
534
+ }
535
+ }
536
+
537
+ // Ensure gitignore
538
+ const giResult = await ensureGitignore({ cwd: process.cwd() })
539
+ process.stdout.write(` [3/5] gitignore: ${giResult.path} (${giResult.status})\n`)
540
+
541
+ // Daemon and container lifecycle
542
+ let currentGlobalConfig = globalConfig
543
+ let containerStatus = await inspectContainerStatus(exec)
544
+ let daemonMeta = await detectRunningDaemon(exec)
545
+
546
+ if (containerStatus.exists && !containerStatus.valid) {
547
+ let wantRecreate = true
548
+ if (interactive) {
549
+ wantRecreate = await askYesNo(
550
+ rl,
551
+ `AnchorDB container "${containerStatus.name}" is in an invalid state (${containerStatus.status}). Would you like to recreate it?`,
552
+ true
553
+ )
554
+ }
555
+ if (wantRecreate) {
556
+ process.stdout.write(`Removing invalid container "${containerStatus.name}" ...\n`)
557
+ try {
558
+ await removeContainer(exec, containerStatus.runtime, containerStatus.name)
559
+ } catch {}
560
+ await runInstallCommand('anchordb', {
561
+ values: { ...values, method: 'container' },
562
+ rl
563
+ })
564
+ const freshGlobal = await readGlobalConfig()
565
+ currentGlobalConfig = freshGlobal.config || currentGlobalConfig
566
+ daemonMeta = await detectRunningDaemon(exec)
567
+ }
568
+ } else if (containerStatus.exists && !containerStatus.running && containerStatus.valid) {
569
+ let wantStart = true
570
+ if (interactive) {
571
+ wantStart = await askYesNo(
572
+ rl,
573
+ `AnchorDB container "${containerStatus.name}" is stopped. Would you like to start it now?`,
574
+ true
575
+ )
576
+ }
577
+ if (wantStart) {
578
+ process.stdout.write(`Starting container "${containerStatus.name}" ...\n`)
579
+ await startContainer(exec, containerStatus.runtime, containerStatus.name)
580
+ await waitAnchorHealth()
581
+ daemonMeta = await detectRunningDaemon(exec)
582
+ if (daemonMeta) {
583
+ const updatedAnchor = {
584
+ ...(currentGlobalConfig?.anchordb || {}),
585
+ url: `wire://127.0.0.1:${daemonMeta.wire_port || WIRE_PORT_DEFAULT}`,
586
+ token: daemonMeta.token || currentGlobalConfig?.anchordb?.token || '',
587
+ sync: currentGlobalConfig?.anchordb?.sync !== false,
588
+ daemon: daemonMeta.daemon || daemonMeta.deamon,
589
+ deamon: daemonMeta.daemon || daemonMeta.deamon,
590
+ container_name: daemonMeta.container_name,
591
+ container_id: daemonMeta.container_id,
592
+ container_created_on: daemonMeta.container_created_on
593
+ }
594
+ await mergeGlobalAnchordb(home, updatedAnchor)
595
+ currentGlobalConfig = { ...currentGlobalConfig, anchordb: updatedAnchor }
596
+ process.stdout.write(` [info] global config updated with ${daemonMeta.daemon || daemonMeta.deamon} daemon metadata\n`)
597
+ }
598
+ }
599
+ } else if (!daemonMeta && !containerStatus.exists) {
600
+ const hasConfigured = Boolean(currentGlobalConfig?.anchordb?.url)
601
+ let wantInstall = Boolean(values.runtime)
602
+ if (!wantInstall && interactive && !hasConfigured) {
603
+ wantInstall = await askYesNo(
604
+ rl,
605
+ 'AnchorDB is not running. Would you like to create an AnchorDB container now?',
606
+ false
607
+ )
608
+ }
609
+ if (wantInstall) {
610
+ process.stdout.write('\nProvisioning AnchorDB container ...\n')
611
+ await runInstallCommand('anchordb', {
612
+ values: { ...values, method: 'container' },
613
+ rl
614
+ })
615
+ const freshGlobal = await readGlobalConfig()
616
+ currentGlobalConfig = freshGlobal.config || currentGlobalConfig
617
+ daemonMeta = await detectRunningDaemon(exec)
618
+ }
619
+ }
620
+
621
+ if (daemonMeta && currentGlobalConfig?.anchordb) {
622
+ const existing = currentGlobalConfig.anchordb
623
+ const daemonType = daemonMeta.daemon || daemonMeta.deamon || existing.daemon || existing.deamon
624
+ const updated = {
625
+ ...existing,
626
+ daemon: daemonType,
627
+ deamon: daemonType
628
+ }
629
+ if (daemonMeta.container_name) updated.container_name = daemonMeta.container_name
630
+ if (daemonMeta.container_id) updated.container_id = daemonMeta.container_id
631
+ if (daemonMeta.container_created_on) updated.container_created_on = daemonMeta.container_created_on
632
+
633
+ if (JSON.stringify(existing) !== JSON.stringify(updated)) {
634
+ await mergeGlobalAnchordb(home, updated)
635
+ currentGlobalConfig = { ...currentGlobalConfig, anchordb: updated }
636
+ }
637
+ }
638
+
639
+ let currentConfig = projectConfig || {}
640
+ const hasGlobalAnchor = Boolean(currentGlobalConfig?.anchordb)
641
+ const hasLocalAnchor = Boolean(projectConfig?.anchordb)
642
+ const hasAnchor = hasGlobalAnchor || hasLocalAnchor
643
+
644
+ if (hasAnchor) {
645
+ const slug = projectSlug(process.cwd())
646
+ const neededRoles = ['knowledges', 'sources']
647
+ let modelsUpdated = false
648
+ const nextModels = { ...(currentConfig.models || {}) }
649
+
650
+ for (const role of neededRoles) {
651
+ if (!nextModels[role]) {
652
+ const name = generateModelName()
653
+ await registerModel(name, { role, project: slug, cwd: process.cwd() })
654
+ nextModels[role] = name
655
+ modelsUpdated = true
656
+ }
657
+ }
658
+
659
+ if (modelsUpdated) {
660
+ currentConfig = { ...currentConfig, models: nextModels }
661
+ delete currentConfig.model
662
+ fs.writeFileSync(path.join(process.cwd(), 'knl-config.json'), JSON.stringify(currentConfig, null, 2) + '\n', 'utf8')
663
+ process.stdout.write(` [4/5] anchordb models: mapped knowledges (${nextModels.knowledges}) and sources (${nextModels.sources}) in knl-config.json\n`)
664
+ } else {
665
+ process.stdout.write(` [4/5] anchordb models: already mapped in knl-config.json\n`)
666
+ }
667
+
668
+ const targetAnchordb = {
669
+ ...(settings.anchordb || currentGlobalConfig?.anchordb || currentConfig?.anchordb),
670
+ model: nextModels.sources
671
+ }
672
+ const opts = anchordbClientOptions(targetAnchordb)
673
+ if (opts) {
674
+ try {
675
+ process.stdout.write(' syncing project sources to AnchorDB ...\n')
676
+ const client = await createAnchorWireClient(opts)
677
+ const result = await syncSourcesToAnchorDB(client, process.cwd(), {
678
+ labelDir: settings.labelDir,
679
+ include: settings.include,
680
+ exclude: settings.exclude
681
+ })
682
+ client.close()
683
+ process.stdout.write(` sources sync complete: ${result.files} files, ${result.edges} edges\n`)
684
+ } catch (err) {
685
+ process.stdout.write(` (sources sync deferred: ${err.message})\n`)
686
+ }
687
+ }
688
+ } else {
689
+ process.stdout.write(' [4/5] anchordb: inactive (run "knl-view install anchordb" to enable)\n')
690
+ }
691
+
692
+ // Seed agent rules
693
+ let wantAgentRules = true
694
+ if (interactive) {
695
+ wantAgentRules = await askYesNo(
696
+ rl,
697
+ 'Would you like to configure KNL-View as your primary code intelligence tool in your agent rule files (AGENTS.md, etc.)?',
698
+ true
699
+ )
700
+ }
701
+ if (wantAgentRules) {
702
+ const hasGraphify = await detectGraphify(exec)
703
+ const ruleActions = await seedAgentInstructions({ cwd: process.cwd(), home, hasGraphify })
704
+ for (const action of ruleActions) {
705
+ const rel = path.relative(process.cwd(), action.path)
706
+ const display = rel.startsWith('..') ? action.path : rel
707
+ process.stdout.write(` [5/5] agent rules: ${display} (${action.status}${hasGraphify ? ', graphify fallback enabled' : ''})\n`)
708
+ }
709
+ } else {
710
+ process.stdout.write(' [5/5] agent rules: skipped by user\n')
711
+ }
712
+
713
+ process.stdout.write('\nSetup complete! You can now run:\n knl-view # start the viewer\n knl-view graph trace --target=README.md\n')
714
+ } finally {
715
+ if (rl) rl.close()
716
+ }
717
+ }
718
+
431
719
  function report(actions) {
432
720
  for (const action of actions) {
433
721
  const label = action.kind === 'dir' ? 'folder ' : 'file '
@@ -479,13 +767,14 @@ async function askYesNo(rl, question, fallback) {
479
767
  // prefers it): ghcr.io container via docker/podman. After install, the
480
768
  // "anchordb" block is written to the global config so every project can
481
769
  // connect right away.
482
- async function runInstallCommand(target, { values }) {
770
+ async function runInstallCommand(target, { values, rl: externalRl = null }) {
483
771
  if (target !== 'anchordb') {
484
772
  fail(`Unknown install target: ${target || '(none)'}\nAvailable: knl-view install anchordb`)
485
773
  }
486
774
  const nativeOk = process.platform === 'linux' || process.platform === 'darwin'
487
775
  const interactive = Boolean(process.stdin.isTTY) && !values.yes
488
- const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null
776
+ const ownsRl = !externalRl && interactive
777
+ const rl = externalRl || (interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null)
489
778
 
490
779
  let method = values.method
491
780
  if (!method) {
@@ -510,6 +799,36 @@ async function runInstallCommand(target, { values }) {
510
799
  // fs in this module is the callback API; installer steps need the promise version.
511
800
  const fsp = fs.promises
512
801
  let started = false
802
+ let daemonType = method === 'binary' ? 'binary' : null
803
+ let containerMetadata = null
804
+
805
+ const portInfo = await resolveAnchorPorts({
806
+ desiredWirePort: WIRE_PORT_DEFAULT,
807
+ desiredHttpPort: HTTP_PORT_DEFAULT
808
+ })
809
+
810
+ let wirePort = portInfo.wirePort
811
+ let httpPort = portInfo.httpPort
812
+
813
+ if (!portInfo.wireAvailable || !portInfo.httpAvailable) {
814
+ const conflictPort = !portInfo.wireAvailable ? wirePort : httpPort
815
+ const suggested = !portInfo.wireAvailable ? portInfo.suggestedWirePort : portInfo.suggestedHttpPort
816
+ let useSuggested = true
817
+ if (interactive) {
818
+ useSuggested = await askYesNo(
819
+ rl,
820
+ `Port ${conflictPort} is already in use. Would you like to use port ${suggested} instead?`,
821
+ true
822
+ )
823
+ }
824
+ if (useSuggested) {
825
+ wirePort = portInfo.suggestedWirePort
826
+ httpPort = portInfo.suggestedHttpPort
827
+ process.stdout.write(` [port] selected wire port ${wirePort} and HTTP port ${httpPort}\n`)
828
+ } else {
829
+ fail(`Port ${conflictPort} is in use and alternative port was declined.`)
830
+ }
831
+ }
513
832
 
514
833
  try {
515
834
  if (method === 'binary') {
@@ -517,60 +836,82 @@ async function runInstallCommand(target, { values }) {
517
836
  fail('The binary method needs Linux or macOS.\nUse --method=container.')
518
837
  }
519
838
  const { version } = await latestRelease()
839
+ const knlViewDir = knlGlobalDir(home)
520
840
  const asset = releaseAssetName(version)
521
841
  const workdir = path.join(os.tmpdir(), `knlview-install-${Date.now()}`)
522
842
  await fsp.mkdir(workdir, { recursive: true })
523
- const binDir = path.join(home, '.knlview', 'bin')
524
-
525
- process.stdout.write(`Downloading ${asset} (v${version}) ...\n`)
526
- const archive = path.join(workdir, asset)
527
- await downloadFile(`https://github.com/rachmanzz/anchordb/releases/download/v${version}/${asset}`, archive)
528
- const sums = path.join(workdir, 'SHA256SUMS.txt')
529
- await downloadFile(`https://github.com/rachmanzz/anchordb/releases/download/v${version}/SHA256SUMS.txt`, sums)
530
- await verifySha256(archive, await fsp.readFile(sums, 'utf8'))
531
- process.stdout.write(' SHA256 matches SHA256SUMS.txt\n')
532
-
533
- process.stdout.write('Extracting ...\n')
534
- await exec('tar', ['-xzf', archive, '-C', workdir])
535
- await fsp.mkdir(binDir, { recursive: true })
536
- for (const bin of ['anchordb-server', 'anchordb']) {
537
- const target2 = path.join(binDir, bin)
538
- await fsp.copyFile(path.join(workdir, bin), target2)
539
- await fsp.chmod(target2, 0o755)
540
- }
541
- process.stdout.write(` installed to ${binDir}\n`)
843
+ try {
844
+ const binDir = path.join(knlViewDir, 'bin')
845
+
846
+ process.stdout.write(`Downloading ${asset} (v${version}) ...\n`)
847
+ const archive = path.join(workdir, asset)
848
+ await downloadFile(`https://github.com/rachmanzz/anchordb/releases/download/v${version}/${asset}`, archive)
849
+ const sums = path.join(workdir, 'SHA256SUMS.txt')
850
+ await downloadFile(`https://github.com/rachmanzz/anchordb/releases/download/v${version}/SHA256SUMS.txt`, sums)
851
+ await verifySha256(archive, await fsp.readFile(sums, 'utf8'))
852
+ process.stdout.write(' SHA256 matches SHA256SUMS.txt\n')
853
+
854
+ process.stdout.write('Extracting ...\n')
855
+ await exec('tar', ['-xzf', archive, '-C', workdir])
856
+ await fsp.mkdir(binDir, { recursive: true, mode: 0o700 })
857
+ for (const bin of ['anchordb-server', 'anchordb']) {
858
+ const target2 = path.join(binDir, bin)
859
+ await fsp.copyFile(path.join(workdir, bin), target2)
860
+ await fsp.chmod(target2, 0o755)
861
+ }
862
+ process.stdout.write(` installed to ${binDir}\n`)
863
+ } finally {
864
+ await fsp.rm(workdir, { recursive: true, force: true }).catch(() => {})
865
+ }
542
866
 
543
867
  const runNow = interactive
544
868
  ? await askYesNo(rl, 'Start the AnchorDB server now?', true)
545
869
  : true
546
870
  if (runNow) {
547
- const dataDir = path.join(home, '.knlview', 'anchordb-data')
871
+ const dataDir = path.join(knlViewDir, 'anchordb-data')
548
872
  await fsp.mkdir(dataDir, { recursive: true })
549
- const logFd = await fsp.open(path.join(home, '.knlview', 'anchordb.log'), 'a')
873
+ const logFd = await fsp.open(path.join(knlViewDir, 'anchordb.log'), 'a')
550
874
  const child = spawn(path.join(binDir, 'anchordb-server'), [], {
551
875
  env: {
552
876
  ...process.env,
553
877
  ANCHORDB_AUTH_TOKEN: token,
554
878
  ANCHORDB_DATA_DIR: dataDir,
555
- ANCHORDB_PORT: String(HTTP_PORT_DEFAULT),
556
- ANCHORDB_WIRE_PORT: String(WIRE_PORT_DEFAULT)
879
+ ANCHORDB_PORT: String(httpPort),
880
+ ANCHORDB_WIRE_PORT: String(wirePort)
557
881
  },
558
882
  detached: true,
559
883
  stdio: ['ignore', logFd.fd, logFd.fd]
560
884
  })
561
885
  child.unref()
562
886
  logFd.close()
563
- await waitAnchorHealth()
887
+ await waitAnchorHealth(httpPort)
564
888
  started = true
565
- process.stdout.write(` server running (log: ${path.join(home, '.knlview', 'anchordb.log')})\n`)
889
+ process.stdout.write(` server running (log: ${path.join(knlViewDir, 'anchordb.log')})\n`)
566
890
  }
567
891
  } else {
568
- const runtime = await detectContainerRuntime(async (cmd) => {
892
+ const preferredRuntime = values.runtime
893
+ if (preferredRuntime && !['docker', 'podman'].includes(preferredRuntime)) {
894
+ fail(`--runtime must be docker or podman, got "${preferredRuntime}".`)
895
+ }
896
+ const which = async (cmd) => {
569
897
  try { await exec(cmd, ['--version']); return true } catch { return false }
570
- })
571
- if (!runtime) {
898
+ }
899
+ const available = await availableContainerRuntimes(which)
900
+ if (!available.length) {
572
901
  fail('docker/podman not found.\nInstall one first: Docker Desktop (macOS/Windows) or podman.')
573
902
  }
903
+ let runtime = preferredRuntime
904
+ if (!runtime) {
905
+ if (available.length === 1) {
906
+ runtime = available[0]
907
+ } else {
908
+ runtime = interactive
909
+ ? await askChoice(rl, 'Container runtime to use?', available.map((r) => [r, r]), available[0])
910
+ : available[0]
911
+ }
912
+ } else if (!available.includes(runtime)) {
913
+ fail(`Runtime "${runtime}" was requested via --runtime, but it is not installed or available on this system.`)
914
+ }
574
915
  const { version } = await latestRelease()
575
916
  const image = `${GHCR_IMAGE}:${version}`
576
917
  process.stdout.write(`Pulling ${image} via ${runtime} ...\n`)
@@ -583,14 +924,19 @@ async function runInstallCommand(target, { values }) {
583
924
  fail(`Container "${CONTAINER_NAME}" already exists.\nRemove it first if you really want to reinstall: ${runtime} rm -f ${CONTAINER_NAME}`)
584
925
  }
585
926
 
586
- const dataDir = path.join(home, '.knlview', 'anchordb-data')
587
- await fsp.mkdir(dataDir, { recursive: true })
588
- await fsp.chmod(dataDir, 0o777)
927
+ const knlViewDir = knlGlobalDir(home)
928
+ await fsp.mkdir(knlViewDir, { recursive: true, mode: 0o700 })
929
+ try { await fsp.chmod(knlViewDir, 0o700) } catch {}
930
+ const dataDir = path.join(knlViewDir, 'anchordb-data')
931
+ await fsp.mkdir(dataDir, { recursive: true, mode: 0o770 })
932
+ try { await fsp.chmod(dataDir, 0o770) } catch {}
589
933
  const baseArgs = ['run', '-d', '--name', CONTAINER_NAME,
590
- '-p', `${HTTP_PORT_DEFAULT}:${HTTP_PORT_DEFAULT}`,
591
- '-p', `${WIRE_PORT_DEFAULT}:${WIRE_PORT_DEFAULT}`,
934
+ '-p', `${httpPort}:${httpPort}`,
935
+ '-p', `${wirePort}:${wirePort}`,
592
936
  '-e', `ANCHORDB_AUTH_TOKEN=${token}`,
593
- '-e', 'ANCHORDB_DATA_DIR=/data']
937
+ '-e', 'ANCHORDB_DATA_DIR=/data',
938
+ '-e', `ANCHORDB_PORT=${httpPort}`,
939
+ '-e', `ANCHORDB_WIRE_PORT=${wirePort}`]
594
940
  // The image runs as user "anchordb" (not root). Rootless podman maps
595
941
  // that uid to a different host uid, so data files owned by the host
596
942
  // user become unreadable ("permission denied"). keep-id runs the
@@ -598,27 +944,53 @@ async function runInstallCommand(target, { values }) {
598
944
  // container can share the folder. Docker (macOS/Windows) runs in a VM
599
945
  // and its bind mounts are unaffected, hence no flag here.
600
946
  if (runtime === 'podman') baseArgs.push('--userns=keep-id')
947
+ daemonType = runtime
948
+ let containerId = null
949
+ let containerCreatedOn = new Date().toISOString()
601
950
  process.stdout.write(`Starting container ...\n`)
602
951
  try {
603
- await exec(runtime, [...baseArgs, '-v', `${dataDir}:/data:Z`, image])
952
+ const { stdout } = await exec(runtime, [...baseArgs, '-v', `${dataDir}:/data:Z`, image])
953
+ containerId = stdout.trim().split('\n')[0]
604
954
  } catch {
605
955
  // Rootless/sandbox setups sometimes reject volume binds; without a
606
956
  // volume the data dies with the container, so we say so plainly.
607
957
  process.stdout.write(' volume could not be mounted; data is ephemeral inside the container\n')
608
- await exec(runtime, [...baseArgs, image])
958
+ const { stdout } = await exec(runtime, [...baseArgs, image])
959
+ containerId = stdout.trim().split('\n')[0]
960
+ }
961
+ try {
962
+ const { stdout: idOut } = await exec(runtime, ['inspect', '--format', '{{.Id}}', CONTAINER_NAME])
963
+ if (idOut.trim()) containerId = idOut.trim()
964
+ const { stdout: createdOut } = await exec(runtime, ['inspect', '--format', '{{.Created}}', CONTAINER_NAME])
965
+ if (createdOut.trim()) containerCreatedOn = createdOut.trim()
966
+ } catch {
967
+ // fallback to captured containerId and timestamp
968
+ }
969
+ containerMetadata = {
970
+ container_created_on: containerCreatedOn,
971
+ container_name: CONTAINER_NAME,
972
+ container_id: containerId
609
973
  }
610
- await waitAnchorHealth()
974
+ await waitAnchorHealth(httpPort)
611
975
  started = true
612
976
  }
613
977
  } catch (err) {
614
978
  fail(`Install failed: ${err.message || err}`)
615
979
  }
616
980
 
617
- const before = await mergeGlobalAnchordb(home, {
618
- url: `wire://127.0.0.1:${WIRE_PORT_DEFAULT}`,
981
+ const anchordbBlock = {
982
+ url: `wire://127.0.0.1:${wirePort}`,
619
983
  token,
620
- sync: true
621
- })
984
+ sync: true,
985
+ deamon: daemonType
986
+ }
987
+ if (containerMetadata) {
988
+ anchordbBlock.container_created_on = containerMetadata.container_created_on
989
+ anchordbBlock.container_name = containerMetadata.container_name
990
+ anchordbBlock.container_id = containerMetadata.container_id
991
+ }
992
+
993
+ const before = await mergeGlobalAnchordb(home, anchordbBlock)
622
994
  if (before) {
623
995
  process.stdout.write(' (old anchordb block in the global config overwritten; new token applied)\n')
624
996
  }
@@ -626,10 +998,10 @@ async function runInstallCommand(target, { values }) {
626
998
  process.stdout.write(
627
999
  `\nAnchorDB ready (optional layer: projects without an anchordb block\n` +
628
1000
  ` in the global config still run fully local).\n` +
629
- ` HTTP http://127.0.0.1:${HTTP_PORT_DEFAULT}/health${started ? ' (already responding)' : ''}\n` +
630
- ` wire wire://127.0.0.1:${WIRE_PORT_DEFAULT}\n` +
631
- ` token ${token.slice(0, 8)}... (full value in ~/.knlview/knl-config.json)\n` +
632
- ` config ${home}/.knlview/knl-config.json\n\n` +
1001
+ ` HTTP http://127.0.0.1:${httpPort}/health${started ? ' (already responding)' : ''}\n` +
1002
+ ` wire wire://127.0.0.1:${wirePort}\n` +
1003
+ ` token ${token.slice(0, 8)}... (full value in ${globalConfigPath(home)})\n` +
1004
+ ` config ${globalConfigPath(home)}\n\n` +
633
1005
  `Next steps (if this project should use sync):\n` +
634
1006
  ` 1. In your project: knl-view model new knowledges\n` +
635
1007
  ` 2. knl-view, the "anchordb connected" line appears once the graph is in sync\n`
@@ -640,19 +1012,20 @@ async function runInstallCommand(target, { values }) {
640
1012
  ` ~/.knlview/bin/anchordb-server (env: ANCHORDB_AUTH_TOKEN, ANCHORDB_DATA_DIR)\n`
641
1013
  )
642
1014
  }
1015
+ if (ownsRl && rl) rl.close()
643
1016
  }
644
1017
 
645
- async function waitAnchorHealth(attempts = 30) {
1018
+ async function waitAnchorHealth(httpPort = HTTP_PORT_DEFAULT, attempts = 30) {
646
1019
  for (let i = 0; i < attempts; i++) {
647
1020
  try {
648
- const res = await fetch(`http://127.0.0.1:${HTTP_PORT_DEFAULT}/health`)
1021
+ const res = await fetch(`http://127.0.0.1:${httpPort}/health`, { signal: AbortSignal.timeout(2000) })
649
1022
  if (res.ok) return
650
1023
  } catch {
651
1024
  // not ready yet, retry
652
1025
  }
653
1026
  await new Promise((resolve) => setTimeout(resolve, 500))
654
1027
  }
655
- throw new Error(`server not responding at http://127.0.0.1:${HTTP_PORT_DEFAULT}/health within ${attempts / 2} seconds, check the anchordb log`)
1028
+ throw new Error(`server not responding at http://127.0.0.1:${httpPort}/health within ${attempts / 2} seconds, check the anchordb log`)
656
1029
  }
657
1030
 
658
1031
  async function containerExists(runtime) {
@@ -793,7 +1166,11 @@ async function runSourcesCommand(sub, values, { settings }) {
793
1166
  const projectRoot = process.cwd()
794
1167
 
795
1168
  if (sub === 'sync') {
796
- const result = await syncSourcesToAnchorDB(client, projectRoot, { labelDir: settings.labelDir })
1169
+ const result = await syncSourcesToAnchorDB(client, projectRoot, {
1170
+ labelDir: settings.labelDir,
1171
+ include: settings.include,
1172
+ exclude: settings.exclude
1173
+ })
797
1174
  const parts = [
798
1175
  `${result.files} files`,
799
1176
  `${result.symbols} symbols`,