@kentwynn/kgraph 0.1.24 → 0.1.26

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 CHANGED
@@ -100,13 +100,18 @@ kgraph init
100
100
  # 2. Optional: connect AI tools so they know the KGraph workflow
101
101
  kgraph integrate add codex copilot cursor claude-code gemini windsurf cline
102
102
 
103
- # 3. Run the normal workflow for a topic
103
+ # 3. Optional: configure deep language extractors for non-TS repos
104
+ kgraph extractor add jvm python
105
+
106
+ # 4. Run the normal workflow for a topic
104
107
  kgraph "auth token refresh"
105
108
 
106
- # 4. Check health if something feels off
109
+ # 5. Check health if something feels off
107
110
  kgraph doctor
108
111
  ```
109
112
 
113
+ `kgraph init` now scans once, then prints relevant next steps. When KGraph can detect likely AI tools on the machine, it recommends matching integrations. When the repository contains languages that only have basic built-in extraction today, it recommends optional deep extractors and prints the exact install/configure commands.
114
+
110
115
  After useful AI work, assistants save durable runtime-capture notes into `.kgraph/inbox/`. These notes are not project documentation; they are KGraph input files that the next `kgraph` run processes automatically. You can also process them directly with `kgraph update`.
111
116
 
112
117
  Normal agent flow is intentionally small:
@@ -138,7 +143,7 @@ This is optional. Claude Code can use generated hook scripts for automatic captu
138
143
  kgraph init
139
144
  ```
140
145
 
141
- Required once per repo. Creates `.kgraph/` and the local config.
146
+ Required once per repo. Creates `.kgraph/`, writes the local config, runs the first scan, and prints suggested next actions based on the detected repo languages and likely local AI tools.
142
147
 
143
148
  ```bash
144
149
  kgraph init --integrations codex,copilot,cursor,claude-code,gemini,windsurf,cline
@@ -258,6 +263,18 @@ New integrations default to `always` mode because coding agents often under-clas
258
263
  | Codex | `AGENTS.md`, `.agents/skills/kgraph/SKILL.md` |
259
264
  | GitHub Copilot | `.github/copilot-instructions.md`, `.github/prompts/*` |
260
265
  | Cursor | `.cursor/rules/kgraph.mdc` |
266
+
267
+ ## Optional Deep Extractors
268
+
269
+ KGraph ships with built-in extractors for several languages, but TypeScript and JavaScript still have the deepest built-in analysis today. For languages such as Java, Kotlin, Python, Go, Rust, C/C++, and C#, `kgraph init` can recommend optional deep extractors when those languages are detected in the repository.
270
+
271
+ ```bash
272
+ kgraph extractor list
273
+ kgraph extractor add jvm python
274
+ kgraph extractor remove jvm
275
+ ```
276
+
277
+ `extractor add` writes extractor configuration into `.kgraph/config.yaml` and prints the exact `npm install -D ...` command for the matching optional packages. This is explicit on purpose: KGraph recommends install commands by default rather than silently changing package-manager state.
261
278
  | Claude Code | `CLAUDE.md`, `.claude/commands/*` |
262
279
  | Gemini CLI | `GEMINI.md` |
263
280
  | Windsurf | `.windsurf/rules/kgraph.md` |
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerExtractorCommand(program: Command): void;
@@ -0,0 +1,50 @@
1
+ import { installCommandForExtractors, normalizeExtractorNames, } from '../../extractors/extractor-registry.js';
2
+ import { addExtractors, listExtractors, removeExtractors, } from '../../extractors/extractor-store.js';
3
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
4
+ import { KGraphError, runCommand } from '../errors.js';
5
+ export function registerExtractorCommand(program) {
6
+ const extractor = program
7
+ .command('extractor')
8
+ .description('Manage optional deep language extractors');
9
+ extractor
10
+ .command('list')
11
+ .description('List configured optional extractors')
12
+ .action(() => runCommand(async () => {
13
+ const workspace = await assertWorkspace(process.cwd());
14
+ const extractors = await listExtractors(workspace);
15
+ if (extractors.length === 0) {
16
+ console.log('No extractors configured.');
17
+ return;
18
+ }
19
+ for (const item of extractors) {
20
+ console.log(`${item.name} ${item.enabled ? 'enabled' : 'disabled'} ${item.packageName} ${item.packageInstalled ? 'present' : 'missing'}`);
21
+ }
22
+ }));
23
+ extractor
24
+ .command('add')
25
+ .description('Configure optional deep extractors')
26
+ .argument('<names...>')
27
+ .action((names) => runCommand(async () => {
28
+ const workspace = await assertWorkspace(process.cwd());
29
+ const normalized = normalizeExtractorNames(names);
30
+ if (normalized.length === 0) {
31
+ throw new KGraphError('Provide at least one extractor name.');
32
+ }
33
+ const changed = await addExtractors(workspace, normalized);
34
+ console.log(`Configured extractors: ${changed.map((item) => item.name).join(', ')}`);
35
+ console.log(`Install packages: ${installCommandForExtractors(changed.map((item) => item.packageName))}`);
36
+ }));
37
+ extractor
38
+ .command('remove')
39
+ .description('Remove optional deep extractors')
40
+ .argument('<names...>')
41
+ .action((names) => runCommand(async () => {
42
+ const workspace = await assertWorkspace(process.cwd());
43
+ const normalized = normalizeExtractorNames(names);
44
+ if (normalized.length === 0) {
45
+ throw new KGraphError('Provide at least one extractor name.');
46
+ }
47
+ const removed = await removeExtractors(workspace, normalized);
48
+ console.log(`Removed extractors: ${removed.join(', ')}`);
49
+ }));
50
+ }
@@ -1,2 +1,2 @@
1
- import type { Command } from "commander";
1
+ import type { Command } from 'commander';
2
2
  export declare function registerInitCommand(program: Command): void;
@@ -1,28 +1,95 @@
1
- import { writeDefaultConfig } from "../../config/config.js";
2
- import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
- import { addIntegrations } from "../../integrations/integration-store.js";
4
- import { ensureWorkspace } from "../../storage/kgraph-paths.js";
5
- import { KGraphError, runCommand } from "../errors.js";
1
+ import { loadConfig, writeDefaultConfig } from '../../config/config.js';
2
+ import { installCommandForExtractors } from '../../extractors/extractor-registry.js';
3
+ import { addExtractors } from '../../extractors/extractor-store.js';
4
+ import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
5
+ import { addIntegrations } from '../../integrations/integration-store.js';
6
+ import { scanRepository } from '../../scanner/repo-scanner.js';
7
+ import { ensureWorkspace } from '../../storage/kgraph-paths.js';
8
+ import { readMaps, writeMaps } from '../../storage/map-store.js';
9
+ import { KGraphError, runCommand } from '../errors.js';
10
+ import { promptForInitExtractors, promptForInitIntegrations, shouldPromptForInitExtractors, shouldPromptForInitIntegrations, } from '../init-prompt.js';
11
+ import { detectMachineIntegrationRecommendations, recommendedExtractorsForInit, recommendedIntegrationsForInit, } from '../init-recommendations.js';
12
+ import { renderInitSummary } from '../init-summary.js';
6
13
  export function registerInitCommand(program) {
7
14
  program
8
- .command("init")
9
- .description("Initialize a .kgraph workspace")
10
- .option("--integration <name>", "Configure an AI tool integration", collectOption, [])
11
- .option("--integrations <names>", "Configure comma-separated AI tool integrations")
12
- .option("--mode <mode>", "Integration mode: smart, always, manual, or off", "always")
15
+ .command('init')
16
+ .description('Initialize a .kgraph workspace')
17
+ .option('--integration <name>', 'Configure an AI tool integration', collectOption, [])
18
+ .option('--integrations <names>', 'Configure comma-separated AI tool integrations')
19
+ .option('--mode <mode>', 'Integration mode: always, smart, manual, or off', 'always')
13
20
  .action((options) => runCommand(async () => {
14
21
  const workspace = await ensureWorkspace(process.cwd());
15
22
  const wroteConfig = await writeDefaultConfig(workspace);
16
- console.log(wroteConfig ? "Initialized .kgraph workspace." : ".kgraph workspace already initialized.");
23
+ console.log(wroteConfig
24
+ ? 'Initialized .kgraph workspace.'
25
+ : '.kgraph workspace already initialized.');
17
26
  const names = normalizeIntegrationNames([
18
27
  ...(options.integration ?? []),
19
- ...(options.integrations ? [options.integrations] : [])
28
+ ...(options.integrations ? [options.integrations] : []),
20
29
  ]);
21
30
  if (names.length > 0) {
22
31
  const mode = normalizeIntegrationMode(options.mode);
23
32
  const changed = await addIntegrations(workspace, names, mode);
24
- console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
33
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
25
34
  }
35
+ let config = await loadConfig(workspace);
36
+ const previousMaps = await readMaps(workspace);
37
+ const result = await scanRepository(workspace.rootPath, config, {
38
+ files: previousMaps.fileMap.files,
39
+ symbols: previousMaps.symbolMap.symbols,
40
+ dependencies: previousMaps.dependencyMap.dependencies,
41
+ relationships: previousMaps.relationshipMap.relationships,
42
+ warnings: [],
43
+ });
44
+ await writeMaps(workspace, result);
45
+ console.log(`Scanned ${result.files.length} files and ${result.symbols.length} symbols.`);
46
+ const detectedMachineIntegrations = await detectMachineIntegrationRecommendations();
47
+ let recommendedIntegrations = recommendedIntegrationsForInit({
48
+ configuredIntegrations: config.integrations,
49
+ detectedIntegrations: detectedMachineIntegrations,
50
+ });
51
+ let recommendedExtractors = recommendedExtractorsForInit({
52
+ files: result.files,
53
+ configuredExtractors: config.extractors,
54
+ });
55
+ if (shouldPromptForInitIntegrations({
56
+ explicitIntegrationsRequested: names.length > 0,
57
+ configuredIntegrations: config.integrations,
58
+ })) {
59
+ const selected = await promptForInitIntegrations(recommendedIntegrations);
60
+ if (selected.length > 0) {
61
+ const changed = await addIntegrations(workspace, selected, 'always');
62
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
63
+ config = await loadConfig(workspace);
64
+ recommendedIntegrations = recommendedIntegrationsForInit({
65
+ configuredIntegrations: config.integrations,
66
+ detectedIntegrations: detectedMachineIntegrations,
67
+ });
68
+ }
69
+ }
70
+ if (shouldPromptForInitExtractors({
71
+ configuredExtractors: config.extractors,
72
+ })) {
73
+ const selected = await promptForInitExtractors(recommendedExtractors);
74
+ if (selected.length > 0) {
75
+ const changed = await addExtractors(workspace, selected);
76
+ console.log(`Configured extractors: ${changed.map((item) => item.name).join(', ')}`);
77
+ console.log(`Install packages: ${installCommandForExtractors(changed.map((item) => item.packageName))}`);
78
+ config = await loadConfig(workspace);
79
+ recommendedExtractors = recommendedExtractorsForInit({
80
+ files: result.files,
81
+ configuredExtractors: config.extractors,
82
+ });
83
+ }
84
+ }
85
+ console.log('');
86
+ console.log(renderInitSummary({
87
+ files: result.files,
88
+ integrations: config.integrations,
89
+ recommendedIntegrations,
90
+ extractors: config.extractors,
91
+ recommendedExtractors,
92
+ }));
26
93
  }));
27
94
  }
28
95
  function collectOption(value, previous) {
@@ -30,8 +97,11 @@ function collectOption(value, previous) {
30
97
  return previous;
31
98
  }
32
99
  function normalizeIntegrationMode(value) {
33
- if (value === "smart" || value === "always" || value === "manual" || value === "off") {
100
+ if (value === 'smart' ||
101
+ value === 'always' ||
102
+ value === 'manual' ||
103
+ value === 'off') {
34
104
  return value;
35
105
  }
36
- throw new KGraphError("--mode must be smart, always, manual, or off.");
106
+ throw new KGraphError('--mode must be smart, always, manual, or off.');
37
107
  }
@@ -1,2 +1,2 @@
1
- import type { Command } from "commander";
1
+ import type { Command } from 'commander';
2
2
  export declare function registerIntegrateCommand(program: Command): void;
@@ -1,63 +1,75 @@
1
- import { addIntegrations, listIntegrations, removeIntegrations, setIntegrationMode } from "../../integrations/integration-store.js";
2
- import { normalizeIntegrationNames } from "../../integrations/integration-registry.js";
3
- import { assertWorkspace } from "../../storage/kgraph-paths.js";
4
- import { KGraphError, runCommand } from "../errors.js";
1
+ import { normalizeIntegrationNames } from '../../integrations/integration-registry.js';
2
+ import { addIntegrations, listIntegrations, removeIntegrations, setIntegrationMode, } from '../../integrations/integration-store.js';
3
+ import { assertWorkspace } from '../../storage/kgraph-paths.js';
4
+ import { KGraphError, runCommand } from '../errors.js';
5
5
  export function registerIntegrateCommand(program) {
6
- const integrate = program.command("integrate").description("Manage AI tool integrations");
7
- integrate.command("list").description("List configured integrations").action(() => runCommand(async () => {
6
+ const integrate = program
7
+ .command('integrate')
8
+ .description('Manage AI tool integrations');
9
+ integrate
10
+ .command('list')
11
+ .description('List configured integrations')
12
+ .action(() => runCommand(async () => {
8
13
  const workspace = await assertWorkspace(process.cwd());
9
14
  const integrations = await listIntegrations(workspace);
10
15
  if (integrations.length === 0) {
11
- console.log("No integrations configured.");
16
+ console.log('No integrations configured.');
12
17
  return;
13
18
  }
14
19
  for (const integration of integrations) {
15
- console.log(`${integration.name} ${integration.enabled ? "enabled" : "disabled"} ${integration.mode} ${integration.targetPath} ${integration.targetExists ? "present" : "missing"}`);
20
+ console.log(`${integration.name} ${integration.enabled ? 'enabled' : 'disabled'} ${integration.mode} ${integration.targetPath} ${integration.targetExists ? 'present' : 'missing'}`);
16
21
  }
17
22
  }));
18
23
  integrate
19
- .command("add")
20
- .description("Add AI tool integrations")
21
- .argument("<names...>")
22
- .option("--mode <mode>", "smart, always, manual, or off", "always")
24
+ .command('add')
25
+ .description('Add AI tool integrations')
26
+ .argument('<names...>')
27
+ .option('--mode <mode>', 'always, smart, manual, or off', 'always')
23
28
  .action((names, options) => runCommand(async () => {
24
29
  const workspace = await assertWorkspace(process.cwd());
25
30
  const normalized = normalizeIntegrationNames(names);
26
31
  if (normalized.length === 0) {
27
- throw new KGraphError("Provide at least one integration name.");
32
+ throw new KGraphError('Provide at least one integration name.');
28
33
  }
29
34
  const mode = normalizeIntegrationMode(options.mode);
30
35
  const changed = await addIntegrations(workspace, normalized, mode);
31
- console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
36
+ console.log(`Configured integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
32
37
  }));
33
38
  integrate
34
- .command("set")
35
- .description("Set AI tool integration mode")
36
- .argument("<names...>")
37
- .requiredOption("--mode <mode>", "smart, always, manual, or off")
39
+ .command('set')
40
+ .description('Set AI tool integration mode')
41
+ .argument('<names...>')
42
+ .requiredOption('--mode <mode>', 'smart, always, manual, or off')
38
43
  .action((names, options) => runCommand(async () => {
39
44
  const workspace = await assertWorkspace(process.cwd());
40
45
  const normalized = normalizeIntegrationNames(names);
41
46
  if (normalized.length === 0) {
42
- throw new KGraphError("Provide at least one integration name.");
47
+ throw new KGraphError('Provide at least one integration name.');
43
48
  }
44
49
  const mode = normalizeIntegrationMode(options.mode);
45
50
  const changed = await setIntegrationMode(workspace, normalized, mode);
46
- console.log(`Updated integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(", ")}`);
51
+ console.log(`Updated integrations: ${changed.map((item) => `${item.name}:${item.mode}`).join(', ')}`);
47
52
  }));
48
- integrate.command("remove").description("Remove AI tool integrations").argument("<names...>").action((names) => runCommand(async () => {
53
+ integrate
54
+ .command('remove')
55
+ .description('Remove AI tool integrations')
56
+ .argument('<names...>')
57
+ .action((names) => runCommand(async () => {
49
58
  const workspace = await assertWorkspace(process.cwd());
50
59
  const normalized = normalizeIntegrationNames(names);
51
60
  if (normalized.length === 0) {
52
- throw new KGraphError("Provide at least one integration name.");
61
+ throw new KGraphError('Provide at least one integration name.');
53
62
  }
54
63
  const removed = await removeIntegrations(workspace, normalized);
55
- console.log(`Removed integrations: ${removed.join(", ")}`);
64
+ console.log(`Removed integrations: ${removed.join(', ')}`);
56
65
  }));
57
66
  }
58
67
  function normalizeIntegrationMode(value) {
59
- if (value === "smart" || value === "always" || value === "manual" || value === "off") {
68
+ if (value === 'smart' ||
69
+ value === 'always' ||
70
+ value === 'manual' ||
71
+ value === 'off') {
60
72
  return value;
61
73
  }
62
- throw new KGraphError("--mode must be smart, always, manual, or off.");
74
+ throw new KGraphError('--mode must be smart, always, manual, or off.');
63
75
  }
package/dist/cli/help.js CHANGED
@@ -47,6 +47,11 @@ export function renderRootHelp(useColor = supportsColor()) {
47
47
  command('integrate remove cursor', 'Remove KGraph-managed instruction blocks'),
48
48
  command('--mode smart|always|manual|off', 'Control automatic KGraph involvement per integration'),
49
49
  '',
50
+ theme.bold('Extractors'),
51
+ command('extractor list', 'Show configured optional deep extractors'),
52
+ command('extractor add jvm python', 'Configure optional deep extractors and print install commands'),
53
+ command('extractor remove jvm', 'Remove extractor configuration'),
54
+ '',
50
55
  theme.bold('Options'),
51
56
  command('-V, --version', 'Show version'),
52
57
  command('-h, --help', 'Show this help'),
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import { createRequire } from 'node:module';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { registerContextCommand } from './commands/context.js';
7
7
  import { registerDoctorCommand } from './commands/doctor.js';
8
+ import { registerExtractorCommand } from './commands/extractor.js';
8
9
  import { registerHistoryCommand } from './commands/history.js';
9
10
  import { registerImpactCommand } from './commands/impact.js';
10
11
  import { registerInitCommand } from './commands/init.js';
@@ -43,6 +44,7 @@ export function createProgram() {
43
44
  registerUpdateCommand(program);
44
45
  registerContextCommand(program);
45
46
  registerImpactCommand(program);
47
+ registerExtractorCommand(program);
46
48
  registerIntegrateCommand(program);
47
49
  registerVisualizeCommand(program);
48
50
  registerHistoryCommand(program);
@@ -0,0 +1,13 @@
1
+ import type { ExtractorConfig, ExtractorName, IntegrationConfig, IntegrationName } from '../types/config.js';
2
+ import type { InitExtractorRecommendation, InitIntegrationRecommendation } from './init-recommendations.js';
3
+ export declare function shouldPromptForInitIntegrations(options: {
4
+ explicitIntegrationsRequested: boolean;
5
+ configuredIntegrations: Pick<IntegrationConfig, 'name'>[];
6
+ interactive?: boolean;
7
+ }): boolean;
8
+ export declare function promptForInitIntegrations(recommendations: InitIntegrationRecommendation[]): Promise<IntegrationName[]>;
9
+ export declare function shouldPromptForInitExtractors(options: {
10
+ configuredExtractors: Pick<ExtractorConfig, 'name'>[];
11
+ interactive?: boolean;
12
+ }): boolean;
13
+ export declare function promptForInitExtractors(recommendations: InitExtractorRecommendation[]): Promise<ExtractorName[]>;
@@ -0,0 +1,124 @@
1
+ import * as clack from '@clack/prompts';
2
+ import { listExtractorAdapters } from '../extractors/extractor-registry.js';
3
+ import { listIntegrationAdapters } from '../integrations/integration-registry.js';
4
+ // --- Integration prompt ---
5
+ export function shouldPromptForInitIntegrations(options) {
6
+ const interactive = options.interactive ?? isInteractiveTerminal();
7
+ return (interactive &&
8
+ !options.explicitIntegrationsRequested &&
9
+ options.configuredIntegrations.length === 0);
10
+ }
11
+ export async function promptForInitIntegrations(recommendations) {
12
+ const recNames = recommendations.map((item) => item.name);
13
+ const action = await clack.select({
14
+ message: 'AI tool integrations',
15
+ options: [
16
+ ...(recommendations.length > 0
17
+ ? [
18
+ {
19
+ value: 'recommended',
20
+ label: `Use recommended (${recNames.join(', ')})`,
21
+ hint: recommendations
22
+ .map((item) => `${item.name} — ${item.reason}`)
23
+ .join('; '),
24
+ },
25
+ ]
26
+ : []),
27
+ { value: 'custom', label: 'Custom selection' },
28
+ { value: 'skip', label: 'Skip' },
29
+ ],
30
+ });
31
+ if (clack.isCancel(action) || action === 'skip') {
32
+ return [];
33
+ }
34
+ if (action === 'recommended') {
35
+ return recNames;
36
+ }
37
+ const recommendedNames = new Set(recNames);
38
+ const otherAdapters = listIntegrationAdapters().filter((adapter) => !recommendedNames.has(adapter.name));
39
+ const allOptions = [
40
+ ...recommendations.map((rec) => ({
41
+ value: rec.name,
42
+ label: rec.name,
43
+ hint: rec.reason,
44
+ })),
45
+ ...otherAdapters.map((adapter) => ({
46
+ value: adapter.name,
47
+ label: adapter.name,
48
+ })),
49
+ ];
50
+ const selected = await clack.multiselect({
51
+ message: 'Select integrations (space to toggle, enter to confirm)',
52
+ options: allOptions,
53
+ required: false,
54
+ });
55
+ if (clack.isCancel(selected)) {
56
+ return [];
57
+ }
58
+ return selected;
59
+ }
60
+ // --- Extractor prompt ---
61
+ export function shouldPromptForInitExtractors(options) {
62
+ const interactive = options.interactive ?? isInteractiveTerminal();
63
+ return interactive && options.configuredExtractors.length === 0;
64
+ }
65
+ export async function promptForInitExtractors(recommendations) {
66
+ const recNames = recommendations.map((item) => item.name);
67
+ const hasRecommendations = recommendations.length > 0;
68
+ const action = await clack.select({
69
+ message: 'Optional deep language extractors',
70
+ options: [
71
+ ...(hasRecommendations
72
+ ? [
73
+ {
74
+ value: 'recommended',
75
+ label: `Use recommended (${recNames.join(', ')})`,
76
+ hint: recommendations
77
+ .map((item) => `${item.name} for ${item.languages.join(', ')}`)
78
+ .join('; '),
79
+ },
80
+ ]
81
+ : []),
82
+ {
83
+ value: 'custom',
84
+ label: 'Custom selection',
85
+ hint: hasRecommendations
86
+ ? undefined
87
+ : 'no language-specific extractors detected; pick manually',
88
+ },
89
+ { value: 'skip', label: 'Skip' },
90
+ ],
91
+ });
92
+ if (clack.isCancel(action) || action === 'skip') {
93
+ return [];
94
+ }
95
+ if (action === 'recommended') {
96
+ return recNames;
97
+ }
98
+ const recommendedNames = new Set(recNames);
99
+ const otherAdapters = listExtractorAdapters().filter((adapter) => !recommendedNames.has(adapter.name));
100
+ const allOptions = [
101
+ ...recommendations.map((rec) => ({
102
+ value: rec.name,
103
+ label: rec.name,
104
+ hint: `${rec.languages.join(', ')} — ${rec.packageName} (recommended)`,
105
+ })),
106
+ ...otherAdapters.map((adapter) => ({
107
+ value: adapter.name,
108
+ label: adapter.name,
109
+ hint: `${adapter.languages.join(', ')} — ${adapter.packageName}`,
110
+ })),
111
+ ];
112
+ const selected = await clack.multiselect({
113
+ message: 'Select extractors (space to toggle, enter to confirm)',
114
+ options: allOptions,
115
+ required: false,
116
+ });
117
+ if (clack.isCancel(selected)) {
118
+ return [];
119
+ }
120
+ return selected;
121
+ }
122
+ function isInteractiveTerminal() {
123
+ return Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
124
+ }
@@ -0,0 +1,31 @@
1
+ import type { ExtractorConfig, ExtractorName, IntegrationConfig, IntegrationName } from '../types/config.js';
2
+ import type { RepositoryFile } from '../types/maps.js';
3
+ export interface InitIntegrationRecommendation {
4
+ name: IntegrationName;
5
+ reason: string;
6
+ }
7
+ export interface InitExtractorRecommendation {
8
+ name: ExtractorName;
9
+ packageName: string;
10
+ languages: string[];
11
+ }
12
+ interface MachineDetectionContext {
13
+ env?: NodeJS.ProcessEnv;
14
+ homeDir?: string;
15
+ platform?: NodeJS.Platform;
16
+ exists?: (targetPath: string) => Promise<boolean>;
17
+ readDir?: (targetPath: string) => Promise<string[]>;
18
+ localAppData?: string;
19
+ }
20
+ export declare function detectMachineIntegrationRecommendations(context?: MachineDetectionContext): Promise<InitIntegrationRecommendation[]>;
21
+ export declare function recommendedIntegrationsForInit(options: {
22
+ configuredIntegrations: Pick<IntegrationConfig, 'name'>[];
23
+ detectedIntegrations: InitIntegrationRecommendation[];
24
+ }): InitIntegrationRecommendation[];
25
+ export declare function recommendedExtractorsForInit(options: {
26
+ files: RepositoryFile[];
27
+ configuredExtractors: Pick<ExtractorConfig, 'name'>[];
28
+ }): InitExtractorRecommendation[];
29
+ export declare function integrationSetupCommand(recommendations: InitIntegrationRecommendation[]): string | undefined;
30
+ export declare function extractorSetupCommands(recommendations: InitExtractorRecommendation[]): string[];
31
+ export {};