gaia-cli 4.3.5__tar.gz → 4.3.7__tar.gz

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 (103) hide show
  1. {gaia_cli-4.3.5/src/gaia_cli.egg-info → gaia_cli-4.3.7}/PKG-INFO +2 -2
  2. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/README.md +1 -1
  3. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/pyproject.toml +1 -1
  4. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/cardRenderer.py +14 -0
  5. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/commands/stats.py +3 -1
  6. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/graph.py +1 -1
  7. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/main.py +32 -20
  8. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/pathEngine.py +6 -1
  9. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/prWriter.py +5 -0
  10. {gaia_cli-4.3.5 → gaia_cli-4.3.7/src/gaia_cli.egg-info}/PKG-INFO +2 -2
  11. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_card_renderer.py +27 -0
  12. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_dx.py +22 -0
  13. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_intake.py +2 -1
  14. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_path_command.py +8 -8
  15. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_push.py +2 -2
  16. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_stats.py +20 -0
  17. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_validate.py +1 -1
  18. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/LICENSE +0 -0
  19. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/setup.cfg +0 -0
  20. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/__init__.py +0 -0
  21. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/__main__.py +0 -0
  22. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/authz.py +0 -0
  23. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/combinator.py +0 -0
  24. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/commands/__init__.py +0 -0
  25. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/commands/dev.py +0 -0
  26. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/gaia.json +0 -0
  27. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/named/devin-ai/autonomous-swe.md +0 -0
  28. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/named/karpathy/autoresearch.md +0 -0
  29. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/named-skills.json +0 -0
  30. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/combination.schema.json +0 -0
  31. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/meta.json +0 -0
  32. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/namedSkill.schema.json +0 -0
  33. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/pluginConfig.schema.json +0 -0
  34. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/realSkillCatalog.schema.json +0 -0
  35. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/skill.schema.json +0 -0
  36. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/skillBatch.schema.json +0 -0
  37. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/skillSuite.schema.json +0 -0
  38. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/data/registry/schema/skillTree.schema.json +0 -0
  39. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/embeddings.py +0 -0
  40. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/evidence.py +0 -0
  41. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/formatting.py +0 -0
  42. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/hook.py +0 -0
  43. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/install.py +0 -0
  44. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/interactive.py +0 -0
  45. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/leveling.py +0 -0
  46. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/localContext.py +0 -0
  47. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/name.py +0 -0
  48. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/promotion.py +0 -0
  49. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/push.py +0 -0
  50. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/redaction.py +0 -0
  51. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/registry.py +0 -0
  52. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/resolver.py +0 -0
  53. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/scanner.py +0 -0
  54. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/semantic_search.py +0 -0
  55. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/timeline.py +0 -0
  56. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/treeManager.py +0 -0
  57. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/__init__.py +0 -0
  58. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/app.py +0 -0
  59. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/__init__.py +0 -0
  60. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/agent.py +0 -0
  61. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/hero.py +0 -0
  62. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/levelup.py +0 -0
  63. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/scan.py +0 -0
  64. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/screens/tree.py +0 -0
  65. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/theme.tcss +0 -0
  66. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/tui/tokens.py +0 -0
  67. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli/versioning.py +0 -0
  68. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli.egg-info/SOURCES.txt +0 -0
  69. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli.egg-info/dependency_links.txt +0 -0
  70. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli.egg-info/entry_points.txt +0 -0
  71. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli.egg-info/requires.txt +0 -0
  72. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/src/gaia_cli.egg-info/top_level.txt +0 -0
  73. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_authz.py +0 -0
  74. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_crawlers.py +0 -0
  75. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_docs_site.py +0 -0
  76. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_docs_skill_explorer.py +0 -0
  77. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_embeddings.py +0 -0
  78. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_evidence_inheritance.py +0 -0
  79. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_formatting.py +0 -0
  80. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_graph.py +0 -0
  81. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_install.py +0 -0
  82. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_interactive.py +0 -0
  83. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_leveling.py +0 -0
  84. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_lifecycle.py +0 -0
  85. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_local_context.py +0 -0
  86. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_meta_ops.py +0 -0
  87. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_named_skills.py +0 -0
  88. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_packaging.py +0 -0
  89. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_path_engine.py +0 -0
  90. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_pr540_review.py +0 -0
  91. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_pr_writer.py +0 -0
  92. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_promotion.py +0 -0
  93. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_real_skill_catalog.py +0 -0
  94. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_redaction.py +0 -0
  95. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_registry_layout.py +0 -0
  96. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_scanner.py +0 -0
  97. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_suite_install.py +0 -0
  98. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_timelines.py +0 -0
  99. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_treeManager.py +0 -0
  100. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_tui_tokens.py +0 -0
  101. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_unlocked_at_datetime.py +0 -0
  102. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_verify_evidence.py +0 -0
  103. {gaia_cli-4.3.5 → gaia_cli-4.3.7}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaia-cli
3
- Version: 4.3.5
3
+ Version: 4.3.7
4
4
  Summary: Gaia AI Agent Skill Registry CLI
5
5
  Author: Gaia contributors
6
6
  License: MIT
@@ -167,7 +167,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
167
167
  **1. Install the CLI**
168
168
 
169
169
  <!-- gaia:version-start -->
170
- Current Gaia CLI version: `4.3.5`.
170
+ Current Gaia CLI version: `4.3.7`.
171
171
 
172
172
  ```bash
173
173
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -118,7 +118,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
118
118
  **1. Install the CLI**
119
119
 
120
120
  <!-- gaia:version-start -->
121
- Current Gaia CLI version: `4.3.5`.
121
+ Current Gaia CLI version: `4.3.7`.
122
122
 
123
123
  ```bash
124
124
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gaia-cli"
7
- version = "4.3.5"
7
+ version = "4.3.7"
8
8
  description = "Gaia AI Agent Skill Registry CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -561,6 +561,20 @@ def render_appraise_card(
561
561
  deriv_line = prefix + ", ".join(deriv_items)
562
562
  if len(derivatives) > 4:
563
563
  deriv_line += f" +{len(derivatives) - 4} more"
564
+ # Truncate at comma boundary instead of mid-word
565
+ if len(deriv_line) > inner:
566
+ truncated = prefix
567
+ for i, item in enumerate(deriv_items):
568
+ candidate = truncated + (", " if i > 0 else "") + item
569
+ if len(candidate) > inner - 2: # leave room for " …"
570
+ remaining = len(deriv_items) - i + (len(derivatives) - 4 if len(derivatives) > 4 else 0)
571
+ truncated += f" +{remaining} more" if remaining > 0 else ""
572
+ break
573
+ truncated = candidate
574
+ else:
575
+ if len(derivatives) > 4:
576
+ truncated += f" +{len(derivatives) - 4} more"
577
+ deriv_line = truncated
564
578
  lines.append(f"{bc}{V}{r} {fg(*COLOR_MUTED)}{_pad(deriv_line, inner)}{r} {bc}{V}{r}")
565
579
  else:
566
580
  lines.append(f"{bc}{V}{r} {fg(*COLOR_MUTED)}{_pad('Unlocks: (terminal skill)', inner)}{r} {bc}{V}{r}")
@@ -133,7 +133,9 @@ def collect_stats(registry_path: str | Path) -> dict:
133
133
  ]
134
134
 
135
135
  type_counts = Counter(skill.get("type", "unknown") for skill in skills)
136
- level_counts = Counter(skill.get("level", "?") for skill in skills)
136
+ level_counts = Counter(
137
+ skill["level"] for skill in skills if "level" in skill
138
+ )
137
139
  effective_level_counts = Counter()
138
140
  demerit_counts: Counter[str] = Counter()
139
141
  skills_with_demerits = 0
@@ -1205,7 +1205,7 @@ def graph_command(args: Any) -> None:
1205
1205
  pass # Degrade gracefully to canon mode
1206
1206
 
1207
1207
  out_path = write_graph_artifact(registry_path, output=output, fmt=fmt, user_ctx=user_ctx)
1208
- print(f" saved {os.path.basename(out_path)}")
1208
+ print(f" saved {out_path}")
1209
1209
 
1210
1210
  # Regenerate the GEXF from current node data
1211
1211
  if fmt == "html":
@@ -294,8 +294,8 @@ def init_command(args):
294
294
  config_dir = '.gaia'
295
295
  os.makedirs(config_dir, exist_ok=True)
296
296
  config_path = os.path.join(config_dir, 'config.toml')
297
- if os.path.exists(config_path):
298
- print("Gaia is already initialized in this repository.")
297
+ if os.path.exists(config_path) and not getattr(args, "force", False):
298
+ print("Gaia is already initialized in this repository. Use --force to overwrite.")
299
299
  return
300
300
 
301
301
  username = args.user or _detect_github_username()
@@ -472,16 +472,20 @@ def scan_command(args):
472
472
  skill_map = {s['id']: s for s in graph_data.get('skills', [])}
473
473
  unlocked = [s.get('skillId') for s in tree.get('unlockedSkills', [])]
474
474
  combos = get_combinations(graph_data, unlocked, resolved)
475
- if combos and not quiet:
476
- print("\nNew fusion candidates:")
477
- for c in combos:
478
- result_skill = skill_map.get(c['candidateResult'], {})
479
- result_type = result_skill.get('type', 'extra')
480
- print(render_fusion_diagram(
481
- c['detectedSkills'], c['candidateResult'], result_type,
482
- canon=canon, ctx=ctx
483
- ))
484
- print("Run `gaia fuse <skill>` to confirm.")
475
+ if combos:
476
+ # Persist fusion candidates so `gaia fuse` can find them
477
+ tree['pendingCombinations'] = combos
478
+ save_tree(username, tree, registry_path=args.registry)
479
+ if not quiet:
480
+ print("\nNew fusion candidates:")
481
+ for c in combos:
482
+ result_skill = skill_map.get(c['candidateResult'], {})
483
+ result_type = result_skill.get('type', 'extra')
484
+ print(render_fusion_diagram(
485
+ c['detectedSkills'], c['candidateResult'], result_type,
486
+ canon=canon, ctx=ctx
487
+ ))
488
+ print("Run `gaia fuse <skill>` to confirm.")
485
489
 
486
490
  # Path engine integration
487
491
  old_paths = load_paths()
@@ -537,7 +541,7 @@ def render_user_tree_outputs(username: str, tree: dict | None, graph_data: dict
537
541
  with open(html_path, "w", encoding="utf-8") as f:
538
542
  f.write(html)
539
543
  if not quiet:
540
- print(f" saved {os.path.basename(html_path)} & {os.path.basename(md_path)}")
544
+ print(f" saved {html_path} & {md_path}")
541
545
  return html_path, md_path
542
546
 
543
547
 
@@ -599,7 +603,8 @@ def lookup_command(args):
599
603
  display = skill.get("name") or f"/{skill_id}"
600
604
  print(f"{display}")
601
605
 
602
- print(f"Type: {skill.get('type', 'unknown')} Level: {skill.get('level', '?')}")
606
+ user_level = ctx.skill_level(skill_id) if ctx else skill.get('level', '?')
607
+ print(f"Type: {skill.get('type', 'unknown')} Level: {user_level}")
603
608
  if skill.get("description"):
604
609
  print(skill["description"])
605
610
 
@@ -792,7 +797,7 @@ def promote_command(args):
792
797
  return
793
798
  if not skill_id:
794
799
  # Try interactive picker
795
- candidates = promotable_candidates(username, args.registry)
800
+ candidates = promotable_candidates(args.registry, username)
796
801
  if candidates:
797
802
  picked = select_promotion_candidate(candidates, "Select skill to promote:")
798
803
  if picked:
@@ -1284,9 +1289,9 @@ def install_command(args):
1284
1289
  interactive_install(args.registry, location=location)
1285
1290
  return
1286
1291
  if not args.skill_id:
1287
- # Bare 'gaia install' -> update/sync all
1288
- update_skills(args.registry)
1289
- return
1292
+ print("Usage: gaia install <skill_id>", file=sys.stderr)
1293
+ print(" To update all installed skills, use: gaia update", file=sys.stderr)
1294
+ sys.exit(2)
1290
1295
 
1291
1296
  # Use suite logic if flagged or implicitly requested
1292
1297
  if getattr(args, 'ultimate', False) or getattr(args, 'suite', False):
@@ -1300,7 +1305,7 @@ def install_command(args):
1300
1305
 
1301
1306
  def uninstall_command(args):
1302
1307
  from gaia_cli.install import uninstall_skill
1303
- success = uninstall_skill(args.skill_id)
1308
+ success = uninstall_skill(args.skill_id.lstrip("/"))
1304
1309
  if not success:
1305
1310
  sys.exit(1)
1306
1311
 
@@ -1362,7 +1367,7 @@ def skills_command(args):
1362
1367
 
1363
1368
 
1364
1369
  available = [
1365
- {"id": sid, "name": meta.get("name") or sid, "level": meta.get("level", "?"), "description": meta.get("description", "")}
1370
+ {"id": sid, "name": meta.get("name") or sid, "level": meta.get("level", "?"), "type": meta.get("type", "basic"), "description": meta.get("description", "")}
1366
1371
  for sid, meta in list_available(args.registry)
1367
1372
  ]
1368
1373
  items = available + pending
@@ -1459,6 +1464,12 @@ def pull_command(args):
1459
1464
  print("Note: Registry could not be updated via git (no upstream configured). Local registry unchanged.", file=sys.stderr)
1460
1465
  else:
1461
1466
  print(f"Warning: git pull failed. Local registry unchanged.\n {stderr}", file=sys.stderr)
1467
+ else:
1468
+ stdout = res.stdout.strip()
1469
+ if "Already up to date" in stdout:
1470
+ print("Registry is already up to date.")
1471
+ else:
1472
+ print("Registry updated successfully.")
1462
1473
 
1463
1474
 
1464
1475
  def update_command(args):
@@ -1647,6 +1658,7 @@ def get_parser():
1647
1658
  init_parser.add_argument('--registry-ref', help='Gaia registry URL to write into .gaia/config.toml')
1648
1659
  init_parser.add_argument('--scan', action='append', help='Path to scan; repeat for multiple paths')
1649
1660
  init_parser.add_argument('--yes', action='store_true', help='Use non-interactive defaults')
1661
+ init_parser.add_argument('--force', action='store_true', help='Overwrite existing .gaia/config.toml')
1650
1662
  init_parser.add_argument(
1651
1663
  '--auto-prompt-combinations',
1652
1664
  action='store_true',
@@ -144,7 +144,12 @@ def render_unlock_path(
144
144
  total += c_total
145
145
  return owned, total
146
146
 
147
- owned_count, total_count = _count(tree)
147
+ # Count prerequisites only (children of root — root is the target, not a prereq).
148
+ owned_count, total_count = 0, 0
149
+ for child in tree.get('children', []):
150
+ c_owned, c_total = _count(child)
151
+ owned_count += c_owned
152
+ total_count += c_total
148
153
 
149
154
  # Render root line manually.
150
155
  root_sid = tree['id']
@@ -203,6 +203,11 @@ def open_intake_issue(username, batch_data, batch_path=None, repo_root="."):
203
203
 
204
204
  issue_url = issue.stdout.strip().splitlines()[-1] if issue.stdout.strip() else ""
205
205
  print(f"Success: intake issue opened ({issue_url}).")
206
+ # Clean up temp body file
207
+ try:
208
+ os.remove(body_path)
209
+ except OSError:
210
+ pass
206
211
  return issue_url or None
207
212
 
208
213
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gaia-cli
3
- Version: 4.3.5
3
+ Version: 4.3.7
4
4
  Summary: Gaia AI Agent Skill Registry CLI
5
5
  Author: Gaia contributors
6
6
  License: MIT
@@ -167,7 +167,7 @@ Skills rank up through **verifiable evidence** (Class A/B/C) and can be demoted
167
167
  **1. Install the CLI**
168
168
 
169
169
  <!-- gaia:version-start -->
170
- Current Gaia CLI version: `4.3.5`.
170
+ Current Gaia CLI version: `4.3.7`.
171
171
 
172
172
  ```bash
173
173
  curl -fsSL https://gaia.tiongson.co/install.sh | sh
@@ -18,6 +18,7 @@ from gaia_cli.cardRenderer import (
18
18
  load_and_render,
19
19
  _pad,
20
20
  _wrap_lines,
21
+ render_appraise_card,
21
22
  )
22
23
 
23
24
 
@@ -520,3 +521,29 @@ class TestRenderFusionDiagram:
520
521
  # First line (shorter name) should be padded
521
522
  assert "/ab" in lines[0]
522
523
  assert "/longername" in lines[2]
524
+
525
+
526
+ def test_render_appraise_card_truncation():
527
+ skill_data = {
528
+ "id": "test-skill",
529
+ "name": "Test Skill",
530
+ "type": "basic",
531
+ "level": "0★",
532
+ "rarity": "common",
533
+ "description": "Short description.",
534
+ }
535
+ derivatives = [
536
+ {"id": "d1", "name": "First Derivative Skill Name"},
537
+ {"id": "d2", "name": "Second Derivative Skill Name"},
538
+ {"id": "d3", "name": "Third Derivative Skill Name"},
539
+ ]
540
+ output = render_appraise_card(
541
+ skill_data=skill_data,
542
+ prereq_status={},
543
+ derivatives=derivatives,
544
+ actions=[],
545
+ )
546
+ assert "First Derivative Skill Name" in output
547
+ assert "+2 more" in output
548
+ assert "Second Derivative" not in output
549
+
@@ -445,3 +445,25 @@ def test_gaia_uninstall_command_removes_skill(tmp_path, monkeypatch):
445
445
  from gaia_cli.install import load_manifest
446
446
  manifest = load_manifest()
447
447
  assert not any(e["id"] == "testuser/test-skill" for e in manifest["installed"])
448
+
449
+
450
+ def test_init_force_overwrite(tmp_path, monkeypatch, capsys):
451
+ monkeypatch.chdir(tmp_path)
452
+
453
+ # First init
454
+ run_cli(monkeypatch, ["init", "--user", "firstuser", "--yes"])
455
+ config = parse_config(tmp_path / ".gaia" / "config.toml")
456
+ assert config["username"] == "firstuser"
457
+
458
+ # Second init without --force (should print message and not overwrite)
459
+ run_cli(monkeypatch, ["init", "--user", "seconduser", "--yes"])
460
+ output = capsys.readouterr().out
461
+ assert "Gaia is already initialized in this repository. Use --force to overwrite." in output
462
+ config = parse_config(tmp_path / ".gaia" / "config.toml")
463
+ assert config["username"] == "firstuser" # unchanged
464
+
465
+ # Third init with --force (should overwrite)
466
+ run_cli(monkeypatch, ["init", "--user", "seconduser", "--yes", "--force"])
467
+ config = parse_config(tmp_path / ".gaia" / "config.toml")
468
+ assert config["username"] == "seconduser" # overwritten
469
+
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import os
3
3
  import subprocess
4
+ import sys
4
5
  import tempfile
5
6
  import unittest
6
7
 
@@ -15,7 +16,7 @@ def run_validate_intake(intake_dir, *extra_args):
15
16
  env["PYTHONIOENCODING"] = "utf-8"
16
17
  result = subprocess.run(
17
18
  [
18
- "python3",
19
+ sys.executable,
19
20
  VALIDATE_INTAKE_SCRIPT,
20
21
  "--intake-dir",
21
22
  intake_dir,
@@ -236,23 +236,23 @@ class TestRenderUnlockPath:
236
236
  def test_basic_leaf_renders(self, linear_graph):
237
237
  text = render_unlock_path(linear_graph, "a", set())
238
238
  assert "a" in text
239
- # leaf has no children, summary should say 0 owned out of 1
240
- assert "0 / 1" in text
239
+ # leaf has no children, summary should say 0 owned out of 0
240
+ assert "0 / 0" in text
241
241
 
242
242
  def test_footer_counts_correct(self, linear_graph):
243
243
  text = render_unlock_path(linear_graph, "c", set())
244
- # 3 nodes total (a, b, c), 0 owned
245
- assert "0 / 3" in text
246
- assert "3 skill(s) needed" in text
244
+ # 2 prerequisites total (a, b), 0 owned
245
+ assert "0 / 2" in text
246
+ assert "2 skill(s) needed" in text
247
247
 
248
248
  def test_footer_with_owned(self, linear_graph):
249
249
  # unlock_path stops recursing into owned nodes, so the visible tree
250
250
  # for 'c' with owned={'a','b'} is: c(missing) + b(owned, no children).
251
251
  # 'a' is not in the tree because b is already owned → recursion stops.
252
- # Count: 2 nodes total, 1 owned (b), 1 missing (c).
252
+ # Count: 1 prerequisite total (b), 1 owned (b).
253
253
  text = render_unlock_path(linear_graph, "c", {"a", "b"})
254
- assert "1 / 2" in text
255
- assert "1 skill(s) needed" in text
254
+ assert "1 / 1" in text
255
+ assert "0 skill(s) needed" in text
256
256
 
257
257
  def test_owned_only_prunes_owned_branches(self, linear_graph):
258
258
  """--owned-only prunes the display of owned 'b' from the output."""
@@ -37,7 +37,7 @@ class TestGaiaPush(unittest.TestCase):
37
37
  env["PYTHONIOENCODING"] = "utf-8"
38
38
  result = subprocess.run(
39
39
  [
40
- "python3",
40
+ sys.executable,
41
41
  CLI_PATH,
42
42
  "--registry",
43
43
  REPO_ROOT,
@@ -83,7 +83,7 @@ class TestGaiaPush(unittest.TestCase):
83
83
  env["PYTHONIOENCODING"] = "utf-8"
84
84
  result = subprocess.run(
85
85
  [
86
- "python3",
86
+ sys.executable,
87
87
  CLI_PATH,
88
88
  "--registry",
89
89
  registry,
@@ -114,3 +114,23 @@ def test_stats_cli_prints_summary(tmp_path, monkeypatch, capsys):
114
114
  assert "Gaia Registry — 4 skills 1 edges" in output
115
115
  assert "Class A" in output
116
116
  assert "Effective level breakdown" in output
117
+
118
+
119
+ def test_collect_stats_missing_level(tmp_path):
120
+ registry = tmp_path / "registry"
121
+ registry.mkdir(parents=True, exist_ok=True)
122
+ (registry / "gaia.json").write_text(
123
+ json.dumps(
124
+ {
125
+ "skills": [
126
+ {"id": "tokenize", "type": "basic", "evidence": []}, # No 'level' key
127
+ ],
128
+ "edges": [],
129
+ }
130
+ ),
131
+ encoding="utf-8",
132
+ )
133
+ stats = collect_stats(tmp_path)
134
+ assert "?" not in stats["level_counts"]
135
+ assert "level_counts" in stats
136
+
@@ -17,7 +17,7 @@ def run_validate(graph_path):
17
17
  env = os.environ.copy()
18
18
  env["PYTHONIOENCODING"] = "utf-8"
19
19
  result = subprocess.run(
20
- ["python3", VALIDATE_SCRIPT, "--graph", graph_path],
20
+ [sys.executable, VALIDATE_SCRIPT, "--graph", graph_path],
21
21
  capture_output=True,
22
22
  text=True,
23
23
  encoding="utf-8",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes