nexscope 1.0.0 → 1.0.2

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "nexscope",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "NexScope CLI tool",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "nexscope": "bin/nexscope.js"
7
+ "nexscope": "./bin/nexscope.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "node bin/nexscope.js --help"
@@ -16,5 +16,8 @@
16
16
  "license": "MIT",
17
17
  "engines": {
18
18
  "node": ">=14.0.0"
19
+ },
20
+ "dependencies": {
21
+ "listr2": "^5.0.8"
19
22
  }
20
23
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { spawn } = require('child_process');
4
4
  const https = require('https');
5
+ const { Listr } = require('listr2');
5
6
 
6
7
  const SKILLS_REPO = 'nexscope-ai/eCommerce-Skills';
7
8
  const REPO_API = 'https://api.github.com/repos/nexscope-ai/eCommerce-Skills/contents';
@@ -47,37 +48,40 @@ async function fetchSkillList() {
47
48
  return JSON.parse(data).filter((i) => i.type === 'dir').map((i) => i.name);
48
49
  }
49
50
 
50
- // Parse directory-related flags to pass through to npx skills
51
51
  function parseDirFlags(args) {
52
52
  const flags = [];
53
-
54
- if (args.includes('--global') || args.includes('-g')) {
55
- flags.push('-g');
56
- }
57
-
53
+ if (args.includes('--global') || args.includes('-g')) flags.push('-g');
58
54
  const agentIndex = args.findIndex((a) => a === '--agent' || a === '-a');
59
55
  if (agentIndex !== -1 && args[agentIndex + 1]) {
60
56
  flags.push('--agent', args[agentIndex + 1]);
61
57
  }
62
-
63
58
  return flags;
64
59
  }
65
60
 
66
- function installOne(skillName, dirFlags) {
67
- return new Promise((resolve) => {
68
- const skillsArgs = ['skills', 'add', SKILLS_REPO, '--skill', skillName, '-y', ...dirFlags];
69
- const proc = spawn('npx', skillsArgs, { shell: true });
70
-
61
+ function spawnAsync(cmd, args) {
62
+ return new Promise((resolve, reject) => {
63
+ const proc = spawn(cmd, args, { shell: true });
71
64
  let output = '';
72
65
  proc.stdout.on('data', (d) => (output += d.toString()));
73
66
  proc.stderr.on('data', (d) => (output += d.toString()));
74
-
75
67
  proc.on('close', (code) => {
76
- resolve({ skillName, success: code === 0, output });
68
+ if (code === 0) resolve(output);
69
+ else reject(new Error(output.trim()));
77
70
  });
78
71
  });
79
72
  }
80
73
 
74
+ async function getInstalledSkills(dirFlags) {
75
+ try {
76
+ const output = await spawnAsync('npx', ['skills', 'ls', '--json', ...dirFlags]);
77
+ const data = JSON.parse(output);
78
+ // Each entry may be a string or object with a name field
79
+ return data.map((s) => (typeof s === 'string' ? s : s.name || s.skill || '')).filter(Boolean);
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
84
+
81
85
  function runSkillsSync(skillsArgs) {
82
86
  return new Promise((resolve) => {
83
87
  const proc = spawn('npx', ['skills', ...skillsArgs], { shell: true, stdio: 'inherit' });
@@ -85,6 +89,10 @@ function runSkillsSync(skillsArgs) {
85
89
  });
86
90
  }
87
91
 
92
+ function runSkillsSilent(skillsArgs) {
93
+ return spawnAsync('npx', ['skills', ...skillsArgs]);
94
+ }
95
+
88
96
  async function run(args) {
89
97
  if (args.includes('--help') || args.includes('-h')) {
90
98
  printHelp();
@@ -106,60 +114,50 @@ async function run(args) {
106
114
  console.error("Run 'nexscope install --help' for usage.");
107
115
  process.exit(1);
108
116
  }
109
- // Single skill: pass through output directly
110
- const code = await runSkillsSync(['add', SKILLS_REPO, '--skill', skillName, '-y', ...dirFlags]);
117
+
118
+ const installed = await getInstalledSkills(dirFlags);
119
+
120
+ const tasks = new Listr([{
121
+ title: skillName,
122
+ skip: () => installed.includes(skillName) ? 'Already installed' : false,
123
+ task: () => runSkillsSilent(['add', SKILLS_REPO, '--skill', skillName, '-y', ...dirFlags]),
124
+ }], { exitOnError: false });
125
+
126
+ try {
127
+ await tasks.run();
128
+ } catch {
129
+ // error already shown by listr2
130
+ }
131
+
111
132
  printThankYou();
112
- process.exit(code || 0);
113
133
  } else {
114
- // All skills: fetch list then install concurrently
115
134
  process.stdout.write('Fetching skill list... ');
116
- const skills = await fetchSkillList();
135
+ const [skills, installed] = await Promise.all([
136
+ fetchSkillList(),
137
+ getInstalledSkills(dirFlags),
138
+ ]);
117
139
  console.log(`${skills.length} skills found.\n`);
118
140
 
119
- console.log(`Installing all skills concurrently...\n`);
120
-
121
- // Track per-skill status
122
- const status = {};
123
- skills.forEach((s) => (status[s] = 'pending'));
124
-
125
- function printStatus() {
126
- process.stdout.write('\x1B[2J\x1B[0f'); // clear screen
127
- console.log(`Installing ${skills.length} skills concurrently...\n`);
128
- for (const s of skills) {
129
- const icon =
130
- status[s] === 'pending' ? '⏳' :
131
- status[s] === 'running' ? '⚙ ' :
132
- status[s] === 'done' ? '✓ ' : '✗ ';
133
- console.log(` ${icon} ${s}`);
141
+ const tasks = new Listr(
142
+ skills.map((skillName) => ({
143
+ title: skillName,
144
+ skip: () => installed.includes(skillName) ? 'Already installed' : false,
145
+ task: () => spawnAsync('npx', ['skills', 'add', SKILLS_REPO, '--skill', skillName, '-y', ...dirFlags]),
146
+ })),
147
+ {
148
+ concurrent: true,
149
+ exitOnError: false,
150
+ rendererOptions: { collapseErrors: false },
134
151
  }
135
- }
136
-
137
- printStatus();
138
-
139
- const promises = skills.map((skillName) => {
140
- status[skillName] = 'running';
141
- printStatus();
142
- return installOne(skillName, dirFlags).then((result) => {
143
- status[skillName] = result.success ? 'done' : 'failed';
144
- printStatus();
145
- return result;
146
- });
147
- });
148
-
149
- const results = await Promise.all(promises);
150
-
151
- const succeeded = results.filter((r) => r.success);
152
- const failed = results.filter((r) => !r.success);
153
-
154
- console.log(`\n${succeeded.length}/${skills.length} skills installed successfully.`);
152
+ );
155
153
 
156
- if (failed.length > 0) {
157
- console.log('\nFailed skills:');
158
- failed.forEach((r) => console.log(` ✗ ${r.skillName}`));
154
+ try {
155
+ await tasks.run();
156
+ } catch {
157
+ // listr2 throws if any task fails; results are already displayed
159
158
  }
160
159
 
161
160
  printThankYou();
162
- process.exit(failed.length > 0 ? 1 : 0);
163
161
  }
164
162
  }
165
163