@laitszkin/apollo-toolkit 3.10.0 → 3.11.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  3. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  4. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  5. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  6. package/generate-spec/SKILL.md +17 -15
  7. package/generate-spec/agents/openai.yaml +1 -1
  8. package/generate-spec/references/TEMPLATE_SPEC.md +103 -84
  9. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  10. package/init-project-html/SKILL.md +82 -126
  11. package/init-project-html/agents/openai.yaml +17 -8
  12. package/init-project-html/lib/atlas/assets/architecture.css +140 -0
  13. package/init-project-html/lib/atlas/assets/viewer.client.js +93 -0
  14. package/init-project-html/lib/atlas/cli.js +995 -0
  15. package/init-project-html/lib/atlas/layout.js +229 -0
  16. package/init-project-html/lib/atlas/render.js +485 -0
  17. package/init-project-html/lib/atlas/schema.js +310 -0
  18. package/init-project-html/lib/atlas/state.js +402 -0
  19. package/init-project-html/references/TEMPLATE_SPEC.md +123 -84
  20. package/init-project-html/sample-demo/resources/project-architecture/assets/architecture.css +139 -1058
  21. package/init-project-html/sample-demo/resources/project-architecture/assets/viewer.client.js +93 -0
  22. package/init-project-html/sample-demo/resources/project-architecture/atlas/atlas.index.yaml +34 -0
  23. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/get-invite-codes.yaml +159 -0
  24. package/init-project-html/sample-demo/resources/project-architecture/atlas/features/invite-code-registration.yaml +160 -0
  25. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/index.html +67 -52
  26. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-code-generator.html +48 -163
  27. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/invite-issuance-service.html +70 -196
  28. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/postgresql.html +64 -163
  29. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/public-api.html +68 -150
  30. package/init-project-html/sample-demo/resources/project-architecture/features/get-invite-codes/web-get-invite-ui.html +65 -138
  31. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/index.html +61 -51
  32. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/postgresql.html +66 -159
  33. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/public-api.html +63 -143
  34. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/registration-service.html +77 -188
  35. package/init-project-html/sample-demo/resources/project-architecture/features/invite-code-registration/web-register-ui.html +65 -138
  36. package/init-project-html/sample-demo/resources/project-architecture/index.html +232 -335
  37. package/init-project-html/scripts/architecture.js +65 -247
  38. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  39. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  40. package/package.json +6 -2
  41. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  42. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  43. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  44. package/spec-to-project-html/SKILL.md +61 -63
  45. package/spec-to-project-html/agents/openai.yaml +14 -8
  46. package/spec-to-project-html/references/TEMPLATE_SPEC.md +96 -83
  47. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env node
2
- // architecture.js — open the project HTML architecture atlas, or render
3
- // a paginated before/after diff viewer from every active spec's
4
- // architecture_diff/ directory under docs/plans/.
2
+ // architecture.js — thin shim over lib/atlas/cli.js.
5
3
  //
6
- // Usage:
7
- // architecture.js # same as `open`
4
+ // Backward-compatible legacy entrypoint:
5
+ // architecture.js # same as `open`
8
6
  // architecture.js open [--project <root>] [--no-open]
9
7
  // architecture.js diff [--project <root>] [--out <dir>] [--no-open]
10
- // architecture.js --help
8
+ //
9
+ // All new declarative verbs (feature add, submodule add, function add,
10
+ // variable add, dataflow add|remove|reorder, error add, edge add, meta
11
+ // set, actor add, render, validate, undo) are routed through
12
+ // lib/atlas/cli.js, which owns layout, no-overlap, DOM, CSS, and pan/zoom.
11
13
 
12
14
  'use strict';
13
15
 
@@ -15,25 +17,41 @@ const fs = require('node:fs');
15
17
  const path = require('node:path');
16
18
  const { spawn } = require('node:child_process');
17
19
 
20
+ const newCli = require('../lib/atlas/cli');
21
+
18
22
  const ATLAS_REL = path.join('resources', 'project-architecture', 'index.html');
19
23
  const RESOURCES_REL = path.join('resources', 'project-architecture');
20
24
  const PLANS_REL = path.join('docs', 'plans');
21
25
  const DIFF_DIRNAME = 'architecture_diff';
22
26
  const REMOVED_FILE = '_removed.txt';
27
+ const ATLAS_DIRNAME = 'atlas';
23
28
  const DEFAULT_OUT_REL = path.join('.apollo-toolkit', 'architecture-diff');
24
29
 
25
- const USAGE = `apltk architecture — open the project architecture atlas or its diff viewer.
30
+ const LEGACY_VERBS = new Set(['open', 'diff']);
26
31
 
27
- Usage:
28
- apltk architecture Open resources/project-architecture/index.html
29
- apltk architecture open Same as above
30
- apltk architecture diff Render every architecture_diff/ as one paginated viewer
31
- apltk architecture --help Show this help
32
+ const USAGE = `apltk architecture — declarative atlas CLI.
32
33
 
33
- Options:
34
- --project <root> Override project root (default: nearest ancestor with resources/project-architecture/index.html)
35
- --out <dir> Override viewer output dir (default: <project>/${DEFAULT_OUT_REL})
36
- --no-open Skip launching the browser (CI/test friendly)
34
+ Usage:
35
+ apltk architecture Open resources/project-architecture/index.html
36
+ apltk architecture open Same as above
37
+ apltk architecture diff Render every architecture_diff/ as one paginated viewer
38
+ apltk architecture render Regenerate atlas HTML from current YAML state
39
+ apltk architecture validate Run schema + referential checks
40
+ apltk architecture feature add|set|remove Manage feature modules
41
+ apltk architecture submodule add|set|remove Manage sub-modules
42
+ apltk architecture function|variable|dataflow|error|edge add|remove
43
+ Manage component rows and edges
44
+ apltk architecture meta set Update meta.title / meta.summary
45
+ apltk architecture actor add|remove Manage top-level actors
46
+ apltk architecture undo Revert the most recent mutation
47
+ apltk architecture --help Show this help
48
+
49
+ Global flags:
50
+ --project <root> Project root (default: nearest ancestor with resources/project-architecture/)
51
+ --spec <spec_dir> Mutations write to <spec_dir>/architecture_diff/atlas/
52
+ --no-render Skip auto-render after a mutation
53
+ --no-open For open/diff: skip launching the browser
54
+ --out <dir> For diff: override viewer output directory
37
55
  -h, --help Show this help`;
38
56
 
39
57
  function parseArgs(argv) {
@@ -80,6 +98,7 @@ function findProjectRoot(startDir) {
80
98
  let dir = path.resolve(startDir);
81
99
  while (true) {
82
100
  if (fs.existsSync(path.join(dir, ATLAS_REL))) return dir;
101
+ if (fs.existsSync(path.join(dir, RESOURCES_REL, ATLAS_DIRNAME, 'atlas.index.yaml'))) return dir;
83
102
  const parent = path.dirname(dir);
84
103
  if (parent === dir) return null;
85
104
  dir = parent;
@@ -90,44 +109,27 @@ function openInBrowser(filePath) {
90
109
  const platform = process.platform;
91
110
  let command;
92
111
  let args;
93
- if (platform === 'darwin') {
94
- command = 'open';
95
- args = [filePath];
96
- } else if (platform === 'win32') {
97
- command = 'cmd';
98
- args = ['/c', 'start', '""', filePath];
99
- } else {
100
- command = 'xdg-open';
101
- args = [filePath];
102
- }
112
+ if (platform === 'darwin') { command = 'open'; args = [filePath]; }
113
+ else if (platform === 'win32') { command = 'cmd'; args = ['/c', 'start', '""', filePath]; }
114
+ else { command = 'xdg-open'; args = [filePath]; }
103
115
  try {
104
116
  const child = spawn(command, args, { stdio: 'ignore', detached: true });
105
- child.on('error', () => { /* best effort */ });
117
+ child.on('error', () => {});
106
118
  child.unref();
107
- } catch (_err) {
108
- /* best effort */
109
- }
119
+ } catch (_e) { /* best effort */ }
110
120
  }
111
121
 
112
122
  function walkArchitectureDiffDirs(plansRoot) {
113
123
  const result = [];
114
124
  if (!fs.existsSync(plansRoot)) return result;
115
-
116
125
  function recurse(dir) {
117
126
  let entries;
118
- try {
119
- entries = fs.readdirSync(dir, { withFileTypes: true });
120
- } catch (_err) {
121
- return;
122
- }
127
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_e) { return; }
123
128
  for (const entry of entries) {
124
129
  if (!entry.isDirectory()) continue;
125
130
  if (entry.name === 'node_modules' || entry.name.startsWith('.')) continue;
126
131
  const full = path.join(dir, entry.name);
127
- if (entry.name === DIFF_DIRNAME) {
128
- result.push(full);
129
- continue;
130
- }
132
+ if (entry.name === DIFF_DIRNAME) { result.push(full); continue; }
131
133
  recurse(full);
132
134
  }
133
135
  }
@@ -139,20 +141,16 @@ function walkAfterStateHtml(diffDir) {
139
141
  const out = [];
140
142
  function recurse(dir, relParts) {
141
143
  let entries;
142
- try {
143
- entries = fs.readdirSync(dir, { withFileTypes: true });
144
- } catch (_err) {
145
- return;
146
- }
144
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch (_e) { return; }
147
145
  for (const entry of entries) {
148
146
  if (entry.name === 'assets') continue;
147
+ if (entry.name === ATLAS_DIRNAME) continue;
149
148
  if (entry.name === REMOVED_FILE) continue;
150
149
  if (entry.name.startsWith('.')) continue;
151
150
  const full = path.join(dir, entry.name);
152
151
  const nextRel = [...relParts, entry.name];
153
- if (entry.isDirectory()) {
154
- recurse(full, nextRel);
155
- } else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
152
+ if (entry.isDirectory()) recurse(full, nextRel);
153
+ else if (entry.isFile() && entry.name.toLowerCase().endsWith('.html')) {
156
154
  out.push({ abs: full, rel: nextRel.join('/') });
157
155
  }
158
156
  }
@@ -179,7 +177,6 @@ function collectChanges(projectRoot) {
179
177
  for (const diffDir of diffDirs) {
180
178
  const specDir = path.dirname(diffDir);
181
179
  const specLabel = path.relative(projectRoot, specDir);
182
-
183
180
  for (const after of walkAfterStateHtml(diffDir)) {
184
181
  const beforeAbs = path.join(resourcesRoot, after.rel);
185
182
  const beforeExists = fs.existsSync(beforeAbs);
@@ -191,7 +188,6 @@ function collectChanges(projectRoot) {
191
188
  afterPath: path.relative(projectRoot, after.abs),
192
189
  });
193
190
  }
194
-
195
191
  for (const removedRel of readRemovedManifest(diffDir)) {
196
192
  const beforeAbs = path.join(resourcesRoot, removedRel);
197
193
  if (!fs.existsSync(beforeAbs)) continue;
@@ -210,19 +206,9 @@ function collectChanges(projectRoot) {
210
206
  if (a.kind !== b.kind) return a.kind.localeCompare(b.kind);
211
207
  return a.rel.localeCompare(b.rel);
212
208
  });
213
-
214
209
  return changes;
215
210
  }
216
211
 
217
- function htmlEscape(value) {
218
- return String(value)
219
- .replace(/&/g, '&amp;')
220
- .replace(/</g, '&lt;')
221
- .replace(/>/g, '&gt;')
222
- .replace(/"/g, '&quot;')
223
- .replace(/'/g, '&#39;');
224
- }
225
-
226
212
  function toViewerRel(outDir, projectRoot, projectRelPath) {
227
213
  if (!projectRelPath) return null;
228
214
  const absolute = path.resolve(projectRoot, projectRelPath);
@@ -231,190 +217,7 @@ function toViewerRel(outDir, projectRoot, projectRelPath) {
231
217
  }
232
218
 
233
219
  function renderViewer({ changes, projectRoot, outDir }) {
234
- const pages = changes.map((change) => ({
235
- kind: change.kind,
236
- rel: change.rel,
237
- spec: change.spec,
238
- beforeSrc: toViewerRel(outDir, projectRoot, change.beforePath),
239
- afterSrc: toViewerRel(outDir, projectRoot, change.afterPath),
240
- }));
241
-
242
- const summary = {
243
- total: pages.length,
244
- modified: pages.filter((p) => p.kind === 'modified').length,
245
- added: pages.filter((p) => p.kind === 'added').length,
246
- removed: pages.filter((p) => p.kind === 'removed').length,
247
- projectRoot,
248
- };
249
-
250
- const payload = JSON.stringify({ pages, summary });
251
-
252
- return `<!DOCTYPE html>
253
- <html lang="en" data-atlas="diff-viewer">
254
- <head>
255
- <meta charset="utf-8">
256
- <title>Architecture diff — ${htmlEscape(path.basename(projectRoot))}</title>
257
- <style>
258
- :root {
259
- color-scheme: light dark;
260
- --bg: #0f172a;
261
- --panel: #1e293b;
262
- --text: #e2e8f0;
263
- --muted: #94a3b8;
264
- --accent: #38bdf8;
265
- --added: #4ade80;
266
- --removed: #f87171;
267
- --modified: #facc15;
268
- }
269
- * { box-sizing: border-box; }
270
- html, body { height: 100%; margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; background: var(--bg); color: var(--text); }
271
- body { display: flex; flex-direction: column; min-height: 100vh; }
272
- header { padding: 12px 20px; background: var(--panel); border-bottom: 1px solid #334155; display: flex; flex-wrap: wrap; gap: 12px; align-items: center; justify-content: space-between; }
273
- header .title { font-size: 14px; color: var(--muted); }
274
- header .title strong { color: var(--text); }
275
- header .summary { display: flex; gap: 12px; font-size: 12px; color: var(--muted); }
276
- header .summary span.count { font-weight: 600; }
277
- header .summary .modified { color: var(--modified); }
278
- header .summary .added { color: var(--added); }
279
- header .summary .removed { color: var(--removed); }
280
- main { flex: 1; display: flex; flex-direction: column; }
281
- .meta { padding: 10px 20px; background: var(--bg); border-bottom: 1px solid #334155; display: flex; flex-wrap: wrap; gap: 16px; align-items: center; justify-content: space-between; font-size: 13px; }
282
- .meta .left { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
283
- .badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; border: 1px solid currentColor; }
284
- .badge.modified { color: var(--modified); }
285
- .badge.added { color: var(--added); }
286
- .badge.removed { color: var(--removed); }
287
- .path { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--text); }
288
- .spec { color: var(--muted); font-size: 12px; }
289
- .nav { display: flex; align-items: center; gap: 8px; }
290
- .nav button { background: transparent; color: var(--text); border: 1px solid #475569; padding: 6px 12px; border-radius: 6px; cursor: pointer; font-size: 13px; }
291
- .nav button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
292
- .nav button:disabled { opacity: 0.4; cursor: not-allowed; }
293
- .nav .counter { font-variant-numeric: tabular-nums; color: var(--muted); min-width: 72px; text-align: center; }
294
- .frames { flex: 1; display: grid; gap: 1px; background: #334155; padding: 1px; min-height: 0; }
295
- .frames.split { grid-template-columns: 1fr 1fr; }
296
- .frames.single { grid-template-columns: 1fr; }
297
- .pane { background: #ffffff; display: flex; flex-direction: column; min-height: 0; }
298
- .pane h2 { margin: 0; padding: 8px 14px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; background: #f1f5f9; color: #1e293b; border-bottom: 1px solid #cbd5f5; display: flex; align-items: center; gap: 8px; }
299
- .pane h2 .side-badge { font-size: 10px; padding: 1px 6px; border-radius: 4px; background: #cbd5f5; color: #1e293b; }
300
- .pane h2.before .side-badge { background: #fee2e2; color: #991b1b; }
301
- .pane h2.after .side-badge { background: #dcfce7; color: #166534; }
302
- .pane iframe { flex: 1; width: 100%; border: 0; background: #ffffff; }
303
- .empty { display: flex; align-items: center; justify-content: center; padding: 32px; font-size: 14px; color: var(--muted); }
304
- footer { padding: 8px 20px; background: var(--panel); border-top: 1px solid #334155; font-size: 12px; color: var(--muted); }
305
- footer kbd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; background: #0f172a; padding: 1px 6px; border-radius: 4px; border: 1px solid #475569; }
306
- </style>
307
- </head>
308
- <body>
309
- <header>
310
- <div class="title">Apollo Toolkit · <strong>architecture diff</strong> · ${htmlEscape(path.basename(projectRoot))}</div>
311
- <div class="summary">
312
- <span><span class="count">${summary.total}</span> change<span>${summary.total === 1 ? '' : 's'}</span></span>
313
- <span class="modified"><span class="count">${summary.modified}</span> modified</span>
314
- <span class="added"><span class="count">${summary.added}</span> added</span>
315
- <span class="removed"><span class="count">${summary.removed}</span> removed</span>
316
- </div>
317
- </header>
318
- <main>
319
- <div class="meta">
320
- <div class="left">
321
- <span id="badge" class="badge modified">modified</span>
322
- <span class="path" id="path">—</span>
323
- <span class="spec" id="spec">—</span>
324
- </div>
325
- <div class="nav">
326
- <button id="prev" type="button" aria-label="Previous change">← Prev</button>
327
- <span class="counter" id="counter">0 / 0</span>
328
- <button id="next" type="button" aria-label="Next change">Next →</button>
329
- </div>
330
- </div>
331
- <div class="frames" id="frames">
332
- <div class="empty" id="empty">No architecture diffs found under docs/plans/**/architecture_diff/.</div>
333
- </div>
334
- </main>
335
- <footer>
336
- Navigate with <kbd>←</kbd> / <kbd>→</kbd> or the buttons above. Each page pairs the current atlas (left) with the proposed-after HTML (right) for one affected page.
337
- </footer>
338
- <script id="__diff_payload" type="application/json">${payload.replace(/</g, '\\u003c')}</script>
339
- <script>
340
- (function () {
341
- const data = JSON.parse(document.getElementById('__diff_payload').textContent);
342
- const pages = data.pages || [];
343
- const framesEl = document.getElementById('frames');
344
- const emptyEl = document.getElementById('empty');
345
- const badgeEl = document.getElementById('badge');
346
- const pathEl = document.getElementById('path');
347
- const specEl = document.getElementById('spec');
348
- const counterEl = document.getElementById('counter');
349
- const prevBtn = document.getElementById('prev');
350
- const nextBtn = document.getElementById('next');
351
-
352
- if (pages.length === 0) {
353
- counterEl.textContent = '0 / 0';
354
- prevBtn.disabled = true;
355
- nextBtn.disabled = true;
356
- return;
357
- }
358
-
359
- let index = 0;
360
-
361
- function render() {
362
- const page = pages[index];
363
- badgeEl.className = 'badge ' + page.kind;
364
- badgeEl.textContent = page.kind;
365
- pathEl.textContent = page.rel;
366
- specEl.textContent = page.spec;
367
- counterEl.textContent = (index + 1) + ' / ' + pages.length;
368
- prevBtn.disabled = index === 0;
369
- nextBtn.disabled = index === pages.length - 1;
370
-
371
- framesEl.innerHTML = '';
372
- if (page.kind === 'modified') {
373
- framesEl.className = 'frames split';
374
- framesEl.appendChild(buildPane('Before', page.beforeSrc, 'before'));
375
- framesEl.appendChild(buildPane('After', page.afterSrc, 'after'));
376
- } else if (page.kind === 'added') {
377
- framesEl.className = 'frames single';
378
- framesEl.appendChild(buildPane('After (new)', page.afterSrc, 'after'));
379
- } else if (page.kind === 'removed') {
380
- framesEl.className = 'frames single';
381
- framesEl.appendChild(buildPane('Before (removed)', page.beforeSrc, 'before'));
382
- }
383
- }
384
-
385
- function buildPane(label, src, side) {
386
- const pane = document.createElement('div');
387
- pane.className = 'pane';
388
- const heading = document.createElement('h2');
389
- heading.className = side;
390
- const sideBadge = document.createElement('span');
391
- sideBadge.className = 'side-badge';
392
- sideBadge.textContent = side;
393
- heading.appendChild(sideBadge);
394
- heading.appendChild(document.createTextNode(' ' + label));
395
- pane.appendChild(heading);
396
- const frame = document.createElement('iframe');
397
- frame.src = src;
398
- frame.loading = 'lazy';
399
- frame.title = label;
400
- pane.appendChild(frame);
401
- return pane;
402
- }
403
-
404
- prevBtn.addEventListener('click', () => { if (index > 0) { index--; render(); } });
405
- nextBtn.addEventListener('click', () => { if (index < pages.length - 1) { index++; render(); } });
406
- document.addEventListener('keydown', (event) => {
407
- if (event.key === 'ArrowLeft') { prevBtn.click(); }
408
- else if (event.key === 'ArrowRight') { nextBtn.click(); }
409
- });
410
-
411
- emptyEl.remove();
412
- render();
413
- })();
414
- </script>
415
- </body>
416
- </html>
417
- `;
220
+ return newCli.renderDiffViewer({ changes, projectRoot, outDir });
418
221
  }
419
222
 
420
223
  function runOpen(opts, io) {
@@ -443,7 +246,6 @@ function runDiff(opts, io) {
443
246
  );
444
247
  return 1;
445
248
  }
446
-
447
249
  const outDir = opts.out || path.join(projectRoot, DEFAULT_OUT_REL);
448
250
  fs.mkdirSync(outDir, { recursive: true });
449
251
 
@@ -460,6 +262,9 @@ function runDiff(opts, io) {
460
262
  return 0;
461
263
  }
462
264
 
265
+ // main(argv, io) is sync and supports the legacy verbs `open` and
266
+ // `diff` only. Tests rely on the sync return-code contract. All other
267
+ // verbs go through dispatchAsync().
463
268
  function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
464
269
  let opts;
465
270
  try {
@@ -478,9 +283,21 @@ function main(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
478
283
  return 1;
479
284
  }
480
285
 
286
+ async function dispatchAsync(argv, io = { stdout: process.stdout, stderr: process.stderr }) {
287
+ return newCli.dispatch(argv, io);
288
+ }
289
+
481
290
  if (require.main === module) {
482
- const code = main(process.argv.slice(2));
483
- process.exit(code);
291
+ const argv = process.argv.slice(2);
292
+ const verb = argv[0];
293
+ if (!verb || verb.startsWith('-') || LEGACY_VERBS.has(verb)) {
294
+ process.exit(main(argv));
295
+ } else {
296
+ dispatchAsync(argv).then((code) => process.exit(code)).catch((err) => {
297
+ process.stderr.write(`${err && err.stack ? err.stack : err}\n`);
298
+ process.exit(1);
299
+ });
300
+ }
484
301
  }
485
302
 
486
303
  module.exports = {
@@ -493,4 +310,5 @@ module.exports = {
493
310
  walkAfterStateHtml,
494
311
  readRemovedManifest,
495
312
  main,
313
+ dispatchAsync,
496
314
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@laitszkin/apollo-toolkit",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "Apollo Toolkit npm installer for managed skill copying across Codex, OpenClaw, and Trae.",
5
5
  "license": "MIT",
6
6
  "author": "LaiTszKin",
@@ -33,5 +33,9 @@
33
33
  "skills",
34
34
  "openclaw",
35
35
  "trae"
36
- ]
36
+ ],
37
+ "dependencies": {
38
+ "elkjs": "^0.11.1",
39
+ "js-yaml": "^4.1.1"
40
+ }
37
41
  }