nubos-pilot 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/bin/install.js CHANGED
@@ -14,9 +14,37 @@ const agentsMdMod = require('../lib/install/agents-md.cjs');
14
14
  const codexTomlMod = require('../lib/install/codex-toml.cjs');
15
15
  const runtimeDetectMod = require('../lib/install/runtime-detect.cjs');
16
16
  const backupMod = require('../lib/install/backup.cjs');
17
+ const registryMod = require('../lib/install/runtimes-registry.cjs');
17
18
 
18
19
  const cyan = '\x1b[36m', green = '\x1b[32m', yellow = '\x1b[33m',
19
- red = '\x1b[31m', dim = '\x1b[2m', reset = '\x1b[0m';
20
+ red = '\x1b[31m', blue = '\x1b[38;5;33m',
21
+ dim = '\x1b[2m', bold = '\x1b[1m', reset = '\x1b[0m';
22
+
23
+ const LOGO = [
24
+ ' ███╗ ██╗██╗ ██╗██████╗ ██████╗ ███████╗',
25
+ ' ████╗ ██║██║ ██║██╔══██╗██╔═══██╗██╔════╝',
26
+ ' ██╔██╗ ██║██║ ██║██████╔╝██║ ██║███████╗',
27
+ ' ██║╚██╗██║██║ ██║██╔══██╗██║ ██║╚════██║',
28
+ ' ██║ ╚████║╚██████╔╝██████╔╝╚██████╔╝███████║',
29
+ ' ╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝',
30
+ ];
31
+
32
+ function _printBanner() {
33
+ let pkgVersion = '0.0.0';
34
+ let pkgDesc = '';
35
+ try {
36
+ const pkg = require('../package.json');
37
+ pkgVersion = String(pkg.version || '0.0.0');
38
+ pkgDesc = String(pkg.description || '');
39
+ } catch {}
40
+ process.stderr.write('\n');
41
+ for (const line of LOGO) process.stderr.write(blue + line + reset + '\n');
42
+ process.stderr.write('\n');
43
+ process.stderr.write(' ' + bold + blue + 'Nubos Pilot' + reset
44
+ + dim + ' v' + pkgVersion + reset + '\n');
45
+ if (pkgDesc) process.stderr.write(' ' + dim + pkgDesc + reset + '\n');
46
+ process.stderr.write('\n');
47
+ }
20
48
 
21
49
  const PAYLOAD_SUBPATH = path.join('.claude', 'nubos-pilot');
22
50
  const STATE_SUBPATH = '.nubos-pilot';
@@ -38,16 +66,26 @@ const MANAGED_BLOCK_INNER =
38
66
  + ' for planning and execution.\n\nRun `npx nubos-pilot doctor`'
39
67
  + ' to check install integrity.';
40
68
 
41
- const VALID_AGENTS = ['claude', 'codex', 'gemini', 'opencode'];
69
+ const VALID_AGENTS = registryMod.listRuntimeIds();
42
70
  const VALID_SCOPES = ['local', 'global'];
43
71
 
72
+ function _parseAgentsFlag(value) {
73
+ return String(value || '')
74
+ .split(/[,\s]+/)
75
+ .map((s) => s.trim())
76
+ .filter(Boolean);
77
+ }
78
+
44
79
  function parseInstallFlags(args) {
45
- const flags = { agent: null, scope: null, mcp: false, yes: false };
80
+ const flags = { agent: null, agents: null, scope: null, mcp: false, yes: false };
46
81
  const rest = [];
47
82
  for (let i = 0; i < args.length; i++) {
48
83
  const a = args[i];
49
84
  if (a === '--agent' || a === '-a') { flags.agent = args[++i] || null; continue; }
50
85
  if (a.startsWith('--agent=')) { flags.agent = a.slice('--agent='.length); continue; }
86
+ if (a === '--agents') { flags.agents = _parseAgentsFlag(args[++i]); continue; }
87
+ if (a.startsWith('--agents=')) { flags.agents = _parseAgentsFlag(a.slice('--agents='.length)); continue; }
88
+ if (a === '--all') { flags.agents = VALID_AGENTS.slice(); continue; }
51
89
  if (a === '--scope' || a === '-s') { flags.scope = args[++i] || null; continue; }
52
90
  if (a.startsWith('--scope=')) { flags.scope = a.slice('--scope='.length); continue; }
53
91
  if (a === '--mcp') { flags.mcp = true; continue; }
@@ -59,6 +97,21 @@ function parseInstallFlags(args) {
59
97
  '--agent must be one of: ' + VALID_AGENTS.join(', '),
60
98
  { flag: '--agent', got: flags.agent });
61
99
  }
100
+ if (flags.agents !== null) {
101
+ for (const a of flags.agents) {
102
+ if (!VALID_AGENTS.includes(a)) {
103
+ throw new NubosPilotError('invalid-flag',
104
+ '--agents values must be one of: ' + VALID_AGENTS.join(', '),
105
+ { flag: '--agents', got: a });
106
+ }
107
+ }
108
+ if (flags.agents.length === 0) {
109
+ throw new NubosPilotError('invalid-flag',
110
+ '--agents requires at least one value',
111
+ { flag: '--agents' });
112
+ }
113
+ if (!flags.agent) flags.agent = flags.agents[0];
114
+ }
62
115
  if (flags.scope !== null && !VALID_SCOPES.includes(flags.scope)) {
63
116
  throw new NubosPilotError('invalid-flag',
64
117
  '--scope must be one of: ' + VALID_SCOPES.join(', '),
@@ -123,10 +176,34 @@ function _copyTree(src, dst) {
123
176
  }
124
177
  }
125
178
 
179
+ function _runtimeSelectLabels() {
180
+ return registryMod.RUNTIMES.map((r) => {
181
+ const home = registryMod.runtimeGlobalDir(r).replace(process.env.HOME || '', '~');
182
+ return r.label + ' (' + home + ')';
183
+ });
184
+ }
185
+
126
186
  async function _runInitQuestions(detectedRuntime, askUser, flags) {
127
187
  const f = flags || {};
128
- const runtime = f.agent || (await askUser({ type: 'select', question: 'Welche Runtime nutzt du?',
129
- options: VALID_AGENTS, default: detectedRuntime || 'claude' })).value;
188
+ let runtimes;
189
+ if (f.agents && f.agents.length) {
190
+ runtimes = f.agents.slice();
191
+ } else if (f.agent) {
192
+ runtimes = [f.agent];
193
+ } else {
194
+ const labels = _runtimeSelectLabels();
195
+ const picked = (await askUser({ type: 'multiselect',
196
+ question: yellow + 'Which runtime(s) would you like to install for?' + reset,
197
+ options: labels, default: [(detectedRuntime || 'claude')] })).value;
198
+ runtimes = Array.isArray(picked) && picked.length && typeof picked[0] === 'string'
199
+ && picked[0].includes('(')
200
+ ? picked.map((label) => {
201
+ const idx = labels.indexOf(label);
202
+ return VALID_AGENTS[idx];
203
+ })
204
+ : (Array.isArray(picked) ? picked : [picked]);
205
+ }
206
+ const runtime = runtimes[0];
130
207
  const scope = f.scope || (await askUser({ type: 'select', question: 'Installation scope?',
131
208
  options: VALID_SCOPES, default: 'local' })).value;
132
209
  const model_profile = (await askUser({ type: 'select', question: 'Model-Profile?',
@@ -141,7 +218,7 @@ async function _runInitQuestions(detectedRuntime, askUser, flags) {
141
218
  const plan_checker = (await askUser({ type: 'confirm', question: 'Enable plan_checker?', default: true })).value;
142
219
  const verifier = (await askUser({ type: 'confirm', question: 'Enable verifier?', default: true })).value;
143
220
  const response_language = (await askUser({ type: 'input', question: 'Response language (ISO-639 code)?', default: 'en' })).value;
144
- return { runtime, scope, mcp: !!f.mcp, model_profile, commit_docs, branching_strategy, phase_branch_template,
221
+ return { runtime, runtimes, scope, mcp: !!f.mcp, model_profile, commit_docs, branching_strategy, phase_branch_template,
145
222
  milestone_branch_template, parallelization, research, plan_checker, verifier, response_language };
146
223
  }
147
224
 
@@ -157,7 +234,9 @@ function _repairCodexConfig() {
157
234
  return true;
158
235
  }
159
236
 
160
- function _rewriteManagedMarkdown(projectRoot) {
237
+ const LEGACY_AGENTS = new Set(['claude', 'codex', 'gemini', 'opencode']);
238
+
239
+ function _rewriteManagedMarkdown(projectRoot, runtimes) {
161
240
  const claudePath = path.join(projectRoot, 'CLAUDE.md');
162
241
  const agentsPath = path.join(projectRoot, 'AGENTS.md');
163
242
  const geminiPath = path.join(projectRoot, 'GEMINI.md');
@@ -174,10 +253,12 @@ function _rewriteManagedMarkdown(projectRoot) {
174
253
  } else if (claudeExists) {
175
254
  agentsBase = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'codex');
176
255
  } else {
177
- return;
256
+ agentsBase = null;
257
+ }
258
+ if (agentsBase !== null) {
259
+ const agentsNext = managedBlockMod.rewriteBlock(agentsBase, MANAGED_BLOCK_INNER);
260
+ atomicWriteFileSync(agentsPath, agentsNext);
178
261
  }
179
- const agentsNext = managedBlockMod.rewriteBlock(agentsBase, MANAGED_BLOCK_INNER);
180
- atomicWriteFileSync(agentsPath, agentsNext);
181
262
 
182
263
  let geminiBase;
183
264
  if (fs.existsSync(geminiPath)) {
@@ -185,10 +266,30 @@ function _rewriteManagedMarkdown(projectRoot) {
185
266
  } else if (claudeExists) {
186
267
  geminiBase = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'gemini');
187
268
  } else {
188
- return;
269
+ geminiBase = null;
270
+ }
271
+ if (geminiBase !== null) {
272
+ const geminiNext = managedBlockMod.rewriteBlock(geminiBase, MANAGED_BLOCK_INNER);
273
+ atomicWriteFileSync(geminiPath, geminiNext);
274
+ }
275
+
276
+ const extras = (runtimes || []).filter((id) => !LEGACY_AGENTS.has(id));
277
+ for (const id of extras) {
278
+ const meta = registryMod.getRuntimeMeta(id);
279
+ if (!meta) continue;
280
+ const targetPath = registryMod.runtimeAgentsPath(meta, 'local', projectRoot);
281
+ let base;
282
+ if (fs.existsSync(targetPath)) {
283
+ base = fs.readFileSync(targetPath, 'utf-8');
284
+ } else if (claudeExists) {
285
+ base = agentsMdMod.generateAgentsMd(fs.readFileSync(claudePath, 'utf-8'), 'codex');
286
+ } else {
287
+ base = '';
288
+ }
289
+ const next = managedBlockMod.rewriteBlock(base, MANAGED_BLOCK_INNER);
290
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
291
+ atomicWriteFileSync(targetPath, next);
189
292
  }
190
- const geminiNext = managedBlockMod.rewriteBlock(geminiBase, MANAGED_BLOCK_INNER);
191
- atomicWriteFileSync(geminiPath, geminiNext);
192
293
  }
193
294
 
194
295
  async function runInstall(opts) {
@@ -208,6 +309,7 @@ async function runInstall(opts) {
208
309
 
209
310
  async function _runInstallLocked(ctx) {
210
311
  const { projectRoot, mode, dryRun, askUser, sourceDir, stateDir, flags } = ctx;
312
+ _printBanner();
211
313
  console.error(cyan + '→ nubos-pilot install (mode=' + mode + ')' + reset);
212
314
 
213
315
  const preliminaryScope = (flags && flags.scope) || _readExistingScope(projectRoot) || 'local';
@@ -335,7 +437,8 @@ async function _runInstallLocked(ctx) {
335
437
  }
336
438
  }
337
439
 
338
- _rewriteManagedMarkdown(projectRoot);
440
+ const selectedRuntimes = (initConfig && initConfig.runtimes) || (initConfig ? [initConfig.runtime] : []);
441
+ _rewriteManagedMarkdown(projectRoot, selectedRuntimes);
339
442
 
340
443
  if (initConfig && initConfig.mcp && !dryRun) {
341
444
  try {
@@ -413,8 +516,26 @@ function _runUninstallLocked(projectRoot) {
413
516
 
414
517
  try { fs.rmdirSync(payloadDir); } catch { }
415
518
 
416
- for (const name of ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md']) {
417
- const p = path.join(projectRoot, name);
519
+ const cfgPath = path.join(_stateDirFor(projectRoot), 'config.json');
520
+ let installedRuntimes = [];
521
+ try {
522
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
523
+ installedRuntimes = cfg.runtimes || (cfg.runtime ? [cfg.runtime] : []);
524
+ } catch {}
525
+
526
+ const legacyFiles = ['CLAUDE.md', 'AGENTS.md', 'GEMINI.md'];
527
+ const extraFiles = [];
528
+ for (const id of installedRuntimes) {
529
+ if (LEGACY_AGENTS.has(id)) continue;
530
+ const meta = registryMod.getRuntimeMeta(id);
531
+ if (!meta) continue;
532
+ extraFiles.push(registryMod.runtimeAgentsPath(meta, 'local', projectRoot));
533
+ }
534
+
535
+ const toStrip = legacyFiles
536
+ .map((n) => path.join(projectRoot, n))
537
+ .concat(extraFiles);
538
+ for (const p of toStrip) {
418
539
  if (!fs.existsSync(p)) continue;
419
540
  const stripped = managedBlockMod.stripBlock(fs.readFileSync(p, 'utf-8'));
420
541
  if (!stripped || !stripped.trim()) {
@@ -0,0 +1,197 @@
1
+ 'use strict';
2
+
3
+ const os = require('node:os');
4
+ const path = require('node:path');
5
+
6
+ const RUNTIMES = [
7
+ {
8
+ id: 'claude',
9
+ label: 'Claude Code',
10
+ localDir: '.claude',
11
+ globalDir: ['.claude'],
12
+ envConfigDir: 'CLAUDE_CONFIG_DIR',
13
+ agentsMd: 'CLAUDE.md',
14
+ agentsMdScope: 'project',
15
+ payloadSubdir: 'nubos-pilot',
16
+ },
17
+ {
18
+ id: 'antigravity',
19
+ label: 'Antigravity',
20
+ localDir: '.agent',
21
+ globalDir: ['.gemini', 'antigravity'],
22
+ envConfigDir: 'ANTIGRAVITY_CONFIG_DIR',
23
+ agentsMd: 'AGENTS.md',
24
+ agentsMdScope: 'project',
25
+ payloadSubdir: 'nubos-pilot',
26
+ },
27
+ {
28
+ id: 'augment',
29
+ label: 'Augment',
30
+ localDir: '.augment',
31
+ globalDir: ['.augment'],
32
+ envConfigDir: 'AUGMENT_CONFIG_DIR',
33
+ agentsMd: 'AGENTS.md',
34
+ agentsMdScope: 'project',
35
+ payloadSubdir: 'nubos-pilot',
36
+ },
37
+ {
38
+ id: 'cline',
39
+ label: 'Cline',
40
+ localDir: '.',
41
+ globalDir: ['.cline'],
42
+ envConfigDir: 'CLINE_CONFIG_DIR',
43
+ agentsMd: '.clinerules',
44
+ agentsMdScope: 'project',
45
+ payloadSubdir: '.clinerules-nubos-pilot',
46
+ },
47
+ {
48
+ id: 'codebuddy',
49
+ label: 'CodeBuddy',
50
+ localDir: '.codebuddy',
51
+ globalDir: ['.codebuddy'],
52
+ envConfigDir: 'CODEBUDDY_CONFIG_DIR',
53
+ agentsMd: 'AGENTS.md',
54
+ agentsMdScope: 'project',
55
+ payloadSubdir: 'nubos-pilot',
56
+ },
57
+ {
58
+ id: 'codex',
59
+ label: 'Codex',
60
+ localDir: '.codex',
61
+ globalDir: ['.codex'],
62
+ envConfigDir: 'CODEX_HOME',
63
+ agentsMd: 'AGENTS.md',
64
+ agentsMdScope: 'project',
65
+ payloadSubdir: 'nubos-pilot',
66
+ },
67
+ {
68
+ id: 'copilot',
69
+ label: 'Copilot',
70
+ localDir: '.github',
71
+ globalDir: ['.copilot'],
72
+ envConfigDir: 'COPILOT_CONFIG_DIR',
73
+ agentsMd: 'copilot-instructions.md',
74
+ agentsMdScope: 'dir',
75
+ payloadSubdir: 'nubos-pilot',
76
+ },
77
+ {
78
+ id: 'cursor',
79
+ label: 'Cursor',
80
+ localDir: '.cursor',
81
+ globalDir: ['.cursor'],
82
+ envConfigDir: 'CURSOR_CONFIG_DIR',
83
+ agentsMd: 'rules/nubos-pilot.mdc',
84
+ agentsMdScope: 'dir',
85
+ payloadSubdir: 'nubos-pilot',
86
+ },
87
+ {
88
+ id: 'gemini',
89
+ label: 'Gemini',
90
+ localDir: '.gemini',
91
+ globalDir: ['.gemini'],
92
+ envConfigDir: 'GEMINI_CONFIG_DIR',
93
+ agentsMd: 'GEMINI.md',
94
+ agentsMdScope: 'project',
95
+ payloadSubdir: 'nubos-pilot',
96
+ },
97
+ {
98
+ id: 'kilo',
99
+ label: 'Kilo',
100
+ localDir: '.kilo',
101
+ globalDir: ['.config', 'kilo'],
102
+ envConfigDir: 'KILO_CONFIG_DIR',
103
+ agentsMd: 'AGENTS.md',
104
+ agentsMdScope: 'project',
105
+ payloadSubdir: 'nubos-pilot',
106
+ },
107
+ {
108
+ id: 'opencode',
109
+ label: 'OpenCode',
110
+ localDir: '.opencode',
111
+ globalDir: ['.config', 'opencode'],
112
+ envConfigDir: 'OPENCODE_CONFIG_DIR',
113
+ agentsMd: 'AGENTS.md',
114
+ agentsMdScope: 'dir',
115
+ payloadSubdir: 'nubos-pilot',
116
+ },
117
+ {
118
+ id: 'qwen',
119
+ label: 'Qwen Code',
120
+ localDir: '.qwen',
121
+ globalDir: ['.qwen'],
122
+ envConfigDir: 'QWEN_CONFIG_DIR',
123
+ agentsMd: 'AGENTS.md',
124
+ agentsMdScope: 'project',
125
+ payloadSubdir: 'nubos-pilot',
126
+ },
127
+ {
128
+ id: 'trae',
129
+ label: 'Trae',
130
+ localDir: '.trae',
131
+ globalDir: ['.trae'],
132
+ envConfigDir: 'TRAE_CONFIG_DIR',
133
+ agentsMd: 'AGENTS.md',
134
+ agentsMdScope: 'project',
135
+ payloadSubdir: 'nubos-pilot',
136
+ },
137
+ {
138
+ id: 'windsurf',
139
+ label: 'Windsurf',
140
+ localDir: '.windsurf',
141
+ globalDir: ['.codeium', 'windsurf'],
142
+ envConfigDir: 'WINDSURF_CONFIG_DIR',
143
+ agentsMd: '.windsurfrules',
144
+ agentsMdScope: 'project',
145
+ payloadSubdir: '.windsurf-nubos-pilot',
146
+ },
147
+ ];
148
+
149
+ const RUNTIME_INDEX = new Map(RUNTIMES.map((r) => [r.id, r]));
150
+
151
+ function listRuntimeIds() {
152
+ return RUNTIMES.map((r) => r.id);
153
+ }
154
+
155
+ function getRuntimeMeta(id) {
156
+ return RUNTIME_INDEX.get(id) || null;
157
+ }
158
+
159
+ function runtimeGlobalDir(meta) {
160
+ if (meta.envConfigDir && process.env[meta.envConfigDir]) {
161
+ const v = process.env[meta.envConfigDir];
162
+ return v.startsWith('~') ? path.join(os.homedir(), v.slice(1)) : v;
163
+ }
164
+ return path.join(os.homedir(), ...(meta.globalDir || [meta.localDir]));
165
+ }
166
+
167
+ function runtimeLocalDir(meta, projectRoot) {
168
+ return path.join(projectRoot, meta.localDir || '.');
169
+ }
170
+
171
+ function runtimeConfigDir(meta, scope, projectRoot) {
172
+ return scope === 'global'
173
+ ? runtimeGlobalDir(meta)
174
+ : runtimeLocalDir(meta, projectRoot);
175
+ }
176
+
177
+ function runtimePayloadDir(meta, scope, projectRoot) {
178
+ return path.join(runtimeConfigDir(meta, scope, projectRoot), meta.payloadSubdir);
179
+ }
180
+
181
+ function runtimeAgentsPath(meta, scope, projectRoot) {
182
+ if (meta.agentsMdScope === 'dir') {
183
+ return path.join(runtimeConfigDir(meta, scope, projectRoot), meta.agentsMd);
184
+ }
185
+ return path.join(projectRoot, meta.agentsMd);
186
+ }
187
+
188
+ module.exports = {
189
+ RUNTIMES,
190
+ listRuntimeIds,
191
+ getRuntimeMeta,
192
+ runtimeGlobalDir,
193
+ runtimeLocalDir,
194
+ runtimeConfigDir,
195
+ runtimePayloadDir,
196
+ runtimeAgentsPath,
197
+ };
@@ -6,7 +6,11 @@ const REQUIRED_KEYS = ['name', 'detectHints', 'capabilities', 'paths', 'askUser'
6
6
  const CAP_KEYS = ['askUserQuestion', 'slashCommands', 'agentsMd', 'textMode', 'modelResolution'];
7
7
  const VALID_TEXT_MODE = ['auto', 'force', 'off'];
8
8
  const VALID_MODEL_RES = ['explicit', 'inherit', 'profile'];
9
- const VALID_AGENTS_MD = [null, 'AGENTS.md', 'GEMINI.md', 'CLAUDE.md'];
9
+ const VALID_AGENTS_MD = [
10
+ null, 'AGENTS.md', 'GEMINI.md', 'CLAUDE.md',
11
+ '.clinerules', '.windsurfrules',
12
+ 'rules/nubos-pilot.mdc', 'copilot-instructions.md',
13
+ ];
10
14
 
11
15
  for (const name of listRuntimes()) {
12
16
  test('RT-contract(' + name + '): exports all required keys', () => {
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'antigravity',
14
+ detectHints: {
15
+ env: ['ANTIGRAVITY_CONFIG_DIR'],
16
+ pathBinary: 'antigravity',
17
+ diskMarkers: ['.agent/', '.gemini/antigravity/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.agent/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von Antigravity konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'augment',
14
+ detectHints: {
15
+ env: ['AUGMENT_CONFIG_DIR'],
16
+ pathBinary: 'augment',
17
+ diskMarkers: ['.augment/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.augment/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von Augment konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'cline',
14
+ detectHints: {
15
+ env: ['CLINE_CONFIG_DIR'],
16
+ pathBinary: 'cline',
17
+ diskMarkers: ['.clinerules'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: '.clinerules',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.clinerules-nubos-pilot/',
28
+ config: null,
29
+ agentsMd: '.clinerules',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei (.clinerules) wird von Cline konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'codebuddy',
14
+ detectHints: {
15
+ env: ['CODEBUDDY_CONFIG_DIR'],
16
+ pathBinary: 'codebuddy',
17
+ diskMarkers: ['.codebuddy/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.codebuddy/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von CodeBuddy konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'copilot',
14
+ detectHints: {
15
+ env: ['COPILOT_CONFIG_DIR'],
16
+ pathBinary: 'copilot',
17
+ diskMarkers: ['.github/copilot-instructions.md', '.copilot/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'copilot-instructions.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.github/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: '.github/copilot-instructions.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei (.github/copilot-instructions.md) wird von GitHub Copilot konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'cursor',
14
+ detectHints: {
15
+ env: ['CURSOR_CONFIG_DIR'],
16
+ pathBinary: 'cursor',
17
+ diskMarkers: ['.cursor/', '.cursorrules'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'rules/nubos-pilot.mdc',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.cursor/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: '.cursor/rules/nubos-pilot.mdc',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei (.cursor/rules/nubos-pilot.mdc) wird von Cursor konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -3,7 +3,11 @@ const path = require('node:path');
3
3
  const { NubosPilotError } = require('../core.cjs');
4
4
  const { getRuntime: _askuserGetRuntime } = require('../askuser.cjs');
5
5
 
6
- const KNOWN_RUNTIMES = ['claude', 'opencode', 'codex', 'gemini'];
6
+ const KNOWN_RUNTIMES = [
7
+ 'claude', 'antigravity', 'augment', 'cline', 'codebuddy',
8
+ 'codex', 'copilot', 'cursor', 'gemini', 'kilo',
9
+ 'opencode', 'qwen', 'trae', 'windsurf',
10
+ ];
7
11
 
8
12
  function listRuntimes() {
9
13
  return KNOWN_RUNTIMES.slice();
@@ -47,15 +47,21 @@ function writeConfig(cwd, json) {
47
47
  fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify(json), 'utf-8');
48
48
  }
49
49
 
50
- test('RTI-1: listRuntimes returns four known runtimes in canonical order', () => {
51
- assert.deepEqual(rt.listRuntimes(), ['claude', 'opencode', 'codex', 'gemini']);
50
+ const EXPECTED_RUNTIMES = [
51
+ 'claude', 'antigravity', 'augment', 'cline', 'codebuddy',
52
+ 'codex', 'copilot', 'cursor', 'gemini', 'kilo',
53
+ 'opencode', 'qwen', 'trae', 'windsurf',
54
+ ];
55
+
56
+ test('RTI-1: listRuntimes returns 14 known runtimes in canonical order', () => {
57
+ assert.deepEqual(rt.listRuntimes(), EXPECTED_RUNTIMES);
52
58
  });
53
59
 
54
60
  test('RTI-2: listRuntimes returns defensive copy — mutation does not leak', () => {
55
61
  const a = rt.listRuntimes();
56
62
  a.push('pwned');
57
63
  const b = rt.listRuntimes();
58
- assert.deepEqual(b, ['claude', 'opencode', 'codex', 'gemini']);
64
+ assert.deepEqual(b, EXPECTED_RUNTIMES);
59
65
  });
60
66
 
61
67
  test('RTI-3: getAdapter("nonexistent") throws NubosPilotError with code runtime-unknown', () => {
@@ -71,7 +77,7 @@ test('RTI-4: getAdapter unknown-runtime error details.known lists KNOWN_RUNTIMES
71
77
  assert.fail('should throw');
72
78
  } catch (err) {
73
79
  assert.equal(err.code, 'runtime-unknown');
74
- assert.deepEqual(err.details.known, ['claude', 'opencode', 'codex', 'gemini']);
80
+ assert.deepEqual(err.details.known, EXPECTED_RUNTIMES);
75
81
  assert.equal(err.details.name, 'mystery');
76
82
  }
77
83
  });
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'kilo',
14
+ detectHints: {
15
+ env: ['KILO_CONFIG_DIR'],
16
+ pathBinary: 'kilo',
17
+ diskMarkers: ['.kilo/', '.config/kilo/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.kilo/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von Kilo konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'qwen',
14
+ detectHints: {
15
+ env: ['QWEN_CONFIG_DIR'],
16
+ pathBinary: 'qwen',
17
+ diskMarkers: ['.qwen/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.qwen/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von Qwen Code konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'trae',
14
+ detectHints: {
15
+ env: ['TRAE_CONFIG_DIR'],
16
+ pathBinary: 'trae',
17
+ diskMarkers: ['.trae/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: 'AGENTS.md',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.trae/nubos-pilot/',
28
+ config: null,
29
+ agentsMd: 'AGENTS.md',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei wird von Trae konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
@@ -0,0 +1,35 @@
1
+ const { askUserReadline } = require('./_readline.cjs');
2
+
3
+ async function askUser(spec) {
4
+ return askUserReadline({
5
+ type: spec && spec.type,
6
+ question: spec && spec.question,
7
+ options: spec && spec.options,
8
+ def: spec ? spec.default : undefined,
9
+ });
10
+ }
11
+
12
+ module.exports = {
13
+ name: 'windsurf',
14
+ detectHints: {
15
+ env: ['WINDSURF_CONFIG_DIR'],
16
+ pathBinary: 'windsurf',
17
+ diskMarkers: ['.windsurf/', '.windsurfrules', '.codeium/windsurf/'],
18
+ },
19
+ capabilities: {
20
+ askUserQuestion: false,
21
+ slashCommands: false,
22
+ agentsMd: '.windsurfrules',
23
+ textMode: 'auto',
24
+ modelResolution: 'inherit',
25
+ },
26
+ paths: {
27
+ payload: '.windsurf-nubos-pilot/',
28
+ config: null,
29
+ agentsMd: '.windsurfrules',
30
+ },
31
+ runtimeNotice:
32
+ '> **Runtime-Hinweis:** Diese Datei (.windsurfrules) wird von Windsurf konsumiert. '
33
+ + 'Interaktive Prompts laufen über readline (stderr).',
34
+ askUser,
35
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nubos-pilot",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "AI-driven planning and execution tool for code projects",
5
5
  "homepage": "https://github.com/Nubos-AI/nubos-pilot",
6
6
  "repository": {