@hybridlabor-api/aos 4.11.0 → 4.12.1

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/aos-store.mjs CHANGED
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { createHash } from 'node:crypto';
4
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
5
- import { homedir } from 'node:os';
6
- import { basename, dirname, join, resolve } from 'node:path';
4
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
+ import { dirname, join, resolve } from 'node:path';
7
6
  import { fileURLToPath } from 'node:url';
7
+ import { fetchSkillFiles, fileTargets, localAgents, localSkills, targetsFor as sharedTargetsFor, validateFiles, writeSkillFiles } from '../lib/store-shared.mjs';
8
8
 
9
9
  const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
10
- const INDEX_PATH = join(ROOT, 'lib', 'ecc-store-index.json');
10
+ const INDEX_PATH = process.env.AOS_STORE_INDEX || join(ROOT, 'lib', 'ecc-store-index.json');
11
+ const RAW_BASE = (process.env.AOS_STORE_RAW_BASE || 'https://raw.githubusercontent.com/affaan-m/ECC').replace(/\/+$/, '');
11
12
  const index = JSON.parse(readFileSync(INDEX_PATH, 'utf8'));
12
13
  const args = process.argv.slice(2);
13
14
  const dryRun = args.includes('--dry-run');
@@ -33,49 +34,8 @@ function findItem(name) {
33
34
  return null;
34
35
  }
35
36
 
36
- function localSkillNames() {
37
- const names = new Set();
38
- const visit = (dir) => {
39
- if (!existsSync(dir)) return;
40
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
41
- const fullPath = join(dir, entry.name);
42
- if (entry.isDirectory()) visit(fullPath);
43
- else if (entry.name === 'SKILL.md') names.add(basename(dirname(fullPath)));
44
- }
45
- };
46
- visit(join(ROOT, 'skills'));
47
- return names;
48
- }
49
-
50
- function localAgentNames() {
51
- const names = new Set();
52
- for (const dir of [join(ROOT, '.claude', 'agents'), join(ROOT, '.agents', 'agents'), join(ROOT, 'agents')]) {
53
- if (!existsSync(dir)) continue;
54
- for (const entry of readdirSync(dir)) names.add(entry.replace(/\.md$/i, ''));
55
- }
56
- return names;
57
- }
58
-
59
- function globalRoots(type) {
60
- const home = homedir();
61
- return type === 'skills'
62
- ? [
63
- join(home, '.agents', 'skills'),
64
- join(home, '.claude', 'skills'),
65
- join(home, '.codex', 'skills'),
66
- join(home, '.cursor', 'skills'),
67
- join(home, '.roo', 'skills'),
68
- join(home, '.gemini', 'config', 'skills'),
69
- ]
70
- : [
71
- join(home, '.agents', 'agents'),
72
- join(home, '.claude', 'agents'),
73
- join(home, '.codex', 'agents'),
74
- join(home, '.cursor', 'agents'),
75
- join(home, '.roo', 'agents'),
76
- join(home, '.gemini', 'config', 'agents'),
77
- ];
78
- }
37
+ const localSkillNames = () => new Set(localSkills(ROOT).keys());
38
+ const localAgentNames = () => new Set(localAgents(ROOT).keys());
79
39
 
80
40
  function printItems(items) {
81
41
  if (items.length === 0) {
@@ -90,22 +50,13 @@ function printItems(items) {
90
50
  }
91
51
 
92
52
  async function download(upstreamPath) {
93
- const url = `https://raw.githubusercontent.com/affaan-m/ECC/${index.pinned_commit}/${upstreamPath}`;
53
+ const url = `${RAW_BASE}/${index.pinned_commit}/${upstreamPath}`;
94
54
  const response = await fetch(url, { headers: { 'user-agent': 'aos-store' } });
95
55
  if (!response.ok) throw new Error(`Download failed (${response.status}): ${url}`);
96
56
  return Buffer.from(await response.arrayBuffer());
97
57
  }
98
58
 
99
- function targetsFor(type, name) {
100
- if (project) {
101
- return type === 'skills'
102
- ? [join(process.cwd(), 'skills', name, 'SKILL.md')]
103
- : [join(process.cwd(), 'agents', `${name}.md`)];
104
- }
105
- return globalRoots(type).map((root) => type === 'skills'
106
- ? join(root, name, 'SKILL.md')
107
- : join(root, `${name}.md`));
108
- }
59
+ const targetsFor = (type, name) => sharedTargetsFor(type, name, { project });
109
60
 
110
61
  async function install(name) {
111
62
  if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(`Invalid store name: ${name}`);
@@ -118,13 +69,26 @@ async function install(name) {
118
69
  return;
119
70
  }
120
71
  const targets = targetsFor(type, name);
72
+ const multi = type === 'skills' && item.files?.length > 1;
73
+ if (multi) validateFiles(item.files);
121
74
  if (dryRun) {
122
- for (const target of targets) console.log(`[dry-run] write ${target}`);
75
+ for (const target of targets) {
76
+ if (multi) for (const file of fileTargets(target, item.files)) console.log(`[dry-run] write ${file}`);
77
+ else console.log(`[dry-run] write ${target}`);
78
+ }
123
79
  return;
124
80
  }
125
81
  if (!net) {
126
82
  throw new Error(`Downloading '${name}' requires internet access to fetch from upstream GitHub. Pass --net to confirm: aos store install ${name} --net`);
127
83
  }
84
+ if (multi) {
85
+ const files = await fetchSkillFiles(RAW_BASE, index.pinned_commit, dirname(item.upstream_path), item.files);
86
+ for (const target of targets) {
87
+ const written = writeSkillFiles(target, files);
88
+ console.log(`Installed ${name} (${written.length} files) -> ${dirname(target)}`);
89
+ }
90
+ return;
91
+ }
128
92
  const content = await download(item.upstream_path);
129
93
  const digest = createHash('sha256').update(content).digest('hex');
130
94
  if (digest !== item.sha256) throw new Error(`SHA-256 mismatch for ${name}: expected ${item.sha256}, got ${digest}`);
@@ -140,6 +104,7 @@ function usage() {
140
104
  console.log(' aos store list [--type=skills|agents]');
141
105
  console.log(' aos store search <query>');
142
106
  console.log(' aos store install <name> [--project] [--dry-run] [--net]');
107
+ console.log(' aos store ui [--port=N] [--no-open]');
143
108
  }
144
109
 
145
110
  async function main() {
@@ -156,6 +121,11 @@ async function main() {
156
121
  printItems(items);
157
122
  return;
158
123
  }
124
+ if (command === 'ui') {
125
+ const { startUi } = await import('../lib/store-ui/server.mjs');
126
+ await startUi({ argv: args });
127
+ return;
128
+ }
159
129
  if (command === 'install') {
160
130
  if (!positional[1]) throw new Error('Install requires a store item name.');
161
131
  await install(positional[1]);
package/installer.js CHANGED
@@ -4520,6 +4520,16 @@ function generateAndOpenLaunchpad(installedModules = []) {
4520
4520
  background-color: var(--green);
4521
4521
  box-shadow: 0 0 10px rgba(63, 185, 80, 0.6);
4522
4522
  }
4523
+ .card-hint {
4524
+ display: none;
4525
+ font-size: 11px;
4526
+ color: var(--text-muted);
4527
+ margin-top: 4px;
4528
+ font-style: italic;
4529
+ }
4530
+ .card.offline .card-hint {
4531
+ display: block;
4532
+ }
4523
4533
  .footer {
4524
4534
  margin-top: 28px;
4525
4535
  text-align: center;
@@ -4691,6 +4701,25 @@ function generateAndOpenLaunchpad(installedModules = []) {
4691
4701
  <span class="status-dot" id="dot-openwiki" title="Checking..."></span>
4692
4702
  </div>
4693
4703
  </a>
4704
+ <a class="card" href="http://127.0.0.1:4322" target="_blank">
4705
+ <div class="card-content">
4706
+ <div class="card-icon"><svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
4707
+ <defs><linearGradient id="grad-store" x1="50%" y1="0%" x2="50%" y2="100%"><stop offset="0%" stop-color="#FFFFFF"/><stop offset="100%" stop-color="#9b30c4"/></linearGradient></defs>
4708
+ <rect x="20" y="20" width="60" height="60" rx="4" fill="url(#grad-store)" opacity="0.85"/>
4709
+ <rect x="28" y="28" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/><rect x="45" y="28" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/><rect x="62" y="28" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/>
4710
+ <rect x="28" y="48" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/><rect x="45" y="48" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/><rect x="62" y="48" width="12" height="14" rx="1.5" fill="#0a0a0a" opacity="0.6"/>
4711
+ </svg></div>
4712
+ <div class="card-info">
4713
+ <h2>AOS Store</h2>
4714
+ <p>Browse & install skills and agents</p>
4715
+ <div class="card-hint">offline — run: aos-store ui</div>
4716
+ </div>
4717
+ </div>
4718
+ <div class="card-meta">
4719
+ <span class="port-pill">:4322</span>
4720
+ <span class="status-dot" id="dot-store" title="Checking..."></span>
4721
+ </div>
4722
+ </a>
4694
4723
  </div>
4695
4724
  <div class="footer">
4696
4725
  <span>Autostart Daemons • 127.0.0.1</span>
@@ -4700,9 +4729,16 @@ function generateAndOpenLaunchpad(installedModules = []) {
4700
4729
  function checkHealth(url, dotId) {
4701
4730
  const dot = document.getElementById(dotId);
4702
4731
  if (!dot) return;
4732
+ const card = dot.closest('.card');
4703
4733
  fetch(url, { mode: 'no-cors' })
4704
- .then(() => dot.classList.add('online'))
4705
- .catch(() => dot.classList.remove('online'));
4734
+ .then(() => {
4735
+ dot.classList.add('online');
4736
+ if (card) card.classList.remove('offline');
4737
+ })
4738
+ .catch(() => {
4739
+ dot.classList.remove('online');
4740
+ if (card) card.classList.add('offline');
4741
+ });
4706
4742
  }
4707
4743
  function checkAllHealth() {
4708
4744
  checkHealth('http://127.0.0.1:8088', 'dot-memb');
@@ -4710,6 +4746,7 @@ function generateAndOpenLaunchpad(installedModules = []) {
4710
4746
  checkHealth('http://127.0.0.1:3101', 'dot-ao');
4711
4747
  checkHealth('http://127.0.0.1:9080', 'dot-remote');
4712
4748
  checkHealth('http://127.0.0.1:4321', 'dot-openwiki');
4749
+ checkHealth('http://127.0.0.1:4322', 'dot-store');
4713
4750
  }
4714
4751
  checkAllHealth();
4715
4752
  setInterval(checkAllHealth, 5000);