nexo-brain 5.3.28 → 5.3.30

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.3.28",
3
+ "version": "5.3.30",
4
4
  "description": "Local cognitive runtime for Claude Code \u2014 persistent memory, overnight learning, doctor diagnostics, personal scripts, recovery-aware jobs, startup preflight, and optional dashboard/power helper.",
5
5
  "author": {
6
6
  "name": "NEXO Brain",
package/README.md CHANGED
@@ -18,7 +18,9 @@
18
18
 
19
19
  [Watch the overview video](https://nexo-brain.com/watch/) · [Watch on YouTube](https://www.youtube.com/watch?v=i2lkGhKyVqI) · [Open the infographic](https://nexo-brain.com/assets/nexo-brain-infographic-v5.png)
20
20
 
21
- Version `5.3.11` is the current packaged-runtime line: protocol and Cortex now reject malformed `outcome`, `task_type`, and `impact_level` values explicitly instead of silently coercing them into other valid states, so task history, debt, hot context, and decision telemetry stay trustworthy even when a caller passes a bad contract payload.
21
+ Version `5.3.30` is the current packaged-runtime line: four read-only CLI commands (`nexo schema`, `nexo identity`, `nexo onboard`, `nexo scan-profile`) let external UIs like NEXO Desktop auto-adapt to the editable schema, canonical identity, onboarding wizard, and profile heuristics without hardcoding fields.
22
+
23
+ Previously in `5.3.29`: duplicate `* 2` artifacts now fail hygiene gates instead of hiding in the tree, packaged/runtime update paths converge on one canonical core, startup preflight runs synchronously, corrupt DB state no longer respawns an empty brain by default, and cron runs spool locally when SQLite is unavailable.
22
24
 
23
25
  Start here:
24
26
  - [5-minute quickstart](docs/quickstart-5-minutes.md)
@@ -488,7 +490,9 @@ NEXO Brain doesn't just respond — it runs 13 core recovery-aware background jo
488
490
  | **followup-hygiene** | Weekly (Sun) | Normalizes statuses, flags stale followups, cleans orphans |
489
491
  | **learning-housekeep** | 03:15 daily | Dedup learnings, adjust weights by usage, process overdue reviews, reconcile decision outcomes |
490
492
  | **immune** | Every 30 min | Quarantine processing, memory promotion/rejection, synaptic pruning |
493
+ | **impact-scorer** | 05:45 daily | Scores active followups so queues can prioritize by expected impact |
491
494
  | **synthesis** | 06:00 daily | Memory synthesis — discovers cross-memory patterns |
495
+ | **outcome-checker** | 08:00 daily | Verifies tracked outcomes and marks them met, pending, or missed |
492
496
  | **watchdog** | Every 30 min | Monitors services, LaunchAgents, and infrastructure health |
493
497
  | **auto-close-sessions** | Every 5 min | Cleans stale sessions |
494
498
 
package/bin/nexo-brain.js CHANGED
@@ -88,6 +88,20 @@ function log(msg) {
88
88
  console.log(` ${msg}`);
89
89
  }
90
90
 
91
+ function duplicateArtifactCanonicalName(name) {
92
+ const ext = path.extname(name);
93
+ const stem = ext ? name.slice(0, -ext.length) : name;
94
+ const match = stem.match(/^(.*) ([2-9]\d*)$/);
95
+ if (!match) return null;
96
+ return `${match[1]}${ext}`;
97
+ }
98
+
99
+ function isDuplicateArtifactName(name, dirPath = "") {
100
+ const canonical = duplicateArtifactCanonicalName(name);
101
+ if (!canonical || !dirPath) return false;
102
+ return fs.existsSync(path.join(dirPath, canonical));
103
+ }
104
+
91
105
  function syncWatchdogHashRegistry(nexoHome) {
92
106
  try {
93
107
  const watchdogPath = path.join(nexoHome, "scripts", "nexo-watchdog.sh");
@@ -122,7 +136,7 @@ function writeRuntimeCoreArtifactsManifest(nexoHome, srcDir) {
122
136
  return fs.readdirSync(dirPath)
123
137
  .filter((name) => {
124
138
  const full = path.join(dirPath, name);
125
- return fs.existsSync(full) && fs.statSync(full).isFile();
139
+ return fs.existsSync(full) && fs.statSync(full).isFile() && !isDuplicateArtifactName(name, dirPath);
126
140
  })
127
141
  .sort();
128
142
  };
@@ -192,6 +206,7 @@ function getCoreRuntimeFlatFiles(srcDir = path.join(__dirname, "..", "src")) {
192
206
  const discoveredRootModules = fs.existsSync(srcDir)
193
207
  ? fs.readdirSync(srcDir)
194
208
  .filter((name) => {
209
+ if (isDuplicateArtifactName(name, srcDir)) return false;
195
210
  const stat = fs.statSync(path.join(srcDir, name));
196
211
  if (!stat.isFile()) return false;
197
212
  // Include Python modules and any flat JSON config the Python runtime
@@ -1551,7 +1566,7 @@ async function main() {
1551
1566
  const copyDirRec = (src, dest) => {
1552
1567
  fs.mkdirSync(dest, { recursive: true });
1553
1568
  fs.readdirSync(src).forEach(item => {
1554
- if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db")) return;
1569
+ if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db") || isDuplicateArtifactName(item, src)) return;
1555
1570
  const srcPath = path.join(src, item);
1556
1571
  const destPath = path.join(dest, item);
1557
1572
  if (fs.statSync(srcPath).isDirectory()) {
@@ -1620,7 +1635,7 @@ async function main() {
1620
1635
  const pluginsDest = path.join(NEXO_HOME, "plugins");
1621
1636
  fs.mkdirSync(pluginsDest, { recursive: true });
1622
1637
  if (fs.existsSync(pluginsSrc)) {
1623
- fs.readdirSync(pluginsSrc).filter(f => f.endsWith(".py")).forEach((f) => {
1638
+ fs.readdirSync(pluginsSrc).filter(f => f.endsWith(".py") && !isDuplicateArtifactName(f, pluginsSrc)).forEach((f) => {
1624
1639
  fs.copyFileSync(path.join(pluginsSrc, f), path.join(pluginsDest, f));
1625
1640
  });
1626
1641
  }
@@ -1772,7 +1787,7 @@ async function main() {
1772
1787
  const copyDirRec2 = (src, dest) => {
1773
1788
  fs.mkdirSync(dest, { recursive: true });
1774
1789
  fs.readdirSync(src).forEach(item => {
1775
- if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db")) return;
1790
+ if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db") || isDuplicateArtifactName(item, src)) return;
1776
1791
  const srcP = path.join(src, item);
1777
1792
  const destP = path.join(dest, item);
1778
1793
  if (fs.statSync(srcP).isDirectory()) copyDirRec2(srcP, destP);
@@ -1806,7 +1821,7 @@ async function main() {
1806
1821
  const copyDirRec3 = (src, dest) => {
1807
1822
  fs.mkdirSync(dest, { recursive: true });
1808
1823
  fs.readdirSync(src).forEach(item => {
1809
- if (item === "__pycache__" || item.endsWith(".pyc")) return;
1824
+ if (item === "__pycache__" || item.endsWith(".pyc") || isDuplicateArtifactName(item, src)) return;
1810
1825
  const srcP = path.join(src, item);
1811
1826
  const destP = path.join(dest, item);
1812
1827
  if (fs.statSync(srcP).isDirectory()) copyDirRec3(srcP, destP);
@@ -1831,6 +1846,7 @@ async function main() {
1831
1846
  if (fs.existsSync(templatesSrc)) {
1832
1847
  fs.mkdirSync(templatesDest, { recursive: true });
1833
1848
  for (const f of fs.readdirSync(templatesSrc)) {
1849
+ if (isDuplicateArtifactName(f, templatesSrc)) continue;
1834
1850
  const src = path.join(templatesSrc, f);
1835
1851
  const dest = path.join(templatesDest, f);
1836
1852
  if (fs.statSync(src).isFile()) {
@@ -1838,6 +1854,7 @@ async function main() {
1838
1854
  } else if (fs.statSync(src).isDirectory()) {
1839
1855
  fs.mkdirSync(dest, { recursive: true });
1840
1856
  for (const sf of fs.readdirSync(src)) {
1857
+ if (isDuplicateArtifactName(sf, src)) continue;
1841
1858
  const ssrc = path.join(src, sf);
1842
1859
  if (fs.statSync(ssrc).isFile()) {
1843
1860
  fs.copyFileSync(ssrc, path.join(dest, sf));
@@ -2354,7 +2371,7 @@ async function main() {
2354
2371
  const copyDirRecursive = (src, dest) => {
2355
2372
  fs.mkdirSync(dest, { recursive: true });
2356
2373
  fs.readdirSync(src).forEach(item => {
2357
- if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db")) return;
2374
+ if (item === "__pycache__" || item.endsWith(".pyc") || item.endsWith(".db") || isDuplicateArtifactName(item, src)) return;
2358
2375
  const srcPath = path.join(src, item);
2359
2376
  const destPath = path.join(dest, item);
2360
2377
  if (fs.statSync(srcPath).isDirectory()) {
@@ -2461,7 +2478,7 @@ async function main() {
2461
2478
  // Plugins (all .py files in plugins/)
2462
2479
  fs.mkdirSync(path.join(NEXO_HOME, "plugins"), { recursive: true });
2463
2480
  if (fs.existsSync(pluginsSrcDir)) {
2464
- fs.readdirSync(pluginsSrcDir).filter(f => f.endsWith(".py")).forEach((f) => {
2481
+ fs.readdirSync(pluginsSrcDir).filter(f => f.endsWith(".py") && !isDuplicateArtifactName(f, pluginsSrcDir)).forEach((f) => {
2465
2482
  fs.copyFileSync(path.join(pluginsSrcDir, f), path.join(NEXO_HOME, "plugins", f));
2466
2483
  });
2467
2484
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexo-brain",
3
- "version": "5.3.28",
3
+ "version": "5.3.30",
4
4
  "mcpName": "io.github.wazionapps/nexo",
5
5
  "description": "NEXO Brain — Shared brain for AI agents. Persistent memory, semantic RAG, natural forgetting, metacognitive guard, trust scoring, 150+ MCP tools. Works with Claude Code, Codex, Claude Desktop & any MCP client. 100% local, free.",
6
6
  "homepage": "https://nexo-brain.com",
@@ -18,6 +18,7 @@ import time
18
18
  from pathlib import Path
19
19
 
20
20
  from runtime_home import export_resolved_nexo_home, managed_nexo_home
21
+ from tree_hygiene import is_duplicate_artifact_name
21
22
 
22
23
  NEXO_HOME = export_resolved_nexo_home()
23
24
  DATA_DIR = NEXO_HOME / "data"
@@ -63,6 +64,20 @@ def _log(msg: str):
63
64
  print(f"[NEXO auto-update] {msg}", file=sys.stderr)
64
65
 
65
66
 
67
+ def _runtime_copy_ignore(*extra_patterns: str):
68
+ base_ignore = shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo", "*.db", *extra_patterns)
69
+
70
+ def _ignore(dir_name: str, names: list[str]) -> set[str]:
71
+ ignored = set(base_ignore(dir_name, names))
72
+ ignored.update(
73
+ name for name in names
74
+ if is_duplicate_artifact_name(Path(dir_name) / name)
75
+ )
76
+ return ignored
77
+
78
+ return _ignore
79
+
80
+
66
81
  def _critical_table_count(db_path: Path, table: str) -> int | None:
67
82
  """Return COUNT(*) for a critical table when it exists, otherwise None."""
68
83
  import sqlite3
@@ -470,7 +485,7 @@ def _refresh_installed_manifest():
470
485
  if src_crons.exists():
471
486
  dst_crons.mkdir(parents=True, exist_ok=True)
472
487
  for f in src_crons.iterdir():
473
- if f.is_file():
488
+ if f.is_file() and not is_duplicate_artifact_name(f):
474
489
  shutil.copy2(str(f), str(dst_crons / f.name))
475
490
  _log("Refreshed installed crons manifest")
476
491
  except Exception as e:
@@ -746,7 +761,7 @@ def _sync_hooks():
746
761
  hooks_dest.mkdir(parents=True, exist_ok=True)
747
762
  synced = 0
748
763
  for f in hooks_src.iterdir():
749
- if f.is_file() and f.suffix == ".sh":
764
+ if f.is_file() and f.suffix == ".sh" and not is_duplicate_artifact_name(f):
750
765
  dest = hooks_dest / f.name
751
766
  shutil.copy2(str(f), str(dest))
752
767
  os.chmod(str(dest), 0o755)
@@ -1441,7 +1456,7 @@ def _auto_update_check_locked() -> dict:
1441
1456
  import shutil
1442
1457
  scripts_dest.mkdir(parents=True, exist_ok=True)
1443
1458
  for f in scripts_src.iterdir():
1444
- if f.name.startswith('.') or f.name == '__pycache__':
1459
+ if f.name.startswith('.') or f.name == '__pycache__' or is_duplicate_artifact_name(f):
1445
1460
  continue
1446
1461
  dest = scripts_dest / f.name
1447
1462
  if f.is_file() and not dest.exists():
@@ -1475,12 +1490,12 @@ def _auto_update_check_locked() -> dict:
1475
1490
  if doctor_src.is_dir():
1476
1491
  import shutil
1477
1492
  if not doctor_dest.is_dir():
1478
- shutil.copytree(str(doctor_src), str(doctor_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
1493
+ shutil.copytree(str(doctor_src), str(doctor_dest), ignore=_runtime_copy_ignore())
1479
1494
  _log("Backfilled doctor package")
1480
1495
  else:
1481
1496
  # Update existing files
1482
1497
  for root, dirs, files in os.walk(str(doctor_src)):
1483
- dirs[:] = [d for d in dirs if d != "__pycache__"]
1498
+ dirs[:] = [d for d in dirs if d != "__pycache__" and not is_duplicate_artifact_name(Path(root) / d)]
1484
1499
  rel = os.path.relpath(root, str(doctor_src))
1485
1500
  dest_dir = doctor_dest / rel
1486
1501
  dest_dir.mkdir(parents=True, exist_ok=True)
@@ -1488,6 +1503,8 @@ def _auto_update_check_locked() -> dict:
1488
1503
  if f.endswith(".pyc"):
1489
1504
  continue
1490
1505
  src_f = Path(root) / f
1506
+ if is_duplicate_artifact_name(src_f):
1507
+ continue
1491
1508
  dst_f = dest_dir / f
1492
1509
  if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
1493
1510
  shutil.copy2(str(src_f), str(dst_f))
@@ -1501,11 +1518,11 @@ def _auto_update_check_locked() -> dict:
1501
1518
  if skills_src.is_dir():
1502
1519
  import shutil
1503
1520
  if not skills_dest.is_dir():
1504
- shutil.copytree(str(skills_src), str(skills_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
1521
+ shutil.copytree(str(skills_src), str(skills_dest), ignore=_runtime_copy_ignore())
1505
1522
  _log("Backfilled skills-core")
1506
1523
  else:
1507
1524
  for root, dirs, files in os.walk(str(skills_src)):
1508
- dirs[:] = [d for d in dirs if d != "__pycache__"]
1525
+ dirs[:] = [d for d in dirs if d != "__pycache__" and not is_duplicate_artifact_name(Path(root) / d)]
1509
1526
  rel = os.path.relpath(root, str(skills_src))
1510
1527
  dest_dir = skills_dest / rel
1511
1528
  dest_dir.mkdir(parents=True, exist_ok=True)
@@ -1513,6 +1530,8 @@ def _auto_update_check_locked() -> dict:
1513
1530
  if f.endswith(".pyc"):
1514
1531
  continue
1515
1532
  src_f = Path(root) / f
1533
+ if is_duplicate_artifact_name(src_f):
1534
+ continue
1516
1535
  dst_f = dest_dir / f
1517
1536
  if not dst_f.exists() or src_f.stat().st_mtime > dst_f.stat().st_mtime:
1518
1537
  shutil.copy2(str(src_f), str(dst_f))
@@ -1539,7 +1558,7 @@ def _auto_update_check_locked() -> dict:
1539
1558
  import shutil
1540
1559
  if templates_src.is_dir():
1541
1560
  for item in templates_src.iterdir():
1542
- if item.name == "__pycache__":
1561
+ if item.name == "__pycache__" or is_duplicate_artifact_name(item):
1543
1562
  continue
1544
1563
  dest_item = templates_dest / item.name
1545
1564
  if item.is_file():
@@ -1548,7 +1567,7 @@ def _auto_update_check_locked() -> dict:
1548
1567
  elif item.is_dir():
1549
1568
  dest_item.mkdir(parents=True, exist_ok=True)
1550
1569
  for sub in item.iterdir():
1551
- if sub.is_file():
1570
+ if sub.is_file() and not is_duplicate_artifact_name(sub):
1552
1571
  dest_sub = dest_item / sub.name
1553
1572
  if not dest_sub.exists() or sub.stat().st_mtime > dest_sub.stat().st_mtime:
1554
1573
  shutil.copy2(str(sub), str(dest_sub))
@@ -1735,6 +1754,8 @@ def _discover_runtime_root_python_modules(base_dir: Path) -> list[str]:
1735
1754
  continue
1736
1755
  if item.name.startswith(".") or item.name == "__init__.py":
1737
1756
  continue
1757
+ if is_duplicate_artifact_name(item):
1758
+ continue
1738
1759
  modules.append(item.name)
1739
1760
  return modules
1740
1761
 
@@ -1857,7 +1878,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1857
1878
  shutil.copytree(
1858
1879
  str(pkg_src),
1859
1880
  str(pkg_dest),
1860
- ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo", "*.db"),
1881
+ ignore=_runtime_copy_ignore(),
1861
1882
  )
1862
1883
  copied_packages += 1
1863
1884
 
@@ -1874,7 +1895,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1874
1895
  if plugins_src.is_dir():
1875
1896
  plugins_dest.mkdir(parents=True, exist_ok=True)
1876
1897
  for item in plugins_src.iterdir():
1877
- if item.is_file() and item.suffix == ".py":
1898
+ if item.is_file() and item.suffix == ".py" and not is_duplicate_artifact_name(item):
1878
1899
  shutil.copy2(str(item), str(plugins_dest / item.name))
1879
1900
 
1880
1901
  _emit_progress(progress_fn, "Copying scripts...")
@@ -1883,13 +1904,13 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1883
1904
  if scripts_src.is_dir():
1884
1905
  scripts_dest.mkdir(parents=True, exist_ok=True)
1885
1906
  for item in scripts_src.iterdir():
1886
- if item.name == "__pycache__" or item.name.startswith("."):
1907
+ if item.name == "__pycache__" or item.name.startswith(".") or is_duplicate_artifact_name(item):
1887
1908
  continue
1888
1909
  dst = scripts_dest / item.name
1889
1910
  if item.is_dir():
1890
1911
  if dst.exists():
1891
1912
  shutil.rmtree(str(dst), ignore_errors=True)
1892
- shutil.copytree(str(item), str(dst), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
1913
+ shutil.copytree(str(item), str(dst), ignore=_runtime_copy_ignore())
1893
1914
  elif item.is_file():
1894
1915
  existing_class = installed_script_classes.get(item.name, "")
1895
1916
  if dst.exists() and existing_class in {"personal", "non-script"}:
@@ -1919,7 +1940,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1919
1940
  if templates_src.is_dir():
1920
1941
  templates_dest.mkdir(parents=True, exist_ok=True)
1921
1942
  for item in templates_src.iterdir():
1922
- if item.name == "__pycache__":
1943
+ if item.name == "__pycache__" or is_duplicate_artifact_name(item):
1923
1944
  continue
1924
1945
  if item.is_file():
1925
1946
  shutil.copy2(str(item), str(templates_dest / item.name))
@@ -1927,7 +1948,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1927
1948
  sub_dest = templates_dest / item.name
1928
1949
  sub_dest.mkdir(parents=True, exist_ok=True)
1929
1950
  for sub in item.iterdir():
1930
- if sub.is_file():
1951
+ if sub.is_file() and not is_duplicate_artifact_name(sub):
1931
1952
  shutil.copy2(str(sub), str(sub_dest / sub.name))
1932
1953
 
1933
1954
  package_json = repo_dir / "package.json"
@@ -1948,7 +1969,7 @@ def _copy_runtime_from_source(src_dir: Path, repo_dir: Path, dest: Path = NEXO_H
1948
1969
  if skills_src.is_dir():
1949
1970
  if skills_dest.exists():
1950
1971
  shutil.rmtree(str(skills_dest), ignore_errors=True)
1951
- shutil.copytree(str(skills_src), str(skills_dest), ignore=shutil.ignore_patterns("__pycache__", "*.pyc"))
1972
+ shutil.copytree(str(skills_src), str(skills_dest), ignore=_runtime_copy_ignore())
1952
1973
 
1953
1974
  bin_dir = dest / "bin"
1954
1975
  bin_dir.mkdir(parents=True, exist_ok=True)
package/src/cli.py CHANGED
@@ -1944,6 +1944,21 @@ def main():
1944
1944
  dashboard_parser = sub.add_parser("dashboard", help="Web dashboard control")
1945
1945
  dashboard_parser.add_argument("action", choices=["on", "off", "status"], help="Start, stop, or check dashboard")
1946
1946
 
1947
+ # -- desktop bridge (read-only, for NEXO Desktop and any external UI) --
1948
+ schema_parser = sub.add_parser("schema", help="Editable-field schema for Preferences UI")
1949
+ schema_parser.add_argument("--json", action="store_true", help="JSON output (default)")
1950
+
1951
+ identity_parser = sub.add_parser("identity", help="Canonical assistant identity")
1952
+ identity_parser.add_argument("--json", action="store_true", help="JSON output (default)")
1953
+
1954
+ onboard_parser = sub.add_parser("onboard", help="Onboarding wizard steps")
1955
+ onboard_parser.add_argument("--json", action="store_true", help="JSON output (default)")
1956
+
1957
+ scan_profile_parser = sub.add_parser("scan-profile", help="Build profile.json from CLAUDE.md + calibration")
1958
+ scan_profile_parser.add_argument("--json", action="store_true", help="JSON output")
1959
+ scan_profile_parser.add_argument("--apply", action="store_true", help="Write profile.json (default is preview)")
1960
+ scan_profile_parser.add_argument("--force", action="store_true", help="Overwrite existing profile.json on --apply")
1961
+
1947
1962
  args = parser.parse_args()
1948
1963
 
1949
1964
  if args.help or (not args.command and not args.version):
@@ -2037,6 +2052,14 @@ def main():
2037
2052
  return _uninstall(args)
2038
2053
  elif args.command == "dashboard":
2039
2054
  return _dashboard(args)
2055
+ elif args.command in ("schema", "identity", "onboard", "scan-profile"):
2056
+ from desktop_bridge import cmd_schema, cmd_identity, cmd_onboard, cmd_scan_profile
2057
+ return {
2058
+ "schema": cmd_schema,
2059
+ "identity": cmd_identity,
2060
+ "onboard": cmd_onboard,
2061
+ "scan-profile": cmd_scan_profile,
2062
+ }[args.command](args)
2040
2063
  else:
2041
2064
  _print_help()
2042
2065
  return 0