loki-mode 7.16.1 → 7.17.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/agents/hub_install.py +652 -0
- package/autonomy/lib/assets_bundle.py +446 -0
- package/autonomy/lib/proof-generator.py +10 -1
- package/autonomy/lib/wiki-generator.py +5 -1
- package/autonomy/loki +489 -12
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/R10-MARKETPLACE-PLAN.md +137 -0
- package/docs/R8-SHAREABLE-TEAM-ASSETS-PLAN.md +129 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/autonomy/loki
CHANGED
|
@@ -613,6 +613,7 @@ show_help() {
|
|
|
613
613
|
echo " loki quick \"add dark mode\" # Single-task mode (3 iters max)"
|
|
614
614
|
echo " loki demo # 60-second interactive demo"
|
|
615
615
|
echo " loki init -t saas-starter # Scaffold from template"
|
|
616
|
+
echo " loki template install <src> # Install a community PRD template"
|
|
616
617
|
echo ""
|
|
617
618
|
echo " # Session ops + observability"
|
|
618
619
|
echo " loki status [--json] # Current status"
|
|
@@ -621,6 +622,7 @@ show_help() {
|
|
|
621
622
|
echo " loki doctor [--json] # System prereq + skill symlinks"
|
|
622
623
|
echo " loki logs # Tail recent log output"
|
|
623
624
|
echo " loki export json|markdown|csv|timeline # Export session"
|
|
625
|
+
echo " loki assets export team.tgz # Export shareable team assets (redacted)"
|
|
624
626
|
echo " loki cleanup # Kill orphaned processes"
|
|
625
627
|
echo ""
|
|
626
628
|
echo " # Providers + model routing"
|
|
@@ -1218,6 +1220,13 @@ cmd_start() {
|
|
|
1218
1220
|
esac
|
|
1219
1221
|
done
|
|
1220
1222
|
|
|
1223
|
+
# Clear any stale raw-brief marker from a PRIOR run before we decide the
|
|
1224
|
+
# mode of THIS run. Only the brief branch (below) re-writes it. Without this,
|
|
1225
|
+
# a brief run leaves .loki/state/brief.txt behind and a later non-brief run
|
|
1226
|
+
# (PRD or codebase-analysis) would have its proof mislabel source="brief"
|
|
1227
|
+
# with the old one-liner, since proof-generator reads brief.txt first.
|
|
1228
|
+
rm -f "$LOKI_DIR/state/brief.txt" 2>/dev/null || true
|
|
1229
|
+
|
|
1221
1230
|
# v6.84.0: Unified dispatch based on explicit flags or auto-detection
|
|
1222
1231
|
# Precedence: --brief > --issue > --prd > positional auto-detect >
|
|
1223
1232
|
# LOKI_PRD_FILE env
|
|
@@ -1290,6 +1299,15 @@ cmd_start() {
|
|
|
1290
1299
|
synthesize_brief_prd "$brief_prd" "$brief_text"
|
|
1291
1300
|
prd_file="$brief_prd"
|
|
1292
1301
|
|
|
1302
|
+
# Persist the raw one-liner so the proof-of-run can show the actual brief
|
|
1303
|
+
# the user typed ("Built and verified from: <brief>") instead of falling
|
|
1304
|
+
# back to "codebase-analysis / No brief recorded". The synthesized PRD is
|
|
1305
|
+
# kept distinct (brief-prd-$$.md); the raw brief is the stronger, more
|
|
1306
|
+
# honest shareable artifact. proof-generator.py::_collect_spec reads this
|
|
1307
|
+
# as a fallback when no PRD_PATH / generated-prd.md is present.
|
|
1308
|
+
mkdir -p "$LOKI_DIR/state" 2>/dev/null || true
|
|
1309
|
+
printf '%s' "$brief_text" > "$LOKI_DIR/state/brief.txt" 2>/dev/null || true
|
|
1310
|
+
|
|
1293
1311
|
# Apply the shared lightweight profile and flag the TTFV first-run path.
|
|
1294
1312
|
# The signal value ("brief") drives the end-of-run wording in run.sh so
|
|
1295
1313
|
# the message matches what actually ran (lightweight, council off).
|
|
@@ -6298,6 +6316,187 @@ cmd_export() {
|
|
|
6298
6316
|
esac
|
|
6299
6317
|
}
|
|
6300
6318
|
|
|
6319
|
+
# R8: Shareable team assets. Bundles a team's invested, reusable assets
|
|
6320
|
+
# (cross-project + project memory/learnings, the agent registry, PRD
|
|
6321
|
+
# templates, council config, optionally the R5 wiki) into a portable,
|
|
6322
|
+
# REDACTED tarball, and re-imports them into another project or fresh clone.
|
|
6323
|
+
#
|
|
6324
|
+
# Scope vs cmd_export: cmd_export emits a per-run SESSION SNAPSHOT
|
|
6325
|
+
# (json/md/csv/timeline) and is NOT portable or redacted. cmd_assets emits a
|
|
6326
|
+
# portable, redacted TEAM-ASSET tarball -- different scope, different artifact.
|
|
6327
|
+
# Redaction REUSES the single proof_redact chokepoint via the
|
|
6328
|
+
# autonomy/lib/assets_bundle.py helper. No second redactor or parallel exporter.
|
|
6329
|
+
cmd_assets() {
|
|
6330
|
+
local subcommand="${1:-help}"
|
|
6331
|
+
shift 2>/dev/null || true
|
|
6332
|
+
|
|
6333
|
+
local helper="$_LOKI_SCRIPT_DIR/lib/assets_bundle.py"
|
|
6334
|
+
if [ ! -f "$helper" ]; then
|
|
6335
|
+
echo -e "${RED}Error: assets helper not found at $helper${NC}" >&2
|
|
6336
|
+
return 1
|
|
6337
|
+
fi
|
|
6338
|
+
if ! command -v python3 &>/dev/null; then
|
|
6339
|
+
echo -e "${RED}Error: python3 is required for 'loki assets'${NC}" >&2
|
|
6340
|
+
return 1
|
|
6341
|
+
fi
|
|
6342
|
+
|
|
6343
|
+
# Project dir holds .loki/ (memory, council, wiki). Default to cwd.
|
|
6344
|
+
local project_dir
|
|
6345
|
+
project_dir="$(pwd)"
|
|
6346
|
+
|
|
6347
|
+
# repo_root holds agents/types.json + templates/. The export and import
|
|
6348
|
+
# roots are DELIBERATELY ASYMMETRIC (not a bug):
|
|
6349
|
+
# - EXPORT reads from the loki install ($SKILL_DIR), where a team's
|
|
6350
|
+
# edited registry / templates actually live and are read at runtime
|
|
6351
|
+
# (autonomy/loki:9781, 8840 read templates from $SKILL_DIR; cmd_agent
|
|
6352
|
+
# reads agents/types.json from the install first).
|
|
6353
|
+
# - IMPORT writes to the current working directory (the root of the
|
|
6354
|
+
# target clone the user is setting up), NEVER back into the install.
|
|
6355
|
+
# Writing into $SKILL_DIR would clobber the live install's shipped
|
|
6356
|
+
# registry / templates with redacted copies.
|
|
6357
|
+
# Honest limitation: agents/templates restore relative to cwd, so run
|
|
6358
|
+
# `loki assets import` from the root of the target clone. A global-install
|
|
6359
|
+
# user must copy the restored agents/ + templates/ into their install for
|
|
6360
|
+
# loki to read them at runtime (memory/learnings/council/wiki are unaffected
|
|
6361
|
+
# -- those restore to $HOME/.loki and <project>/.loki and take effect
|
|
6362
|
+
# immediately).
|
|
6363
|
+
local export_repo_root="${SKILL_DIR:-$(cd "$_LOKI_SCRIPT_DIR/.." && pwd)}"
|
|
6364
|
+
local import_repo_root="$project_dir"
|
|
6365
|
+
|
|
6366
|
+
case "$subcommand" in
|
|
6367
|
+
--help|-h|help|"")
|
|
6368
|
+
echo -e "${BOLD}loki assets${NC} - Export/import shareable team assets (R8)"
|
|
6369
|
+
echo ""
|
|
6370
|
+
echo "Bundles a team's reusable assets into a portable, redacted"
|
|
6371
|
+
echo "tarball so setup compounds into shared, importable value."
|
|
6372
|
+
echo ""
|
|
6373
|
+
echo "Usage:"
|
|
6374
|
+
echo " loki assets export <bundle.tgz> [--wiki] [--categories a,b,c]"
|
|
6375
|
+
echo " loki assets import <bundle.tgz> [--no-merge] [--into-install]"
|
|
6376
|
+
echo " loki assets inspect <bundle.tgz>"
|
|
6377
|
+
echo ""
|
|
6378
|
+
echo "Assets bundled (default):"
|
|
6379
|
+
echo " learnings ~/.loki/learnings/*.jsonl (cross-project)"
|
|
6380
|
+
echo " memory <project>/.loki/memory/** (episodic/semantic/skills)"
|
|
6381
|
+
echo " agents agents/types.json (agent registry)"
|
|
6382
|
+
echo " templates templates/*.md (PRD templates)"
|
|
6383
|
+
echo " council <project>/.loki/council/*.json (council config/state)"
|
|
6384
|
+
echo " wiki <project>/.loki/wiki/** (opt-in via --wiki)"
|
|
6385
|
+
echo ""
|
|
6386
|
+
echo "All bundled content is redacted via the proof_redact chokepoint:"
|
|
6387
|
+
echo "secrets, keys, tokens, and absolute home/repo paths are stripped"
|
|
6388
|
+
echo "before the bundle is written. Originals on disk are never changed."
|
|
6389
|
+
echo ""
|
|
6390
|
+
echo "Restore locations on import:"
|
|
6391
|
+
echo " learnings -> \$HOME/.loki/learnings (effective immediately)"
|
|
6392
|
+
echo " memory -> <cwd>/.loki/memory (effective immediately)"
|
|
6393
|
+
echo " council -> <cwd>/.loki/council (effective immediately)"
|
|
6394
|
+
echo " wiki -> <cwd>/.loki/wiki (effective immediately)"
|
|
6395
|
+
echo " agents -> <cwd>/agents/types.json"
|
|
6396
|
+
echo " templates -> <cwd>/templates/*.md"
|
|
6397
|
+
echo "Run import from the ROOT of the target clone. Note: agents and"
|
|
6398
|
+
echo "templates are read by loki from its install dir at runtime. On a"
|
|
6399
|
+
echo "global install, pass --into-install so agents/templates restore"
|
|
6400
|
+
echo "into the install dir and are read at runtime (default is cwd,"
|
|
6401
|
+
echo "which suits a repo-clone where loki runs from the clone root)."
|
|
6402
|
+
echo ""
|
|
6403
|
+
echo "Examples:"
|
|
6404
|
+
echo " loki assets export team-assets.tgz"
|
|
6405
|
+
echo " loki assets export team-assets.tgz --wiki"
|
|
6406
|
+
echo " loki assets import team-assets.tgz # merge learnings, overwrite rest"
|
|
6407
|
+
echo " loki assets import team-assets.tgz --no-merge"
|
|
6408
|
+
echo " loki assets inspect team-assets.tgz # show manifest only"
|
|
6409
|
+
return 0
|
|
6410
|
+
;;
|
|
6411
|
+
export)
|
|
6412
|
+
local out_path="${1:-}"
|
|
6413
|
+
if [ -z "$out_path" ]; then
|
|
6414
|
+
echo -e "${RED}Usage: loki assets export <bundle.tgz> [--wiki] [--categories a,b,c]${NC}" >&2
|
|
6415
|
+
return 1
|
|
6416
|
+
fi
|
|
6417
|
+
shift 2>/dev/null || true
|
|
6418
|
+
if ! _export_check_overwrite "$out_path"; then
|
|
6419
|
+
return 1
|
|
6420
|
+
fi
|
|
6421
|
+
echo "Bundling team assets (redacting secrets and paths)..."
|
|
6422
|
+
if LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$export_repo_root" \
|
|
6423
|
+
LOKI_ASSETS_PROJECT="$project_dir" \
|
|
6424
|
+
python3 "$helper" export "$out_path" "$@"; then
|
|
6425
|
+
echo -e "${GREEN}Bundle written: $out_path${NC}"
|
|
6426
|
+
else
|
|
6427
|
+
echo -e "${RED}Export failed${NC}" >&2
|
|
6428
|
+
return 1
|
|
6429
|
+
fi
|
|
6430
|
+
;;
|
|
6431
|
+
import)
|
|
6432
|
+
local bundle_path="${1:-}"
|
|
6433
|
+
if [ -z "$bundle_path" ]; then
|
|
6434
|
+
echo -e "${RED}Usage: loki assets import <bundle.tgz> [--no-merge] [--into-install]${NC}" >&2
|
|
6435
|
+
return 1
|
|
6436
|
+
fi
|
|
6437
|
+
shift 2>/dev/null || true
|
|
6438
|
+
if [ ! -f "$bundle_path" ]; then
|
|
6439
|
+
echo -e "${RED}Error: bundle not found: $bundle_path${NC}" >&2
|
|
6440
|
+
return 1
|
|
6441
|
+
fi
|
|
6442
|
+
# --into-install: restore agents/templates into the loki install
|
|
6443
|
+
# ($SKILL_DIR) so a global-install user has them read at runtime,
|
|
6444
|
+
# instead of into cwd. Strip the flag before passing the rest to
|
|
6445
|
+
# the helper (the helper does not understand it). The install dir
|
|
6446
|
+
# is the same source export reads from, so this is the symmetric
|
|
6447
|
+
# round-trip for a global install. Off by default (cwd is safe).
|
|
6448
|
+
local _import_args=()
|
|
6449
|
+
local arg
|
|
6450
|
+
for arg in "$@"; do
|
|
6451
|
+
if [ "$arg" = "--into-install" ]; then
|
|
6452
|
+
import_repo_root="$export_repo_root"
|
|
6453
|
+
else
|
|
6454
|
+
_import_args+=("$arg")
|
|
6455
|
+
fi
|
|
6456
|
+
done
|
|
6457
|
+
if [ "$import_repo_root" = "$export_repo_root" ]; then
|
|
6458
|
+
echo "Importing team assets (agents/templates -> install dir: $import_repo_root)..."
|
|
6459
|
+
else
|
|
6460
|
+
echo "Importing team assets into this project/clone (cwd)..."
|
|
6461
|
+
fi
|
|
6462
|
+
# Bash 3.2 (macOS) errors on "${arr[@]}" when the array is empty
|
|
6463
|
+
# and set -u is active; guard with the count.
|
|
6464
|
+
if [ "${#_import_args[@]}" -gt 0 ]; then
|
|
6465
|
+
LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$import_repo_root" \
|
|
6466
|
+
LOKI_ASSETS_PROJECT="$project_dir" \
|
|
6467
|
+
python3 "$helper" import "$bundle_path" "${_import_args[@]}"
|
|
6468
|
+
else
|
|
6469
|
+
LOKI_ASSETS_HOME="$HOME" LOKI_ASSETS_REPO="$import_repo_root" \
|
|
6470
|
+
LOKI_ASSETS_PROJECT="$project_dir" \
|
|
6471
|
+
python3 "$helper" import "$bundle_path"
|
|
6472
|
+
fi
|
|
6473
|
+
if [ $? -eq 0 ]; then
|
|
6474
|
+
echo -e "${GREEN}Import complete${NC}"
|
|
6475
|
+
else
|
|
6476
|
+
echo -e "${RED}Import failed${NC}" >&2
|
|
6477
|
+
return 1
|
|
6478
|
+
fi
|
|
6479
|
+
;;
|
|
6480
|
+
inspect)
|
|
6481
|
+
local bundle_path="${1:-}"
|
|
6482
|
+
if [ -z "$bundle_path" ]; then
|
|
6483
|
+
echo -e "${RED}Usage: loki assets inspect <bundle.tgz>${NC}" >&2
|
|
6484
|
+
return 1
|
|
6485
|
+
fi
|
|
6486
|
+
if [ ! -f "$bundle_path" ]; then
|
|
6487
|
+
echo -e "${RED}Error: bundle not found: $bundle_path${NC}" >&2
|
|
6488
|
+
return 1
|
|
6489
|
+
fi
|
|
6490
|
+
python3 "$helper" inspect "$bundle_path"
|
|
6491
|
+
;;
|
|
6492
|
+
*)
|
|
6493
|
+
echo -e "${RED}Unknown subcommand: $subcommand${NC}" >&2
|
|
6494
|
+
echo "Run 'loki assets --help' for usage."
|
|
6495
|
+
return 1
|
|
6496
|
+
;;
|
|
6497
|
+
esac
|
|
6498
|
+
}
|
|
6499
|
+
|
|
6301
6500
|
# Guard: check if output file exists and warn before overwriting
|
|
6302
6501
|
_export_check_overwrite() {
|
|
6303
6502
|
local output="$1"
|
|
@@ -9633,6 +9832,22 @@ cmd_init() {
|
|
|
9633
9832
|
echo -e "Selected: ${CYAN}$template_name${NC} ($(_get_template_label "$template_name"))"
|
|
9634
9833
|
fi
|
|
9635
9834
|
|
|
9835
|
+
# R10: a hub-installed template (.loki/templates/<name>.md) is also valid.
|
|
9836
|
+
# Resolve it via agents/hub_install.py (validates the name as a safe id).
|
|
9837
|
+
local _installed_tpl=""
|
|
9838
|
+
local _hub_py_init="$SKILL_DIR/agents/hub_install.py"
|
|
9839
|
+
[ -f "$_hub_py_init" ] || _hub_py_init="agents/hub_install.py"
|
|
9840
|
+
if [[ -f "$_hub_py_init" ]]; then
|
|
9841
|
+
_installed_tpl=$(TPL_NAME="$template_name" HUB_PY="$_hub_py_init" python3 -c "
|
|
9842
|
+
import os
|
|
9843
|
+
import importlib.util as ilu
|
|
9844
|
+
sp = ilu.spec_from_file_location('loki_hub_install', os.environ.get('HUB_PY'))
|
|
9845
|
+
m = ilu.module_from_spec(sp); sp.loader.exec_module(m)
|
|
9846
|
+
p = m.installed_template_path(os.environ['TPL_NAME'])
|
|
9847
|
+
print(p or '')
|
|
9848
|
+
" 2>/dev/null)
|
|
9849
|
+
fi
|
|
9850
|
+
|
|
9636
9851
|
# Validate template_name against known list before filesystem lookup
|
|
9637
9852
|
local _tpl_valid=false
|
|
9638
9853
|
for _tpl_check in "${TEMPLATE_NAMES[@]}"; do
|
|
@@ -9641,6 +9856,9 @@ cmd_init() {
|
|
|
9641
9856
|
break
|
|
9642
9857
|
fi
|
|
9643
9858
|
done
|
|
9859
|
+
if [[ -n "$_installed_tpl" ]]; then
|
|
9860
|
+
_tpl_valid=true
|
|
9861
|
+
fi
|
|
9644
9862
|
if ! $_tpl_valid; then
|
|
9645
9863
|
echo -e "${RED}Unknown template: $template_name${NC}"
|
|
9646
9864
|
echo ""
|
|
@@ -9649,18 +9867,23 @@ cmd_init() {
|
|
|
9649
9867
|
echo " $tname"
|
|
9650
9868
|
done
|
|
9651
9869
|
echo ""
|
|
9652
|
-
echo "Run 'loki init --list' for details."
|
|
9870
|
+
echo "Run 'loki init --list' for built-in details, 'loki template list' for installed."
|
|
9653
9871
|
exit 1
|
|
9654
9872
|
fi
|
|
9655
9873
|
|
|
9656
9874
|
# Resolve template file
|
|
9657
9875
|
local template_file=""
|
|
9658
|
-
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
|
|
9663
|
-
|
|
9876
|
+
if [[ -n "$_installed_tpl" && -f "$_installed_tpl" ]]; then
|
|
9877
|
+
template_file="$_installed_tpl"
|
|
9878
|
+
fi
|
|
9879
|
+
if [[ -z "$template_file" ]]; then
|
|
9880
|
+
for dir in "$SKILL_DIR/templates" "$SKILL_DIR/examples"; do
|
|
9881
|
+
if [[ -f "$dir/${template_name}.md" ]]; then
|
|
9882
|
+
template_file="$dir/${template_name}.md"
|
|
9883
|
+
break
|
|
9884
|
+
fi
|
|
9885
|
+
done
|
|
9886
|
+
fi
|
|
9664
9887
|
|
|
9665
9888
|
if [[ -z "$template_file" ]]; then
|
|
9666
9889
|
echo -e "${RED}Unknown template: $template_name${NC}"
|
|
@@ -13094,6 +13317,9 @@ main() {
|
|
|
13094
13317
|
export)
|
|
13095
13318
|
cmd_export "$@"
|
|
13096
13319
|
;;
|
|
13320
|
+
assets)
|
|
13321
|
+
cmd_assets "$@"
|
|
13322
|
+
;;
|
|
13097
13323
|
config)
|
|
13098
13324
|
cmd_config "$@"
|
|
13099
13325
|
;;
|
|
@@ -13173,6 +13399,9 @@ main() {
|
|
|
13173
13399
|
agent)
|
|
13174
13400
|
cmd_agent "$@"
|
|
13175
13401
|
;;
|
|
13402
|
+
template)
|
|
13403
|
+
cmd_template "$@"
|
|
13404
|
+
;;
|
|
13176
13405
|
state)
|
|
13177
13406
|
cmd_state "$@"
|
|
13178
13407
|
;;
|
|
@@ -19562,6 +19791,104 @@ cmd_completions() {
|
|
|
19562
19791
|
esac
|
|
19563
19792
|
}
|
|
19564
19793
|
|
|
19794
|
+
cmd_template() {
|
|
19795
|
+
local subcommand="${1:-list}"
|
|
19796
|
+
shift 2>/dev/null || true
|
|
19797
|
+
|
|
19798
|
+
local script_dir
|
|
19799
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19800
|
+
local hub_py="${script_dir}/../agents/hub_install.py"
|
|
19801
|
+
[ -f "$hub_py" ] || hub_py="agents/hub_install.py"
|
|
19802
|
+
|
|
19803
|
+
case "$subcommand" in
|
|
19804
|
+
install)
|
|
19805
|
+
local source="${1:-}"
|
|
19806
|
+
if [ -z "$source" ]; then
|
|
19807
|
+
echo -e "${RED}Usage: loki template install <source>${NC}"
|
|
19808
|
+
echo " source: local path, git repo URL, or raw manifest URL"
|
|
19809
|
+
return 1
|
|
19810
|
+
fi
|
|
19811
|
+
if [ ! -f "$hub_py" ]; then
|
|
19812
|
+
echo -e "${RED}Error: agents/hub_install.py not found${NC}"
|
|
19813
|
+
return 1
|
|
19814
|
+
fi
|
|
19815
|
+
local result
|
|
19816
|
+
if result=$(python3 "$hub_py" install-template "$source" 2>&1); then
|
|
19817
|
+
TPL_RESULT="$result" python3 - << 'PYEOF'
|
|
19818
|
+
import json, os
|
|
19819
|
+
r = json.loads(os.environ["TPL_RESULT"])
|
|
19820
|
+
print(f"Installed template: {r['name']} ({r.get('label','')})")
|
|
19821
|
+
desc = r.get("description") or ""
|
|
19822
|
+
if desc:
|
|
19823
|
+
print(f" {desc}")
|
|
19824
|
+
print(f" body: {r.get('path','')}")
|
|
19825
|
+
print(f" source: {r.get('source','')}")
|
|
19826
|
+
ig = r.get("_ignored_executable_fields") or []
|
|
19827
|
+
if ig:
|
|
19828
|
+
print(f" NOTE: ignored executable-looking fields (never run): {', '.join(ig)}")
|
|
19829
|
+
print(f"Use with: loki init --template {r['name']}")
|
|
19830
|
+
PYEOF
|
|
19831
|
+
else
|
|
19832
|
+
echo -e "${RED}Install failed:${NC} $result"
|
|
19833
|
+
return 1
|
|
19834
|
+
fi
|
|
19835
|
+
;;
|
|
19836
|
+
|
|
19837
|
+
list|installed)
|
|
19838
|
+
echo -e "${BOLD}Hub-installed Templates${NC}"
|
|
19839
|
+
echo ""
|
|
19840
|
+
if [ ! -f "$hub_py" ]; then
|
|
19841
|
+
echo " (none)"
|
|
19842
|
+
else
|
|
19843
|
+
local _tpls_json
|
|
19844
|
+
_tpls_json="$(python3 "$hub_py" list-templates 2>/dev/null)"
|
|
19845
|
+
HUB_ITEMS="$_tpls_json" python3 - << 'PYEOF'
|
|
19846
|
+
import json, os
|
|
19847
|
+
try:
|
|
19848
|
+
items = json.loads(os.environ.get("HUB_ITEMS", "") or "[]")
|
|
19849
|
+
except Exception:
|
|
19850
|
+
items = []
|
|
19851
|
+
if not items:
|
|
19852
|
+
print(" (none)")
|
|
19853
|
+
else:
|
|
19854
|
+
for t in items:
|
|
19855
|
+
print(f" {t.get('name',''):24s} {t.get('label','')}")
|
|
19856
|
+
print(f"\n Total: {len(items)} installed template(s)")
|
|
19857
|
+
PYEOF
|
|
19858
|
+
fi
|
|
19859
|
+
;;
|
|
19860
|
+
|
|
19861
|
+
--help|-h|help)
|
|
19862
|
+
echo -e "${BOLD}loki template${NC} - PRD template marketplace (R10)"
|
|
19863
|
+
echo ""
|
|
19864
|
+
echo "Usage: loki template <command> [args]"
|
|
19865
|
+
echo ""
|
|
19866
|
+
echo "Commands:"
|
|
19867
|
+
echo " install <source> Install a community template (local/git/url manifest)"
|
|
19868
|
+
echo " list List templates installed from a hub source"
|
|
19869
|
+
echo " help Show this help"
|
|
19870
|
+
echo ""
|
|
19871
|
+
echo "Examples:"
|
|
19872
|
+
echo " loki template install ./my-template/manifest.json"
|
|
19873
|
+
echo " loki template install https://github.com/owner/loki-template-rust.git"
|
|
19874
|
+
echo " loki template list"
|
|
19875
|
+
echo ""
|
|
19876
|
+
echo "Installed templates are usable via 'loki init --template <name>'."
|
|
19877
|
+
echo "Built-in templates ship in templates/ (see 'loki init --list')."
|
|
19878
|
+
echo ""
|
|
19879
|
+
echo "Note: install is install-from-source (git/local/url). A hosted"
|
|
19880
|
+
echo "central hub registry is future work. Manifests are validated as"
|
|
19881
|
+
echo "DATA only; no code from a manifest is ever executed."
|
|
19882
|
+
echo ""
|
|
19883
|
+
;;
|
|
19884
|
+
*)
|
|
19885
|
+
echo -e "${RED}Unknown template command: $subcommand${NC}"
|
|
19886
|
+
echo "Run 'loki template help' for usage."
|
|
19887
|
+
return 1
|
|
19888
|
+
;;
|
|
19889
|
+
esac
|
|
19890
|
+
}
|
|
19891
|
+
|
|
19565
19892
|
# Agent type dispatch (v6.7.0)
|
|
19566
19893
|
cmd_agent() {
|
|
19567
19894
|
local subcommand="${1:-list}"
|
|
@@ -19592,6 +19919,20 @@ import json, sys, os
|
|
|
19592
19919
|
with open(os.environ["TYPES_FILE"]) as f:
|
|
19593
19920
|
agents = json.load(f)
|
|
19594
19921
|
|
|
19922
|
+
# R10: union built-in agents with hub-installed agents (.loki/agents/installed.json).
|
|
19923
|
+
try:
|
|
19924
|
+
import importlib.util as _ilu
|
|
19925
|
+
_hub_path = os.path.join(os.path.dirname(os.path.abspath(os.environ["TYPES_FILE"])), "hub_install.py")
|
|
19926
|
+
_spec = _ilu.spec_from_file_location("loki_hub_install", _hub_path)
|
|
19927
|
+
_hub = _ilu.module_from_spec(_spec)
|
|
19928
|
+
_spec.loader.exec_module(_hub)
|
|
19929
|
+
_seen = {a.get("type") for a in agents}
|
|
19930
|
+
for _inst in _hub.installed_agent_list():
|
|
19931
|
+
if _inst.get("type") not in _seen:
|
|
19932
|
+
agents.append(_inst)
|
|
19933
|
+
except Exception:
|
|
19934
|
+
pass
|
|
19935
|
+
|
|
19595
19936
|
filter_swarm = os.environ.get("FILTER_SWARM", "")
|
|
19596
19937
|
if filter_swarm and filter_swarm.startswith("--swarm"):
|
|
19597
19938
|
# Handle --swarm=X or --swarm X
|
|
@@ -19634,6 +19975,20 @@ import json, sys, os
|
|
|
19634
19975
|
with open(os.environ["TYPES_FILE"]) as f:
|
|
19635
19976
|
agents = json.load(f)
|
|
19636
19977
|
|
|
19978
|
+
# R10: include hub-installed agents in the lookup.
|
|
19979
|
+
try:
|
|
19980
|
+
import importlib.util as _ilu
|
|
19981
|
+
_hub_path = os.path.join(os.path.dirname(os.path.abspath(os.environ["TYPES_FILE"])), "hub_install.py")
|
|
19982
|
+
_spec = _ilu.spec_from_file_location("loki_hub_install", _hub_path)
|
|
19983
|
+
_hub = _ilu.module_from_spec(_spec)
|
|
19984
|
+
_spec.loader.exec_module(_hub)
|
|
19985
|
+
_seen = {a.get("type") for a in agents}
|
|
19986
|
+
for _inst in _hub.installed_agent_list():
|
|
19987
|
+
if _inst.get("type") not in _seen:
|
|
19988
|
+
agents.append(_inst)
|
|
19989
|
+
except Exception:
|
|
19990
|
+
pass
|
|
19991
|
+
|
|
19637
19992
|
target = os.environ["AGENT_TYPE"]
|
|
19638
19993
|
found = None
|
|
19639
19994
|
for a in agents:
|
|
@@ -19671,6 +20026,17 @@ PYEOF
|
|
|
19671
20026
|
import json, os
|
|
19672
20027
|
with open(os.environ['TYPES_FILE']) as f:
|
|
19673
20028
|
agents = json.load(f)
|
|
20029
|
+
try:
|
|
20030
|
+
import importlib.util as _ilu
|
|
20031
|
+
_hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
|
|
20032
|
+
_sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
|
|
20033
|
+
_hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
|
|
20034
|
+
_seen = {a.get('type') for a in agents}
|
|
20035
|
+
for _i in _hub.installed_agent_list():
|
|
20036
|
+
if _i.get('type') not in _seen:
|
|
20037
|
+
agents.append(_i)
|
|
20038
|
+
except Exception:
|
|
20039
|
+
pass
|
|
19674
20040
|
for a in agents:
|
|
19675
20041
|
if a['type'] == os.environ['AGENT_TYPE']:
|
|
19676
20042
|
print(a['persona'])
|
|
@@ -19742,6 +20108,17 @@ USER TASK: ${prompt}"
|
|
|
19742
20108
|
import json, os
|
|
19743
20109
|
with open(os.environ['TYPES_FILE']) as f:
|
|
19744
20110
|
agents = json.load(f)
|
|
20111
|
+
try:
|
|
20112
|
+
import importlib.util as _ilu
|
|
20113
|
+
_hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
|
|
20114
|
+
_sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
|
|
20115
|
+
_hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
|
|
20116
|
+
_seen = {a.get('type') for a in agents}
|
|
20117
|
+
for _i in _hub.installed_agent_list():
|
|
20118
|
+
if _i.get('type') not in _seen:
|
|
20119
|
+
agents.append(_i)
|
|
20120
|
+
except Exception:
|
|
20121
|
+
pass
|
|
19745
20122
|
for a in agents:
|
|
19746
20123
|
if a['type'] == os.environ['AGENT_TYPE']:
|
|
19747
20124
|
print(a['persona'])
|
|
@@ -19785,6 +20162,17 @@ for a in agents:
|
|
|
19785
20162
|
import json, os
|
|
19786
20163
|
with open(os.environ['TYPES_FILE']) as f:
|
|
19787
20164
|
agents = json.load(f)
|
|
20165
|
+
try:
|
|
20166
|
+
import importlib.util as _ilu
|
|
20167
|
+
_hp = os.path.join(os.path.dirname(os.path.abspath(os.environ['TYPES_FILE'])), 'hub_install.py')
|
|
20168
|
+
_sp = _ilu.spec_from_file_location('loki_hub_install', _hp)
|
|
20169
|
+
_hub = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_hub)
|
|
20170
|
+
_seen = {a.get('type') for a in agents}
|
|
20171
|
+
for _i in _hub.installed_agent_list():
|
|
20172
|
+
if _i.get('type') not in _seen:
|
|
20173
|
+
agents.append(_i)
|
|
20174
|
+
except Exception:
|
|
20175
|
+
pass
|
|
19788
20176
|
for a in agents:
|
|
19789
20177
|
if a['type'] == os.environ['AGENT_TYPE']:
|
|
19790
20178
|
print(a['persona'])
|
|
@@ -19822,17 +20210,81 @@ $diff"
|
|
|
19822
20210
|
esac
|
|
19823
20211
|
;;
|
|
19824
20212
|
|
|
20213
|
+
install)
|
|
20214
|
+
# R10: install a community agent from a source (local path /
|
|
20215
|
+
# git repo / raw URL containing a manifest). DATA-ONLY install --
|
|
20216
|
+
# never executes code from the manifest. See agents/hub_install.py.
|
|
20217
|
+
local source="${1:-}"
|
|
20218
|
+
if [ -z "$source" ]; then
|
|
20219
|
+
echo -e "${RED}Usage: loki agent install <source>${NC}"
|
|
20220
|
+
echo " source: local path, git repo URL, or raw manifest URL"
|
|
20221
|
+
return 1
|
|
20222
|
+
fi
|
|
20223
|
+
local hub_py="${script_dir}/../agents/hub_install.py"
|
|
20224
|
+
[ -f "$hub_py" ] || hub_py="agents/hub_install.py"
|
|
20225
|
+
if [ ! -f "$hub_py" ]; then
|
|
20226
|
+
echo -e "${RED}Error: agents/hub_install.py not found${NC}"
|
|
20227
|
+
return 1
|
|
20228
|
+
fi
|
|
20229
|
+
local result
|
|
20230
|
+
if result=$(python3 "$hub_py" install-agent "$source" 2>&1); then
|
|
20231
|
+
AGENT_RESULT="$result" python3 - << 'PYEOF'
|
|
20232
|
+
import json, os
|
|
20233
|
+
r = json.loads(os.environ["AGENT_RESULT"])
|
|
20234
|
+
print(f"Installed agent: {r['type']} ({r.get('name','')})")
|
|
20235
|
+
print(f" swarm: {r.get('swarm','')}")
|
|
20236
|
+
print(f" source: {r.get('source','')}")
|
|
20237
|
+
ig = r.get("_ignored_executable_fields") or []
|
|
20238
|
+
if ig:
|
|
20239
|
+
print(f" NOTE: ignored executable-looking fields (never run): {', '.join(ig)}")
|
|
20240
|
+
print("Stored in .loki/agents/installed.json -- visible to 'loki agent list/info/run'.")
|
|
20241
|
+
PYEOF
|
|
20242
|
+
else
|
|
20243
|
+
echo -e "${RED}Install failed:${NC} $result"
|
|
20244
|
+
return 1
|
|
20245
|
+
fi
|
|
20246
|
+
;;
|
|
20247
|
+
|
|
20248
|
+
installed)
|
|
20249
|
+
# R10: list agents installed from a hub source.
|
|
20250
|
+
local hub_py="${script_dir}/../agents/hub_install.py"
|
|
20251
|
+
[ -f "$hub_py" ] || hub_py="agents/hub_install.py"
|
|
20252
|
+
echo -e "${BOLD}Hub-installed Agents${NC}"
|
|
20253
|
+
echo ""
|
|
20254
|
+
if [ ! -f "$hub_py" ]; then
|
|
20255
|
+
echo " (none)"
|
|
20256
|
+
else
|
|
20257
|
+
local _agents_json
|
|
20258
|
+
_agents_json="$(python3 "$hub_py" list-agents 2>/dev/null)"
|
|
20259
|
+
HUB_ITEMS="$_agents_json" python3 - << 'PYEOF'
|
|
20260
|
+
import json, os
|
|
20261
|
+
try:
|
|
20262
|
+
items = json.loads(os.environ.get("HUB_ITEMS", "") or "[]")
|
|
20263
|
+
except Exception:
|
|
20264
|
+
items = []
|
|
20265
|
+
if not items:
|
|
20266
|
+
print(" (none)")
|
|
20267
|
+
else:
|
|
20268
|
+
for a in items:
|
|
20269
|
+
print(f" {a.get('type',''):24s} {a.get('name','')} [{a.get('swarm','')}]")
|
|
20270
|
+
print(f"\n Total: {len(items)} installed agent(s)")
|
|
20271
|
+
PYEOF
|
|
20272
|
+
fi
|
|
20273
|
+
;;
|
|
20274
|
+
|
|
19825
20275
|
--help|-h|help)
|
|
19826
20276
|
echo -e "${BOLD}loki agent${NC} - Agent type dispatch (v6.7.0)"
|
|
19827
20277
|
echo ""
|
|
19828
20278
|
echo "Usage: loki agent <command> [args]"
|
|
19829
20279
|
echo ""
|
|
19830
20280
|
echo "Commands:"
|
|
19831
|
-
echo " list [--swarm NAME] List all agent types (
|
|
20281
|
+
echo " list [--swarm NAME] List all agent types (built-in + installed)"
|
|
19832
20282
|
echo " info <type> Show agent type details"
|
|
19833
20283
|
echo " run <type> \"<prompt>\" Single-shot: run prompt with agent persona"
|
|
19834
20284
|
echo " start <type> <prd|prompt> Full autonomous loop with agent persona"
|
|
19835
20285
|
echo " review [type] Code review with agent persona (default: ops-security)"
|
|
20286
|
+
echo " install <source> Install a community agent (local/git/url manifest)"
|
|
20287
|
+
echo " installed List agents installed from a hub source"
|
|
19836
20288
|
echo " help Show this help"
|
|
19837
20289
|
echo ""
|
|
19838
20290
|
echo "Examples:"
|
|
@@ -19842,6 +20294,12 @@ $diff"
|
|
|
19842
20294
|
echo " loki agent run eng-frontend \"Create a responsive navbar\""
|
|
19843
20295
|
echo " loki agent start eng-backend ./api-prd.md"
|
|
19844
20296
|
echo " loki agent review ops-security"
|
|
20297
|
+
echo " loki agent install ./my-agent/manifest.json"
|
|
20298
|
+
echo " loki agent install https://github.com/owner/loki-agent-rust.git"
|
|
20299
|
+
echo ""
|
|
20300
|
+
echo "Note: 'install' is install-from-source (git/local/url). A hosted"
|
|
20301
|
+
echo "central hub registry is future work. Manifests are validated as"
|
|
20302
|
+
echo "DATA only; no code from a manifest is ever executed."
|
|
19845
20303
|
echo ""
|
|
19846
20304
|
;;
|
|
19847
20305
|
*)
|
|
@@ -25313,19 +25771,38 @@ _loki_gist_upload() {
|
|
|
25313
25771
|
local desc="$2"
|
|
25314
25772
|
local visibility="$3"
|
|
25315
25773
|
|
|
25316
|
-
|
|
25317
|
-
|
|
25318
|
-
|
|
25774
|
+
# gh prints progress ("- Creating gist...", "Created public gist...") on
|
|
25775
|
+
# STDERR and the final gist URL on STDOUT. Capture them separately so the
|
|
25776
|
+
# URL we expose and print is clean: folding stderr into stdout pollutes the
|
|
25777
|
+
# "Shared:" line and the ready-to-post hook with multi-line gh chatter.
|
|
25778
|
+
local gist_out gist_err gist_exit
|
|
25779
|
+
local errfile
|
|
25780
|
+
# Fall back to /dev/null if mktemp fails (disk full / unwritable /tmp) so the
|
|
25781
|
+
# redirect target is always a real path -- never an empty string (2>"" is a
|
|
25782
|
+
# bash error). We lose the captured stderr text in that rare case, not the
|
|
25783
|
+
# clean URL parse.
|
|
25784
|
+
errfile=$(mktemp "/tmp/loki-gist-err-XXXXXX" 2>/dev/null) || errfile=/dev/null
|
|
25785
|
+
gist_out=$(gh gist create "$file" --desc "$desc" $visibility 2>"$errfile")
|
|
25786
|
+
gist_exit=$?
|
|
25787
|
+
gist_err=$(cat "$errfile" 2>/dev/null)
|
|
25788
|
+
[ "$errfile" != "/dev/null" ] && rm -f "$errfile"
|
|
25319
25789
|
|
|
25320
25790
|
# Cleanup temp file
|
|
25321
25791
|
rm -f "$file"
|
|
25322
25792
|
|
|
25323
25793
|
if [ $gist_exit -ne 0 ]; then
|
|
25324
25794
|
echo -e "${RED}Failed to create gist${NC}"
|
|
25325
|
-
echo "$
|
|
25795
|
+
[ -n "$gist_err" ] && echo "$gist_err"
|
|
25796
|
+
[ -n "$gist_out" ] && echo "$gist_out"
|
|
25326
25797
|
exit 1
|
|
25327
25798
|
fi
|
|
25328
25799
|
|
|
25800
|
+
# The URL is the gist permalink on stdout. Extract just the URL line in
|
|
25801
|
+
# case any non-URL text slips into stdout on some gh versions.
|
|
25802
|
+
local gist_url
|
|
25803
|
+
gist_url=$(printf '%s\n' "$gist_out" | grep -Eo 'https://gist\.github\.com/[^[:space:]]+' | head -1)
|
|
25804
|
+
[ -z "$gist_url" ] && gist_url="$gist_out"
|
|
25805
|
+
|
|
25329
25806
|
# Expose the URL to callers (e.g. cmd_proof prints a ready-to-post hook
|
|
25330
25807
|
# after this returns). The shared "Shared:" line stays unchanged.
|
|
25331
25808
|
LOKI_LAST_GIST_URL="$gist_url"
|
package/dashboard/__init__.py
CHANGED