@adityaaria/spark 6.0.3 → 6.0.4
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 +1 -1
- package/docs/porting-to-a-new-harness.md +1 -2
- package/package.json +1 -1
- package/src/cli/output.js +2 -0
- package/src/installer/detect.js +40 -34
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Installation differs by harness. If you use more than one, install SPARK separat
|
|
|
25
25
|
|
|
26
26
|
### NPM Meta-Installer
|
|
27
27
|
|
|
28
|
-
If you want one command that
|
|
28
|
+
If you want one command that asks which harness you are using, run:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
31
|
npx @adityaaria/spark install
|
|
@@ -5,8 +5,7 @@ agent runner that isn't Claude Code — so that SPARK skills auto-trigger
|
|
|
5
5
|
there the same way they do natively.
|
|
6
6
|
|
|
7
7
|
For end users, the preferred front door is now `npx @adityaaria/spark install`; the
|
|
8
|
-
CLI
|
|
9
|
-
appropriate adapter.
|
|
8
|
+
CLI asks which harness to target, then delegates to the appropriate adapter.
|
|
10
9
|
|
|
11
10
|
It is written in two layers. **Part 1–3** explain how the system works and how
|
|
12
11
|
to tell whether a harness can be supported at all; read these before you touch
|
package/package.json
CHANGED
package/src/cli/output.js
CHANGED
|
@@ -7,5 +7,7 @@ export function printHelp() {
|
|
|
7
7
|
printLine('');
|
|
8
8
|
printLine('Usage: npx @adityaaria/spark install [--harness <name>] [--dry-run] [--yes] [--verbose]');
|
|
9
9
|
printLine('');
|
|
10
|
+
printLine('Without --harness, the installer will ask which AI assistance to target.');
|
|
11
|
+
printLine('');
|
|
10
12
|
printLine('Supported harnesses: Codex, Cursor, Antigravity, Copilot, OpenCode, Gemini, Pi');
|
|
11
13
|
}
|
package/src/installer/detect.js
CHANGED
|
@@ -4,9 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import { getAdapterById, listAdapters } from './registry.js';
|
|
5
5
|
import { InstallerError } from './errors.js';
|
|
6
6
|
|
|
7
|
-
const HIGH_CONFIDENCE_SCORE = 80;
|
|
8
|
-
const AMBIGUOUS_MARGIN = 20;
|
|
9
|
-
|
|
10
7
|
export function detectHarnessCandidates({ env = process.env, fsImpl = fs } = {}) {
|
|
11
8
|
const homeDir = os.homedir();
|
|
12
9
|
const adapters = listAdapters();
|
|
@@ -31,25 +28,22 @@ export async function chooseHarness({
|
|
|
31
28
|
return { adapter, source: 'forced', candidates: [] };
|
|
32
29
|
}
|
|
33
30
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
throw new InstallerError(
|
|
37
|
-
`Could not detect a supported harness. Supported harnesses: ${listAdapters().map((adapter) => adapter.label).join(', ')}`
|
|
38
|
-
);
|
|
31
|
+
if (!prompt) {
|
|
32
|
+
throw new InstallerError('No harness selected. Re-run with --harness or use an interactive terminal.');
|
|
39
33
|
}
|
|
40
34
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return { adapter, source: 'auto', candidates };
|
|
44
|
-
}
|
|
35
|
+
const detectedCandidates = detectHarnessCandidates({ env, fsImpl: fs });
|
|
36
|
+
const candidates = buildPromptCandidates(detectedCandidates);
|
|
45
37
|
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
let validationMessage = null;
|
|
39
|
+
while (true) {
|
|
40
|
+
const answer = await prompt(renderPrompt(candidates, validationMessage));
|
|
41
|
+
const adapter = resolvePromptAnswer(answer, candidates);
|
|
42
|
+
if (adapter) {
|
|
43
|
+
return { adapter, source: 'prompt', candidates };
|
|
44
|
+
}
|
|
45
|
+
validationMessage = `Unsupported choice: ${answer || '(empty)'}. Please choose a number or harness name from the list.`;
|
|
48
46
|
}
|
|
49
|
-
|
|
50
|
-
const answer = await prompt(renderPrompt(candidates));
|
|
51
|
-
const adapter = resolvePromptAnswer(answer, candidates);
|
|
52
|
-
return { adapter, source: 'prompt', candidates };
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
function scoreAdapter(adapter, env, fsImpl, homeDir) {
|
|
@@ -94,12 +88,16 @@ function candidateConfigPaths(homeDir, env, configPath) {
|
|
|
94
88
|
paths.push(path.join(env.OPENCODE_CONFIG_DIR, 'opencode.json'));
|
|
95
89
|
}
|
|
96
90
|
if (configPath === 'GEMINI.md') {
|
|
97
|
-
paths.push(
|
|
98
|
-
|
|
91
|
+
paths.push(
|
|
92
|
+
path.join(homeDir, '.gemini', 'GEMINI.md'),
|
|
93
|
+
path.join(homeDir, 'GEMINI.md')
|
|
94
|
+
);
|
|
99
95
|
}
|
|
100
96
|
if (configPath === 'gemini-extension.json') {
|
|
101
|
-
paths.push(
|
|
102
|
-
|
|
97
|
+
paths.push(
|
|
98
|
+
path.join(homeDir, 'gemini-extension.json'),
|
|
99
|
+
path.join(homeDir, '.gemini', 'gemini-extension.json')
|
|
100
|
+
);
|
|
103
101
|
}
|
|
104
102
|
return paths;
|
|
105
103
|
}
|
|
@@ -130,15 +128,27 @@ function commandExists(commandName, env, fsImpl) {
|
|
|
130
128
|
return false;
|
|
131
129
|
}
|
|
132
130
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
function buildPromptCandidates(detectedCandidates) {
|
|
132
|
+
const allAdapters = listAdapters();
|
|
133
|
+
const detectedIds = new Set(detectedCandidates.map((candidate) => candidate.id));
|
|
134
|
+
|
|
135
|
+
return [
|
|
136
|
+
...detectedCandidates,
|
|
137
|
+
...allAdapters
|
|
138
|
+
.filter((adapter) => !detectedIds.has(adapter.id))
|
|
139
|
+
.map((adapter) => ({
|
|
140
|
+
id: adapter.id,
|
|
141
|
+
label: adapter.label,
|
|
142
|
+
score: 0,
|
|
143
|
+
reasons: [],
|
|
144
|
+
})),
|
|
145
|
+
];
|
|
137
146
|
}
|
|
138
147
|
|
|
139
|
-
function renderPrompt(candidates) {
|
|
148
|
+
function renderPrompt(candidates, validationMessage = null) {
|
|
140
149
|
return [
|
|
141
|
-
|
|
150
|
+
...(validationMessage ? [validationMessage] : []),
|
|
151
|
+
'Install SPARK for which AI assistance?',
|
|
142
152
|
...candidates.map((candidate, index) => `${index + 1}. ${candidate.label} (${candidate.id})`),
|
|
143
153
|
'Enter a number or harness name:',
|
|
144
154
|
].join('\n');
|
|
@@ -146,9 +156,7 @@ function renderPrompt(candidates) {
|
|
|
146
156
|
|
|
147
157
|
function resolvePromptAnswer(answer, candidates) {
|
|
148
158
|
const value = normalizeHarness(answer);
|
|
149
|
-
if (!value)
|
|
150
|
-
throw new InstallerError('No harness selected.');
|
|
151
|
-
}
|
|
159
|
+
if (!value) return null;
|
|
152
160
|
|
|
153
161
|
const numeric = Number(value);
|
|
154
162
|
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= candidates.length) {
|
|
@@ -156,9 +164,7 @@ function resolvePromptAnswer(answer, candidates) {
|
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
const exact = candidates.find((candidate) => candidate.id === value || candidate.label.toLowerCase() === value);
|
|
159
|
-
if (!exact)
|
|
160
|
-
throw new InstallerError(`Unsupported harness choice: ${answer}`);
|
|
161
|
-
}
|
|
167
|
+
if (!exact) return null;
|
|
162
168
|
|
|
163
169
|
return getAdapterById(exact.id);
|
|
164
170
|
}
|