nubos-pilot 0.2.2 → 0.3.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
|
@@ -15,6 +15,7 @@ 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
17
|
const registryMod = require('../lib/install/runtimes-registry.cjs');
|
|
18
|
+
const runtimeAssetsMod = require('../lib/install/runtime-assets.cjs');
|
|
18
19
|
|
|
19
20
|
const cyan = '\x1b[36m', green = '\x1b[32m', yellow = '\x1b[33m',
|
|
20
21
|
red = '\x1b[31m', blue = '\x1b[38;5;33m',
|
|
@@ -53,6 +54,8 @@ const OPENCODE_SUBPATH = path.join('.opencode', 'nubos-pilot');
|
|
|
53
54
|
const OPENCODE_MANIFEST_PREFIX = '.opencode/nubos-pilot/';
|
|
54
55
|
const SOURCE_OPENCODE_DIR = path.join(__dirname, '..', 'templates', 'opencode', 'payload');
|
|
55
56
|
const OPENCODE_JSON_TEMPLATE = path.join(__dirname, '..', 'templates', 'opencode', 'opencode.json');
|
|
57
|
+
const SOURCE_WORKFLOWS_DIR = path.join(__dirname, '..', 'workflows');
|
|
58
|
+
const SOURCE_AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
|
56
59
|
|
|
57
60
|
function _autoAskUser(spec) {
|
|
58
61
|
return Promise.resolve({
|
|
@@ -149,6 +152,17 @@ function _readExistingScope(projectRoot) {
|
|
|
149
152
|
} catch { return null; }
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
function _readExistingRuntimes(projectRoot) {
|
|
156
|
+
const cfgPath = path.join(_stateDirFor(projectRoot), 'config.json');
|
|
157
|
+
if (!fs.existsSync(cfgPath)) return null;
|
|
158
|
+
try {
|
|
159
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf-8'));
|
|
160
|
+
if (Array.isArray(cfg.runtimes) && cfg.runtimes.length) return cfg.runtimes.slice();
|
|
161
|
+
if (cfg.runtime) return [cfg.runtime];
|
|
162
|
+
return null;
|
|
163
|
+
} catch { return null; }
|
|
164
|
+
}
|
|
165
|
+
|
|
152
166
|
function detectMode(projectRoot, scope) {
|
|
153
167
|
const s = scope || _readExistingScope(projectRoot) || 'local';
|
|
154
168
|
const payloadDir = _payloadDirFor(projectRoot, s);
|
|
@@ -344,13 +358,31 @@ async function _runInstallLocked(ctx) {
|
|
|
344
358
|
try { pkgVersion = String(require('../package.json').version || '0.0.0'); } catch {}
|
|
345
359
|
const newManifest = manifestMod.buildManifest(tmp, pkgVersion);
|
|
346
360
|
|
|
361
|
+
const selectedRuntimesEarly = (initConfig && initConfig.runtimes)
|
|
362
|
+
|| (initConfig ? [initConfig.runtime] : null)
|
|
363
|
+
|| _readExistingRuntimes(projectRoot)
|
|
364
|
+
|| [];
|
|
365
|
+
const opencodeSelected = selectedRuntimesEarly.includes('opencode');
|
|
366
|
+
|
|
367
|
+
const assetPlans = runtimeAssetsMod.planRuntimeAssets({
|
|
368
|
+
selectedRuntimes: selectedRuntimesEarly,
|
|
369
|
+
scope: resolvedScope,
|
|
370
|
+
projectRoot,
|
|
371
|
+
workflowsDir: SOURCE_WORKFLOWS_DIR,
|
|
372
|
+
agentsDir: SOURCE_AGENTS_DIR,
|
|
373
|
+
});
|
|
374
|
+
const assetEntries = runtimeAssetsMod.manifestEntriesForPlans(assetPlans);
|
|
375
|
+
for (const k of Object.keys(assetEntries)) {
|
|
376
|
+
newManifest.files[k] = assetEntries[k];
|
|
377
|
+
}
|
|
378
|
+
|
|
347
379
|
const opencodeTarget = _opencodePayloadDirFor(projectRoot, resolvedScope);
|
|
348
380
|
const opencodeManifestPrefix = _opencodeManifestPrefix(resolvedScope);
|
|
349
381
|
const opencodeTmp = path.join(stateDir, '.opencode.tmp');
|
|
350
382
|
try { fs.rmSync(opencodeTmp, { recursive: true, force: true }); } catch {}
|
|
351
383
|
try {
|
|
352
384
|
let opencodeManifest = null;
|
|
353
|
-
if (fs.existsSync(SOURCE_OPENCODE_DIR)) {
|
|
385
|
+
if (opencodeSelected && fs.existsSync(SOURCE_OPENCODE_DIR)) {
|
|
354
386
|
_copyTree(SOURCE_OPENCODE_DIR, opencodeTmp);
|
|
355
387
|
opencodeManifest = manifestMod.buildManifest(opencodeTmp, pkgVersion);
|
|
356
388
|
for (const rel of Object.keys(opencodeManifest.files)) {
|
|
@@ -388,7 +420,7 @@ async function _runInstallLocked(ctx) {
|
|
|
388
420
|
wouldWrite: Object.keys(newManifest.files).length,
|
|
389
421
|
wouldBackup: backupLog.length, wouldDelete: diff.stale.length,
|
|
390
422
|
wouldWriteGemini: true,
|
|
391
|
-
wouldWriteOpencodeJson: !fs.existsSync(path.join(projectRoot, 'opencode.json')),
|
|
423
|
+
wouldWriteOpencodeJson: opencodeSelected && !fs.existsSync(path.join(projectRoot, 'opencode.json')),
|
|
392
424
|
stale: diff.stale, changed: diff.changed, added: diff.added };
|
|
393
425
|
process.stdout.write(JSON.stringify(summary, null, 2) + '\n');
|
|
394
426
|
try { stagingMod.cleanStaleStaging(payloadBase); } catch {}
|
|
@@ -436,11 +468,31 @@ async function _runInstallLocked(ctx) {
|
|
|
436
468
|
try { fs.unlinkSync(relFs); } catch {}
|
|
437
469
|
}
|
|
438
470
|
}
|
|
471
|
+
} else if (!opencodeSelected && fs.existsSync(opencodeTarget)) {
|
|
472
|
+
try { fs.rmSync(opencodeTarget, { recursive: true, force: true }); } catch {}
|
|
473
|
+
const opencodeParent = path.dirname(opencodeTarget);
|
|
474
|
+
try { fs.rmdirSync(opencodeParent); } catch {}
|
|
475
|
+
const projectOpencodeJson = path.join(projectRoot, 'opencode.json');
|
|
476
|
+
if (fs.existsSync(projectOpencodeJson) && fs.existsSync(OPENCODE_JSON_TEMPLATE)) {
|
|
477
|
+
try {
|
|
478
|
+
const template = fs.readFileSync(OPENCODE_JSON_TEMPLATE, 'utf-8');
|
|
479
|
+
const existing = fs.readFileSync(projectOpencodeJson, 'utf-8');
|
|
480
|
+
if (existing === template) fs.unlinkSync(projectOpencodeJson);
|
|
481
|
+
} catch {}
|
|
482
|
+
}
|
|
439
483
|
}
|
|
440
484
|
|
|
441
485
|
const selectedRuntimes = (initConfig && initConfig.runtimes) || (initConfig ? [initConfig.runtime] : []);
|
|
442
486
|
_rewriteManagedMarkdown(projectRoot, selectedRuntimes);
|
|
443
487
|
|
|
488
|
+
if (assetPlans.length) {
|
|
489
|
+
runtimeAssetsMod.writeRuntimeAssets(assetPlans);
|
|
490
|
+
}
|
|
491
|
+
const assetStale = diff.stale.filter(runtimeAssetsMod.isAssetManifestKey);
|
|
492
|
+
if (assetStale.length) {
|
|
493
|
+
runtimeAssetsMod.removeStaleAssets(assetStale, resolvedScope, projectRoot);
|
|
494
|
+
}
|
|
495
|
+
|
|
444
496
|
if (initConfig && initConfig.mcp && !dryRun) {
|
|
445
497
|
try {
|
|
446
498
|
const mcpWriter = require('../lib/install/mcp-writer.cjs');
|
|
@@ -455,10 +507,12 @@ async function _runInstallLocked(ctx) {
|
|
|
455
507
|
}
|
|
456
508
|
}
|
|
457
509
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
510
|
+
if (opencodeSelected) {
|
|
511
|
+
const projectOpencodeJson = path.join(projectRoot, 'opencode.json');
|
|
512
|
+
if (!fs.existsSync(projectOpencodeJson) && fs.existsSync(OPENCODE_JSON_TEMPLATE)) {
|
|
513
|
+
const template = fs.readFileSync(OPENCODE_JSON_TEMPLATE, 'utf-8');
|
|
514
|
+
atomicWriteFileSync(projectOpencodeJson, template);
|
|
515
|
+
}
|
|
462
516
|
}
|
|
463
517
|
|
|
464
518
|
try { _repairCodexConfig(); } catch (err) {
|
|
@@ -503,15 +557,34 @@ function _runUninstallLocked(projectRoot) {
|
|
|
503
557
|
}
|
|
504
558
|
}
|
|
505
559
|
|
|
560
|
+
const payloadBase = scope === 'global' ? os.homedir() : projectRoot;
|
|
506
561
|
let removed = 0;
|
|
562
|
+
const assetDirs = new Set();
|
|
507
563
|
for (const rel of Object.keys(manifest.files)) {
|
|
508
|
-
const
|
|
509
|
-
|
|
564
|
+
const isAsset = runtimeAssetsMod.isAssetManifestKey(rel);
|
|
565
|
+
const abs = isAsset ? path.join(payloadBase, rel) : path.join(payloadDir, rel);
|
|
566
|
+
try {
|
|
567
|
+
fs.unlinkSync(abs);
|
|
568
|
+
removed++;
|
|
569
|
+
if (isAsset) assetDirs.add(path.dirname(abs));
|
|
570
|
+
} catch (err) {
|
|
510
571
|
if (err && err.code !== 'ENOENT') {
|
|
511
572
|
console.error(yellow + ' [uninstall] ' + rel + ' not removed: ' + err.message + reset);
|
|
512
573
|
}
|
|
513
574
|
}
|
|
514
575
|
}
|
|
576
|
+
const sortedDirs = Array.from(assetDirs).sort((a, b) => b.length - a.length);
|
|
577
|
+
for (const dir of sortedDirs) {
|
|
578
|
+
let cur = dir;
|
|
579
|
+
while (cur && cur.startsWith(payloadBase) && cur !== payloadBase) {
|
|
580
|
+
try {
|
|
581
|
+
const entries = fs.readdirSync(cur);
|
|
582
|
+
if (entries.length > 0) break;
|
|
583
|
+
fs.rmdirSync(cur);
|
|
584
|
+
} catch { break; }
|
|
585
|
+
cur = path.dirname(cur);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
515
588
|
|
|
516
589
|
try { fs.unlinkSync(path.join(payloadDir, '.manifest.json')); } catch {}
|
|
517
590
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const os = require('node:os');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const crypto = require('node:crypto');
|
|
7
|
+
const registryMod = require('./runtimes-registry.cjs');
|
|
8
|
+
|
|
9
|
+
function _hashFile(file) {
|
|
10
|
+
const h = crypto.createHash('sha256');
|
|
11
|
+
h.update(fs.readFileSync(file));
|
|
12
|
+
return h.digest('hex');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function _listMarkdown(dir) {
|
|
16
|
+
if (!fs.existsSync(dir)) return [];
|
|
17
|
+
return fs.readdirSync(dir)
|
|
18
|
+
.filter((n) => n.endsWith('.md'))
|
|
19
|
+
.sort();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function _payloadBase(scope, projectRoot) {
|
|
23
|
+
return scope === 'global' ? os.homedir() : projectRoot;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _toPosix(p) {
|
|
27
|
+
return p.split(path.sep).join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function planRuntimeAssets({ selectedRuntimes, scope, projectRoot, workflowsDir, agentsDir }) {
|
|
31
|
+
const base = _payloadBase(scope, projectRoot);
|
|
32
|
+
const workflows = _listMarkdown(workflowsDir);
|
|
33
|
+
const agents = _listMarkdown(agentsDir);
|
|
34
|
+
const plans = [];
|
|
35
|
+
for (const id of selectedRuntimes || []) {
|
|
36
|
+
const meta = registryMod.getRuntimeMeta(id);
|
|
37
|
+
if (!meta) continue;
|
|
38
|
+
const configDir = registryMod.runtimeConfigDir(meta, scope, projectRoot);
|
|
39
|
+
if (meta.commandsSubdir) {
|
|
40
|
+
for (const file of workflows) {
|
|
41
|
+
const targetFile = path.join(configDir, meta.commandsSubdir, file);
|
|
42
|
+
plans.push({
|
|
43
|
+
runtime: id,
|
|
44
|
+
kind: 'command',
|
|
45
|
+
sourceFile: path.join(workflowsDir, file),
|
|
46
|
+
targetFile,
|
|
47
|
+
manifestKey: _toPosix(path.relative(base, targetFile)),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (meta.agentsSubdir) {
|
|
52
|
+
for (const file of agents) {
|
|
53
|
+
const targetFile = path.join(configDir, meta.agentsSubdir, file);
|
|
54
|
+
plans.push({
|
|
55
|
+
runtime: id,
|
|
56
|
+
kind: 'agent',
|
|
57
|
+
sourceFile: path.join(agentsDir, file),
|
|
58
|
+
targetFile,
|
|
59
|
+
manifestKey: _toPosix(path.relative(base, targetFile)),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return plans;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function manifestEntriesForPlans(plans) {
|
|
68
|
+
const entries = Object.create(null);
|
|
69
|
+
for (const plan of plans) {
|
|
70
|
+
entries[plan.manifestKey] = _hashFile(plan.sourceFile);
|
|
71
|
+
}
|
|
72
|
+
return entries;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function writeRuntimeAssets(plans) {
|
|
76
|
+
const written = [];
|
|
77
|
+
for (const plan of plans) {
|
|
78
|
+
fs.mkdirSync(path.dirname(plan.targetFile), { recursive: true });
|
|
79
|
+
fs.copyFileSync(plan.sourceFile, plan.targetFile);
|
|
80
|
+
written.push(plan.targetFile);
|
|
81
|
+
}
|
|
82
|
+
return written;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function removeStaleAssets(staleKeys, scope, projectRoot) {
|
|
86
|
+
const base = _payloadBase(scope, projectRoot);
|
|
87
|
+
const removed = [];
|
|
88
|
+
const dirs = new Set();
|
|
89
|
+
for (const key of staleKeys || []) {
|
|
90
|
+
if (!_isAssetKey(key)) continue;
|
|
91
|
+
const abs = path.join(base, key);
|
|
92
|
+
try {
|
|
93
|
+
fs.unlinkSync(abs);
|
|
94
|
+
removed.push(abs);
|
|
95
|
+
dirs.add(path.dirname(abs));
|
|
96
|
+
} catch {}
|
|
97
|
+
}
|
|
98
|
+
_pruneEmptyDirs(dirs, base);
|
|
99
|
+
return removed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function _isAssetKey(key) {
|
|
103
|
+
if (typeof key !== 'string') return false;
|
|
104
|
+
if (key.startsWith('~/')) return true;
|
|
105
|
+
if (key.startsWith('.')) {
|
|
106
|
+
if (key.startsWith('.claude/commands/')) return true;
|
|
107
|
+
if (key.startsWith('.claude/agents/')) return true;
|
|
108
|
+
for (const meta of registryMod.RUNTIMES) {
|
|
109
|
+
if (meta.commandsSubdir) {
|
|
110
|
+
if (key.startsWith(meta.localDir + '/' + meta.commandsSubdir + '/')) return true;
|
|
111
|
+
}
|
|
112
|
+
if (meta.agentsSubdir) {
|
|
113
|
+
if (key.startsWith(meta.localDir + '/' + meta.agentsSubdir + '/')) return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function _pruneEmptyDirs(dirSet, base) {
|
|
121
|
+
const sorted = Array.from(dirSet).sort((a, b) => b.length - a.length);
|
|
122
|
+
for (const dir of sorted) {
|
|
123
|
+
let cur = dir;
|
|
124
|
+
while (cur && cur.startsWith(base) && cur !== base) {
|
|
125
|
+
try {
|
|
126
|
+
const entries = fs.readdirSync(cur);
|
|
127
|
+
if (entries.length > 0) break;
|
|
128
|
+
fs.rmdirSync(cur);
|
|
129
|
+
} catch { break; }
|
|
130
|
+
cur = path.dirname(cur);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function isAssetManifestKey(key) {
|
|
136
|
+
return _isAssetKey(key);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
planRuntimeAssets,
|
|
141
|
+
manifestEntriesForPlans,
|
|
142
|
+
writeRuntimeAssets,
|
|
143
|
+
removeStaleAssets,
|
|
144
|
+
isAssetManifestKey,
|
|
145
|
+
};
|
|
@@ -13,6 +13,8 @@ const RUNTIMES = [
|
|
|
13
13
|
agentsMd: 'CLAUDE.md',
|
|
14
14
|
agentsMdScope: 'project',
|
|
15
15
|
payloadSubdir: 'nubos-pilot',
|
|
16
|
+
commandsSubdir: 'commands/np',
|
|
17
|
+
agentsSubdir: 'agents',
|
|
16
18
|
},
|
|
17
19
|
{
|
|
18
20
|
id: 'antigravity',
|
|
@@ -113,6 +115,8 @@ const RUNTIMES = [
|
|
|
113
115
|
agentsMd: 'AGENTS.md',
|
|
114
116
|
agentsMdScope: 'dir',
|
|
115
117
|
payloadSubdir: 'nubos-pilot',
|
|
118
|
+
commandsSubdir: 'command/np',
|
|
119
|
+
agentsSubdir: 'agent',
|
|
116
120
|
},
|
|
117
121
|
{
|
|
118
122
|
id: 'qwen',
|