@alipay/alipay-aipay 1.1.0 → 1.2.2

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 (41) hide show
  1. package/dist/commands/install.js +1 -1
  2. package/dist/commands/install.js.map +1 -1
  3. package/dist/commands/update.js +1 -1
  4. package/dist/commands/update.js.map +1 -1
  5. package/dist/utils/alipay-cli.js +4 -4
  6. package/dist/utils/alipay-cli.js.map +1 -1
  7. package/package.json +3 -4
  8. package/skill/SKILL.md +75 -12
  9. package/skill/VERSION +1 -1
  10. package/skill/references/integration/flow.md +150 -108
  11. package/skill/references/integration/modules/alipay-sdk-reminder.md +29 -13
  12. package/skill/references/integration/modules/checklist.md +49 -19
  13. package/skill/references/integration/modules/code-examples/csharp/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  14. package/skill/references/integration/modules/code-examples/csharp/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.cs +27 -13
  15. package/skill/references/integration/modules/code-examples/java/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  16. package/skill/references/integration/modules/code-examples/java/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemoController.java +31 -12
  17. package/skill/references/integration/modules/code-examples/nodejs/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +5 -4
  18. package/skill/references/integration/modules/code-examples/nodejs/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.js +69 -37
  19. package/skill/references/integration/modules/code-examples/php/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  20. package/skill/references/integration/modules/code-examples/php/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.php +38 -17
  21. package/skill/references/integration/modules/code-examples/python/2-/347/275/221/347/253/231/346/224/257/344/273/230//347/273/237/344/270/200/346/224/266/345/215/225/344/270/213/345/215/225/345/271/266/346/224/257/344/273/230/351/241/265/351/235/242/346/216/245/345/217/243/344/273/243/347/240/201/347/244/272/344/276/213.md +3 -3
  22. package/skill/references/integration/modules/code-examples/python/4-/346/214/211/351/207/217/344/273/230/350/264/271/A2MPaymentDemo.py +31 -18
  23. package/skill/references/integration/modules/interface-guide.md +7 -3
  24. package/skill/references/integration/modules/sandbox/a2m-sandbox-test.md +76 -54
  25. package/skill/references/integration/modules/sandbox/alipay-sandbox-tool.md +3 -3
  26. package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +80 -24
  27. package/skill/references/integration/modules/scripts/local_402_sandbox_pay.py +35 -4
  28. package/skill/references/normal/alipay-cli-env.md +2 -2
  29. package/skill/references/normal/self-update.md +2 -2
  30. package/skill/references/onboarding/flow.md +193 -297
  31. package/skill/references/onboarding/modules/app-release.md +91 -35
  32. package/skill/references/onboarding/modules/authorization.md +19 -13
  33. package/skill/references/onboarding/modules/error-handling.md +19 -14
  34. package/skill/references/onboarding/modules/product-sign.md +22 -21
  35. package/skill/references/onboarding/modules/scripts/app.sh +139 -40
  36. package/skill/references/onboarding/modules/scripts/auth.sh +168 -99
  37. package/skill/references/onboarding/modules/scripts/error_handler.sh +148 -18
  38. package/skill/references/onboarding/modules/scripts/query_sign_status.sh +17 -11
  39. package/skill/references/onboarding/modules/scripts/service.sh +208 -37
  40. package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +1 -1
  41. package/skill/references/onboarding/modules/service-registration.md +72 -29
@@ -2,7 +2,7 @@
2
2
  #=============================================================================
3
3
  # 脚本名称: app.sh
4
4
  # 功能描述: 应用发布全流程 - 查询/复用 + 创建 + 公钥设置 + 提交审核
5
- # 调用位置: Step 5.3 应用发布
5
+ # 调用位置: list 用于 Step 3.1 前置资源查询;其他命令用于 Step 5.3 应用发布
6
6
  # 调用前置: 脚本通过 error_handler.sh 间接初始化 DEV_TOOL_NAME
7
7
  # list/create 可传 --application-type,未传时按 --product-type/--sales-code 或兼容环境变量推断
8
8
  # 用法:
@@ -13,8 +13,8 @@
13
13
  # app audit <appId> - 提交应用审核
14
14
  # app reuse <appId> - 复用已有应用(查询安全密钥)
15
15
  # 返回值:
16
- # list: FLOW:CREATE_NEW | FLOW:SELECT | FLOW:ERROR
17
- # create: 输出 APP_ID=xxx
16
+ # list: FLOW:CREATE_NEW | FLOW:SELECT | FLOW:PENDING_APPLICATIONS | FLOW:ERROR
17
+ # create: 成功输出 APP_ID=xxx;失败 exit 1,返回结构异常时同时输出 FLOW:ERROR
18
18
  # key: 输出 confirmPageUrl
19
19
  # audit: 输出审核结果
20
20
  # reuse: FLOW:REUSE_SUCCESS | FLOW:REUSE_NO_KEY | FLOW:ERROR
@@ -31,7 +31,6 @@ else
31
31
  fi
32
32
 
33
33
  require_command jq || exit 1
34
- require_command alipay-cli || exit 1
35
34
 
36
35
  parse_app_context_args() {
37
36
  while [[ $# -gt 0 ]]; do
@@ -261,36 +260,54 @@ extract_application_status() {
261
260
  '
262
261
  }
263
262
 
264
- extract_rsa2_key_info() {
263
+ extract_rsa2_application_key_info() {
265
264
  echo "$SECURITY_BUSINESS" | jq -c '
266
265
  (
267
- (.resultObj.alipayKeyList // []) +
266
+ (.resultObj.securityConfigList // []) +
268
267
  (.resultObj.securityKeys // []) +
269
- (.data.alipayKeyList // []) +
270
- (.data.securityKeys // [])
271
- ) | map(select(.signType == "RSA2")) | .[0] // {}
268
+ (.data.securityConfigList // []) +
269
+ (.data.securityKeys // []) +
270
+ (.resultObj.alipayKeyList // []) +
271
+ (.data.alipayKeyList // [])
272
+ ) |
273
+ map(select(
274
+ .signType == "RSA2" and
275
+ (
276
+ ((.partnerPublicKey // "") != "") or
277
+ ((.certInfoDTO | type) == "object" and ((.certInfoDTO.publicKey // "") != ""))
278
+ )
279
+ )) |
280
+ .[0] // {}
272
281
  '
273
282
  }
274
283
 
275
- extract_rsa2_security_config() {
284
+ extract_rsa2_alipay_key_info() {
276
285
  echo "$SECURITY_BUSINESS" | jq -c '
277
286
  (
278
- (.resultObj.securityConfigList // []) +
279
- (.data.securityConfigList // [])
280
- ) | map(select(.signType == "RSA2")) | .[0] // {}
287
+ (.resultObj.alipayKeyList // []) +
288
+ (.data.alipayKeyList // []) +
289
+ (.resultObj.securityKeys // []) +
290
+ (.data.securityKeys // [])
291
+ ) |
292
+ map(select(.signType == "RSA2" and ((.alipayPublicKey // "") != ""))) |
293
+ .[0] // {}
281
294
  '
282
295
  }
283
296
 
297
+ extract_alipay_public_key() {
298
+ local RSA2_KEY_INFO="$1"
299
+ echo "$RSA2_KEY_INFO" | jq -r '.alipayPublicKey // empty'
300
+ }
301
+
284
302
  is_rsa2_application_key_ready() {
285
303
  local RSA2_KEY_INFO="$1"
286
- local RSA2_SECURITY_CONFIG="$2"
287
304
 
288
305
  echo "$RSA2_KEY_INFO" | jq -e '
289
- ((.certInfoDTO.publicKey // .publicKey // .alipayPublicKey // "") != "")
290
- ' >/dev/null 2>&1 && return 0
291
-
292
- echo "$RSA2_SECURITY_CONFIG" | jq -e '
293
- ((.partnerPublicKey // "") != "") and (.invalid != true)
306
+ ((.partnerPublicKey // "") != "") or
307
+ (
308
+ (.certInfoDTO | type) == "object" and
309
+ ((.certInfoDTO.publicKey // "") != "")
310
+ )
294
311
  ' >/dev/null 2>&1
295
312
  }
296
313
 
@@ -342,10 +359,9 @@ ensure_rsa2_application_key_ready() {
342
359
  while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
343
360
  query_security_key "$APP_ID" "$PUBLIC_KEY" || return 1
344
361
 
345
- RSA2_KEY_INFO=$(extract_rsa2_key_info)
346
- RSA2_SECURITY_CONFIG=$(extract_rsa2_security_config)
362
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
347
363
 
348
- if is_rsa2_application_key_ready "$RSA2_KEY_INFO" "$RSA2_SECURITY_CONFIG"; then
364
+ if is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
349
365
  return 0
350
366
  fi
351
367
 
@@ -361,30 +377,35 @@ ensure_rsa2_application_key_ready() {
361
377
  return 1
362
378
  }
363
379
 
364
- ensure_alipay_public_key_exported() {
380
+ verify_key_and_export_alipay_public_key() {
365
381
  local APP_ID="$1"
366
382
 
367
383
  query_security_key "$APP_ID" || return 1
368
384
 
369
- RSA2_KEY_INFO=$(extract_rsa2_key_info)
370
- RSA2_SECURITY_CONFIG=$(extract_rsa2_security_config)
371
- ALIPAY_PUBLIC_KEY=$(echo "$RSA2_KEY_INFO" | jq -r '.alipayPublicKey // empty')
385
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
386
+ RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
387
+ ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
372
388
 
373
- if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO" "$RSA2_SECURITY_CONFIG"; then
389
+ if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
374
390
  echo "❌ 应用公钥尚未确认,禁止提交应用审核"
375
391
  return 1
376
392
  fi
377
393
 
378
394
  if [ -z "$ALIPAY_PUBLIC_KEY" ]; then
379
- echo "❌ 未获取到 RSA2 支付宝公钥,禁止提交应用审核"
395
+ echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥,禁止提交应用审核"
380
396
  return 1
381
397
  fi
382
398
 
383
399
  if write_alipay_public_key "$APP_ID" "$ALIPAY_PUBLIC_KEY"; then
384
400
  echo "✅ 已确认应用公钥并导出支付宝公钥"
385
401
  echo "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
402
+ echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
386
403
  echo "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
404
+ else
405
+ echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
387
406
  fi
407
+
408
+ return 0
388
409
  }
389
410
 
390
411
  # ─── app list: 查询已有应用 ──────────────────────────────────────────────────
@@ -422,7 +443,7 @@ app_list() {
422
443
  elif (.resultObj | type) == "array" then
423
444
  .resultObj
424
445
  else
425
- []
446
+ null
426
447
  end
427
448
  ' 2>/dev/null)
428
449
  if [ -z "$APP_LIST" ] || ! echo "$APP_LIST" | jq -e 'type == "array"' >/dev/null 2>&1; then
@@ -433,17 +454,32 @@ app_list() {
433
454
  APP_COUNT=$(echo "$APP_LIST" | jq 'length')
434
455
 
435
456
  if [ "$APP_COUNT" -eq 0 ]; then
436
- echo "📋 暂无 ${APP_APPLICATION_TYPE} 应用,进入创建应用流程"
457
+ echo "📋 暂无 ${APP_APPLICATION_TYPE} 应用;记录新建候选分支,等待 Step 4 统一决策"
437
458
  echo "FLOW:CREATE_NEW"
438
459
  else
439
460
  # 实际返回的状态字段值是 "ON_LINE"(带下划线)
440
461
  ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status == "ON_LINE")]')
462
+ NON_ONLINE_APPS=$(echo "$APP_LIST" | jq -r '[.[] | select(.status != "ON_LINE")]')
441
463
  ONLINE_COUNT=$(echo "$ONLINE_APPS" | jq 'length')
464
+ NON_ONLINE_COUNT=$(echo "$NON_ONLINE_APPS" | jq 'length')
442
465
  ONLINE_COUNT=${ONLINE_COUNT:-0}
466
+ NON_ONLINE_COUNT=${NON_ONLINE_COUNT:-0}
443
467
 
444
468
  if [ "$ONLINE_COUNT" -eq 0 ]; then
445
- echo "📋 暂无上线 ${APP_APPLICATION_TYPE} 应用,进入创建应用流程"
446
- echo "FLOW:CREATE_NEW"
469
+ echo "📋 当前已有以下尚未上线的 ${APP_APPLICATION_TYPE} 应用:"
470
+ echo ""
471
+ echo "| 应用ID | 应用名称 | 实际状态 |"
472
+ echo "|--------|----------|----------|"
473
+ for i in $(seq 0 $((NON_ONLINE_COUNT-1))); do
474
+ APP=$(echo "$NON_ONLINE_APPS" | jq ".[$i]")
475
+ APP_ID=$(echo "$APP" | jq -r '.appId // "未知"')
476
+ APP_NAME=$(echo "$APP" | jq -r '.appName // "未知"')
477
+ APP_STATUS=$(echo "$APP" | jq -r '.status // "未知"')
478
+ echo "| $APP_ID | $APP_NAME | $APP_STATUS |"
479
+ done
480
+ echo ""
481
+ echo "以上应用当前不可复用。请等待应用上线,或在了解现有应用状态后明确选择新建。"
482
+ echo "FLOW:PENDING_APPLICATIONS"
447
483
  else
448
484
  echo "📋 发现您已有以下上线 ${APP_APPLICATION_TYPE} 应用:"
449
485
  echo ""
@@ -457,6 +493,23 @@ app_list() {
457
493
  echo "| $((i+1)) | $APP_ID | $APP_NAME | 已上线 |"
458
494
  done
459
495
 
496
+ if [ "$NON_ONLINE_COUNT" -gt 0 ]; then
497
+ echo ""
498
+ echo "另有以下尚未上线的同类型应用,当前不可复用:"
499
+ echo ""
500
+ echo "| 应用ID | 应用名称 | 实际状态 |"
501
+ echo "|--------|----------|----------|"
502
+ for i in $(seq 0 $((NON_ONLINE_COUNT-1))); do
503
+ APP=$(echo "$NON_ONLINE_APPS" | jq ".[$i]")
504
+ APP_ID=$(echo "$APP" | jq -r '.appId // "未知"')
505
+ APP_NAME=$(echo "$APP" | jq -r '.appName // "未知"')
506
+ APP_STATUS=$(echo "$APP" | jq -r '.status // "未知"')
507
+ echo "| $APP_ID | $APP_NAME | $APP_STATUS |"
508
+ done
509
+ echo ""
510
+ echo "新建前请结合以上状态避免重复创建。"
511
+ fi
512
+
460
513
  echo ""
461
514
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
462
515
  echo " 请选择操作:"
@@ -499,6 +552,11 @@ app_create() {
499
552
 
500
553
  if [ "$SUCCESS" = "true" ]; then
501
554
  APP_ID=$(echo "$BUSINESS" | jq -r '.resultObj.appId // .data.appId // empty')
555
+ if [ -z "$APP_ID" ]; then
556
+ echo "❌ 应用创建返回成功,但未解析到 appId,禁止进入公钥设置流程"
557
+ echo "FLOW:ERROR"
558
+ exit 1
559
+ fi
502
560
  echo "✅ 应用创建成功"
503
561
  echo "APPLICATION_TYPE=$APP_APPLICATION_TYPE"
504
562
  echo "APP_ID=$APP_ID"
@@ -511,6 +569,12 @@ app_create() {
511
569
 
512
570
  # ─── app key: 设置应用公钥 ───────────────────────────────────────────────────
513
571
  app_key() {
572
+ if [ "$#" -ne 2 ] || [[ "${1:-}" == --* ]]; then
573
+ echo "❌ 参数格式错误:key 使用位置参数 <appId> <publicKey>,不支持 --app-id 等未定义选项"
574
+ echo "用法: bash app.sh key <appId> <publicKey>"
575
+ exit 1
576
+ fi
577
+
514
578
  APP_ID="$1"
515
579
  PUBLIC_KEY="$2"
516
580
 
@@ -546,7 +610,8 @@ app_key() {
546
610
  echo ""
547
611
  echo "请点击以下链接确认公钥设置:"
548
612
  echo ""
549
- echo "[点击确认公钥设置]($CONFIRM_PAGE_URL)(无法跳链时,请复制链接到网页浏览器打开)"
613
+ echo "提示:无法跳链时,请复制下方链接到网页浏览器打开。"
614
+ echo "[点击确认公钥设置]($CONFIRM_PAGE_URL)"
550
615
  echo ""
551
616
  echo "链接有效期为10分钟。"
552
617
  echo ""
@@ -564,6 +629,12 @@ app_key() {
564
629
 
565
630
  # ─── app audit: 提交应用审核 ─────────────────────────────────────────────────
566
631
  app_audit() {
632
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
633
+ echo "❌ 参数格式错误:audit 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
634
+ echo "用法: bash app.sh audit <appId>"
635
+ exit 1
636
+ fi
637
+
567
638
  APP_ID="$1"
568
639
 
569
640
  if [ -z "$APP_ID" ]; then
@@ -572,7 +643,7 @@ app_audit() {
572
643
  exit 1
573
644
  fi
574
645
 
575
- ensure_alipay_public_key_exported "$APP_ID" || exit 1
646
+ verify_key_and_export_alipay_public_key "$APP_ID" || exit 1
576
647
 
577
648
  REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
578
649
 
@@ -602,6 +673,12 @@ app_audit() {
602
673
 
603
674
  # ─── app verify-key: 确认应用公钥已设置 ─────────────────────────────────────
604
675
  app_verify_key() {
676
+ if [ "$#" -lt 1 ] || [ "$#" -gt 2 ] || [[ "${1:-}" == --* ]]; then
677
+ echo "❌ 参数格式错误:verify-key 使用位置参数 <appId> [publicKey],不支持 --app-id 等未定义选项"
678
+ echo "用法: bash app.sh verify-key <appId> [publicKey]"
679
+ exit 1
680
+ fi
681
+
605
682
  APP_ID="$1"
606
683
  PUBLIC_KEY="${2:-}"
607
684
 
@@ -623,6 +700,12 @@ app_verify_key() {
623
700
 
624
701
  # ─── app reuse: 复用已有应用(查询安全密钥) ──────────────────────────────────
625
702
  app_reuse() {
703
+ if [ "$#" -ne 1 ] || [[ "${1:-}" == --* ]]; then
704
+ echo "❌ 参数格式错误:reuse 使用位置参数 <appId>,不支持 --app-id 等未定义选项"
705
+ echo "用法: bash app.sh reuse <appId>"
706
+ exit 1
707
+ fi
708
+
626
709
  APP_ID="$1"
627
710
 
628
711
  if [ -z "$APP_ID" ]; then
@@ -644,20 +727,29 @@ app_reuse() {
644
727
  fi
645
728
 
646
729
  if query_security_key "$APP_ID"; then
647
- RSA2_KEY_INFO=$(extract_rsa2_key_info)
648
- ALIPAY_PUBLIC_KEY=$(echo "$RSA2_KEY_INFO" | jq -r '.alipayPublicKey // empty')
730
+ RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
731
+ RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
732
+ ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
649
733
 
650
- if [ -n "$ALIPAY_PUBLIC_KEY" ]; then
734
+ if ! is_rsa2_application_key_ready "$RSA2_KEY_INFO"; then
735
+ echo "⚠️ 未确认到已生效的 RSA2 应用公钥"
736
+ echo "FLOW:REUSE_NO_KEY"
737
+ exit 1
738
+ elif [ -n "$ALIPAY_PUBLIC_KEY" ]; then
651
739
  if write_alipay_public_key "$APP_ID" "$ALIPAY_PUBLIC_KEY"; then
652
740
  echo "✅ 已获取支付宝公钥"
653
741
  echo "支付宝公钥保存路径: ~/.config/${APP_ID}-alipayPublicKey.keytext"
742
+ echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=EXPORTED"
654
743
  echo "ALIPAY_PUBLIC_KEY_FILE=~/.config/${APP_ID}-alipayPublicKey.keytext"
744
+ else
745
+ echo "ALIPAY_PUBLIC_KEY_EXPORT_STATUS=MANUAL_CONFIGURATION_REQUIRED"
655
746
  fi
656
747
  echo "APP_ID=$APP_ID"
657
748
  echo "FLOW:REUSE_SUCCESS"
658
749
  else
659
- echo "⚠️ 未找到 RSA2 公钥,请先设置应用公钥"
660
- echo "FLOW:REUSE_NO_KEY"
750
+ echo "❌ 已确认应用公钥生效,但未获取到 RSA2 支付宝公钥"
751
+ echo "FLOW:ERROR"
752
+ exit 1
661
753
  fi
662
754
  else
663
755
  echo "FLOW:ERROR"
@@ -666,7 +758,14 @@ app_reuse() {
666
758
  }
667
759
 
668
760
  # ─── 入口分发 ────────────────────────────────────────────────────────────────
669
- case "${1:-}" in
761
+ APP_COMMAND="${1:-}"
762
+ case "$APP_COMMAND" in
763
+ list|create|key|verify-key|audit|reuse)
764
+ require_command alipay-cli || exit 1
765
+ ;;
766
+ esac
767
+
768
+ case "$APP_COMMAND" in
670
769
  list)
671
770
  shift
672
771
  app_list "$@"