@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.
- package/README.md +117 -32
- package/bin/knl-view.js +435 -58
- package/dist/assets/{abnfDiagram-WEYOIA3C-B1gaBaSI.js → abnfDiagram-WEYOIA3C-hoSbsMOz.js} +1 -1
- package/dist/assets/{arc-NZ9uRaCl.js → arc-g87Np6E5.js} +1 -1
- package/dist/assets/{architecture-WOLXFQ4H-8MalLwTd.js → architecture-WOLXFQ4H-DOIq-0IA.js} +1 -1
- package/dist/assets/{architectureDiagram-ZHVYKT53-eZ8i5cl4.js → architectureDiagram-ZHVYKT53-Dxwe8n7m.js} +1 -1
- package/dist/assets/{blockDiagram-RJAE7QA3-BavOE8BM.js → blockDiagram-RJAE7QA3-BnGng59Z.js} +1 -1
- package/dist/assets/{c4Diagram-XDPPPE54-DqMaTAzx.js → c4Diagram-XDPPPE54-C5LjqIIo.js} +1 -1
- package/dist/assets/channel-BVWkRFBw.js +1 -0
- package/dist/assets/{chunk-2E7WHVMD-B1Hj2BjJ.js → chunk-2E7WHVMD-BJ1dyGLq.js} +1 -1
- package/dist/assets/{chunk-4EMNKR2T-HDscrCiq.js → chunk-4EMNKR2T-DXF02Sgw.js} +1 -1
- package/dist/assets/{chunk-55XCU76K-QR_ARj8I.js → chunk-55XCU76K-CM1akAbu.js} +1 -1
- package/dist/assets/{chunk-7S6XSTFO-B0p3o547.js → chunk-7S6XSTFO-BOUjqldL.js} +1 -1
- package/dist/assets/{chunk-AQDTSKZB-CCxwNyyA.js → chunk-AQDTSKZB-C4isdNLA.js} +1 -1
- package/dist/assets/{chunk-DSNLSHJZ-BM5PBFmz.js → chunk-DSNLSHJZ-CbOTkSVM.js} +1 -1
- package/dist/assets/{chunk-HAGFOTED-UCDVFuN_.js → chunk-HAGFOTED-CRfnketR.js} +1 -1
- package/dist/assets/{chunk-HNE4Q36P-C_1sakp2.js → chunk-HNE4Q36P-rxlpGUU8.js} +1 -1
- package/dist/assets/{chunk-NQOKRVIF-b2D1Z4oK.js → chunk-NQOKRVIF-BOYCgTmV.js} +1 -1
- package/dist/assets/{chunk-UHW7PFBP-CJs0VwBY.js → chunk-UHW7PFBP-Cnogp1Fb.js} +1 -1
- package/dist/assets/{chunk-VQ5W62QI-DQxjgE4_.js → chunk-VQ5W62QI-Cu1WY5Uo.js} +1 -1
- package/dist/assets/{chunk-WQQL4CDK-DcjPeAfQ.js → chunk-WQQL4CDK-BJc3FPE2.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-glA0kB5s.js → chunk-XXDRQBXY-BlPj4H7s.js} +1 -1
- package/dist/assets/{chunk-YZO67HFG-DJqACL5V.js → chunk-YZO67HFG-V85H31x4.js} +1 -1
- package/dist/assets/{chunk-ZAUX2LTX-CWfaYtwb.js → chunk-ZAUX2LTX-C3XKXW09.js} +1 -1
- package/dist/assets/{chunk-ZCHMQSOR-BGX73Hho.js → chunk-ZCHMQSOR-B0mmpEgW.js} +1 -1
- package/dist/assets/{classDiagram-v2-4MYWR3UA-BpYXPz-e.js → classDiagram-v2-4MYWR3UA-oMd7YaHP.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-e1quKm0Q.js → cose-bilkent-JH36ORCC-J0kSVsDf.js} +1 -1
- package/dist/assets/{cynefin-EF2NZ3EQ-hb3oUmFO.js → cynefin-EF2NZ3EQ-DK2TNhel.js} +1 -1
- package/dist/assets/{cynefinDiagram-WF24UDRP-CnMQvLqi.js → cynefinDiagram-WF24UDRP-DDvKgieA.js} +1 -1
- package/dist/assets/{dagre-NK4GIZVX-BH_Y8y3F.js → dagre-NK4GIZVX-Ca2Fe-i-.js} +1 -1
- package/dist/assets/{diagram-CBK3MWTC-Dh79VetZ.js → diagram-CBK3MWTC-L6dmeQ2V.js} +1 -1
- package/dist/assets/{diagram-I5XIIV2D-D3u-o1RV.js → diagram-I5XIIV2D-DhG8R-v5.js} +1 -1
- package/dist/assets/{diagram-ODIL2UCW-Bfuo-OKS.js → diagram-ODIL2UCW-BpYYbYBT.js} +1 -1
- package/dist/assets/{diagram-PFBEJUXE-CwHasIbs.js → diagram-PFBEJUXE-DKQpHSsC.js} +1 -1
- package/dist/assets/{diagram-RJA3I4VV-D8ttmuE8.js → diagram-RJA3I4VV-DP7Dsnya.js} +1 -1
- package/dist/assets/{diagram-TYUNFSNR-CzC-8RZ0.js → diagram-TYUNFSNR-CfSFewUS.js} +1 -1
- package/dist/assets/{dist-CUJYGnnI.js → dist-CQNjnrld.js} +1 -1
- package/dist/assets/{ebnfDiagram-V4P3TTPT-9AAhU7In.js → ebnfDiagram-V4P3TTPT-DE3MZoJO.js} +1 -1
- package/dist/assets/{elk-QONTBCTQ-BgvnD1v_.js → elk-QONTBCTQ-p_aAQPHU.js} +1 -1
- package/dist/assets/{erDiagram-KYQIGQAA-RLtbiLlP.js → erDiagram-KYQIGQAA-DZO3vQrk.js} +1 -1
- package/dist/assets/{eventmodeling-K75KTNOO-DVY22vNZ.js → eventmodeling-K75KTNOO-BII9EPl5.js} +1 -1
- package/dist/assets/flowDiagram-IUGLLWBU-D9uPno39.js +1 -0
- package/dist/assets/{ganttDiagram-R7GPNTJY-hHiBGa9W.js → ganttDiagram-R7GPNTJY-BKiH3v2r.js} +1 -1
- package/dist/assets/{gitGraph-VSP46ZUC-Cvxay1wh.js → gitGraph-VSP46ZUC-oHL6cr73.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PDYL7XHY-AOhlpSqg.js → gitGraphDiagram-PDYL7XHY-CqPVIEeo.js} +1 -1
- package/dist/assets/{index-CBLNQvK3.js → index-CSpirV16.js} +3 -3
- package/dist/assets/{info-OHQRW6UA-CJ8EUO1m.js → info-OHQRW6UA-Drrbnhpd.js} +1 -1
- package/dist/assets/{infoDiagram-MVDRKAX5-DOQu7oYI.js → infoDiagram-MVDRKAX5-DnQQFMAd.js} +1 -1
- package/dist/assets/{ishikawaDiagram-HPEHV2GE-DcVMvGmx.js → ishikawaDiagram-HPEHV2GE-DIpQlxRh.js} +1 -1
- package/dist/assets/{journeyDiagram-BOSHW7O7-luMeqEqz.js → journeyDiagram-BOSHW7O7-EBR9I5h1.js} +1 -1
- package/dist/assets/{kanban-definition-FNSA4HC5-KTZbmrW2.js → kanban-definition-FNSA4HC5-CdvNsjik.js} +1 -1
- package/dist/assets/{line-CmPHiOVm.js → line-CDLzgxDc.js} +1 -1
- package/dist/assets/{linear-DHYlKXBA.js → linear-DfXKql9u.js} +1 -1
- package/dist/assets/{mermaid-parser.core-BRDI-WhC.js → mermaid-parser.core-DnXGphT2.js} +3 -3
- package/dist/assets/{mermaid.core-DPTRaYLy.js → mermaid.core-h6fiCaSZ.js} +4 -4
- package/dist/assets/{mindmap-definition-6OY57UG3-70EN5Kw_.js → mindmap-definition-6OY57UG3-BiNfio-j.js} +1 -1
- package/dist/assets/{packet-JDAUHWVQ-CXWz83oS.js → packet-JDAUHWVQ-D76byv1S.js} +1 -1
- package/dist/assets/{pegDiagram-ECIUJNBX-z5UXKmp4.js → pegDiagram-ECIUJNBX-Drs8Pxdt.js} +1 -1
- package/dist/assets/{pie-XZMESJXO-rWI1HIQ7.js → pie-XZMESJXO-D_83z1lM.js} +1 -1
- package/dist/assets/{pieDiagram-GNMPY74T-Bh4Kv3NQ.js → pieDiagram-GNMPY74T-wvzIphSf.js} +1 -1
- package/dist/assets/{quadrantDiagram-D57K4MC3-wGM4Ckig.js → quadrantDiagram-D57K4MC3-B7EfMRWb.js} +1 -1
- package/dist/assets/{radar-ABXABTNO-CIJ92p5N.js → radar-ABXABTNO-Bs6dVdAR.js} +1 -1
- package/dist/assets/{railroad-I3PHUGI6-8bxua-Hf.js → railroad-I3PHUGI6-lKaN4miw.js} +1 -1
- package/dist/assets/{railroad-abnf-LNEFI6M7-B9KVI10m.js → railroad-abnf-LNEFI6M7-Bhh16b7Z.js} +1 -1
- package/dist/assets/{railroad-ebnf-DWYD2UWJ-Bt73rnyJ.js → railroad-ebnf-DWYD2UWJ-m6ybW-Y_.js} +1 -1
- package/dist/assets/{railroad-peg-JR7BVNR7-Cie82UPT.js → railroad-peg-JR7BVNR7-DNn72e7r.js} +1 -1
- package/dist/assets/{railroadDiagram-Y72NRBL7-C1nmi_tl.js → railroadDiagram-Y72NRBL7-DNeop1iZ.js} +1 -1
- package/dist/assets/{requirementDiagram-EQYSW5RA-BSianW40.js → requirementDiagram-EQYSW5RA-BVYZ2XYJ.js} +1 -1
- package/dist/assets/{sankeyDiagram-SY24JHO5-DIa11-h7.js → sankeyDiagram-SY24JHO5-B2MP51on.js} +1 -1
- package/dist/assets/{sequenceDiagram-BLEHNHQU-Df5rZKz2.js → sequenceDiagram-BLEHNHQU-BUvxstXG.js} +1 -1
- package/dist/assets/{src-dClg85lX.js → src-BrR0lqlG.js} +1 -1
- package/dist/assets/{stateDiagram-v2-A6IESKWZ-B15PTSvX.js → stateDiagram-v2-A6IESKWZ-Ba84OGKE.js} +1 -1
- package/dist/assets/{swimlanes-B3ENRKFQ-C9mgRCMi.js → swimlanes-B3ENRKFQ-BMSl2PNA.js} +1 -1
- package/dist/assets/swimlanesDiagram-AYQRV4AK-M1dmvq7m.js +8 -0
- package/dist/assets/{timeline-definition-JXYKGLGB-BDVf_mIv.js → timeline-definition-JXYKGLGB-DOdW-dxq.js} +1 -1
- package/dist/assets/{treeView-D4JQ5SDB-BbBK-25J.js → treeView-D4JQ5SDB-DZ9KQyn5.js} +1 -1
- package/dist/assets/{treemap-SAJKECNS-CEDX8apk.js → treemap-SAJKECNS-WEC5wtDV.js} +1 -1
- package/dist/assets/{usecaseDiagram-WPOLBNNX-D1QkwFF8.js → usecaseDiagram-WPOLBNNX-cldTztFL.js} +1 -1
- package/dist/assets/{vennDiagram-Q3Y3IDCW-5PeGZvEO.js → vennDiagram-Q3Y3IDCW-c41KySoY.js} +1 -1
- package/dist/assets/{wardley-7MLQ67FV-BatN94pO.js → wardley-7MLQ67FV-Bf9_aSSZ.js} +1 -1
- package/dist/assets/{wardleyDiagram-IVAIDKWA-DyCcDS3Y.js → wardleyDiagram-IVAIDKWA-BeKEvEBG.js} +1 -1
- package/dist/assets/{xychartDiagram-4URNHDU4-CbCXcRlg.js → xychartDiagram-4URNHDU4-tNkAxTxG.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +5 -1
- package/server/config.js +140 -14
- package/server/consensus.js +18 -13
- package/server/graph/parser.js +3 -2
- package/server/graph/sources.js +165 -24
- package/server/graph/syntax.js +196 -16
- package/server/index.js +124 -61
- package/server/install.js +350 -10
- package/server/models_registry.js +34 -9
- package/server/scaffold.js +99 -9
- package/dist/assets/channel-BZ2_TTTJ.js +0 -1
- package/dist/assets/flowDiagram-IUGLLWBU-l7L5DTb5.js +0 -1
- 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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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(
|
|
871
|
+
const dataDir = path.join(knlViewDir, 'anchordb-data')
|
|
548
872
|
await fsp.mkdir(dataDir, { recursive: true })
|
|
549
|
-
const logFd = await fsp.open(path.join(
|
|
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(
|
|
556
|
-
ANCHORDB_WIRE_PORT: String(
|
|
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(
|
|
889
|
+
process.stdout.write(` server running (log: ${path.join(knlViewDir, 'anchordb.log')})\n`)
|
|
566
890
|
}
|
|
567
891
|
} else {
|
|
568
|
-
const
|
|
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
|
-
|
|
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
|
|
587
|
-
await fsp.mkdir(
|
|
588
|
-
await fsp.chmod(
|
|
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', `${
|
|
591
|
-
'-p', `${
|
|
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
|
|
618
|
-
url: `wire://127.0.0.1:${
|
|
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:${
|
|
630
|
-
` wire wire://127.0.0.1:${
|
|
631
|
-
` token ${token.slice(0, 8)}... (full value in
|
|
632
|
-
` config ${home}
|
|
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:${
|
|
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:${
|
|
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, {
|
|
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`,
|