Compare commits

..

569 Commits

Author SHA1 Message Date
clawdhub[bot]
e0ee34d354
meta: casely-qa-skill v1.5.0 2026-05-01 20:05:27 +00:00
clawdhub[bot]
26b98d3dd1
skill: casely-qa-skill v1.5.0 2026-05-01 20:05:23 +00:00
clawdhub[bot]
00a0d1945c
meta: nutrigenomics v0.3.1 2026-05-01 20:05:12 +00:00
clawdhub[bot]
ab5db5c9d4
skill: nutrigenomics v0.3.1 2026-05-01 20:05:08 +00:00
clawdhub[bot]
d60bef3cfa
meta: opensea-skill v2.2.3 2026-05-01 19:32:39 +00:00
clawdhub[bot]
752ba82c81
skill: opensea-skill v2.2.3 2026-05-01 19:32:35 +00:00
clawdhub[bot]
f548f739cf
meta: bilibili-messager v1.4.4 2026-05-01 19:32:16 +00:00
clawdhub[bot]
3584dace28
skill: bilibili-messager v1.4.4 2026-05-01 19:32:12 +00:00
clawdhub[bot]
90cfe50834
meta: skill-manager-all-in-one v4.5.2 2026-05-01 19:32:03 +00:00
clawdhub[bot]
a10c17eba6
skill: skill-manager-all-in-one v4.5.2 2026-05-01 19:31:58 +00:00
clawdhub[bot]
ef7a2204cb
meta: laiye-doc-processing v1.10.3 2026-05-01 18:32:12 +00:00
clawdhub[bot]
4c0c3cb20b
skill: laiye-doc-processing v1.10.3 2026-05-01 18:32:09 +00:00
clawdhub[bot]
8e11a03c7b
meta: jil-sander v1.0.0 2026-05-01 18:07:03 +00:00
clawdhub[bot]
636adfe3f2
skill: jil-sander v1.0.0 2026-05-01 18:07:00 +00:00
clawdhub[bot]
d5067b60c2
meta: autonomous-skill v1.0.3 2026-05-01 18:03:23 +00:00
clawdhub[bot]
ebecfea6b1
skill: autonomous-skill v1.0.3 2026-05-01 18:03:20 +00:00
clawdhub[bot]
55ff0a4aaf
meta: zoho-projects v1.0.1 2026-05-01 18:03:06 +00:00
clawdhub[bot]
1dba5a0a94
skill: zoho-projects v1.0.1 2026-05-01 18:03:02 +00:00
clawdhub[bot]
fbedb22be7
meta: reducto v1.0.1 2026-05-01 18:02:53 +00:00
clawdhub[bot]
2d5b1b6e45
skill: reducto v1.0.1 2026-05-01 18:02:50 +00:00
clawdhub[bot]
0ade3ee314
meta: manus-api v1.0.1 2026-05-01 18:02:41 +00:00
clawdhub[bot]
703567f481
skill: manus-api v1.0.1 2026-05-01 18:02:37 +00:00
clawdhub[bot]
af4e537e9c
meta: poku v1.0.13 2026-05-01 18:02:25 +00:00
clawdhub[bot]
59e561b14b
skill: poku v1.0.13 2026-05-01 18:02:21 +00:00
clawdhub[bot]
27d9fc4129
meta: public-2 v0.1.0 2026-05-01 18:02:03 +00:00
clawdhub[bot]
2dffe45c55
skill: public-2 v0.1.0 2026-05-01 18:01:59 +00:00
clawdhub[bot]
52ff1e56f7
meta: cn-todo-tracker v1.2.0 2026-05-01 17:04:13 +00:00
clawdhub[bot]
80c1c36033
skill: cn-todo-tracker v1.2.0 2026-05-01 17:04:10 +00:00
clawdhub[bot]
c7968e15c4
meta: agentcall v2.1.2 2026-05-01 17:02:16 +00:00
clawdhub[bot]
a11b5caf89
skill: agentcall v2.1.2 2026-05-01 17:02:12 +00:00
clawdhub[bot]
617951ebef
meta: miaoji-asin-clinic v1.2.0 2026-05-01 16:39:33 +00:00
clawdhub[bot]
ec50064079
skill: miaoji-asin-clinic v1.2.0 2026-05-01 16:39:29 +00:00
clawdhub[bot]
e3a181e4f8
meta: squarespace v1.0.1 2026-05-01 16:32:18 +00:00
clawdhub[bot]
2ea0091ee1
skill: squarespace v1.0.1 2026-05-01 16:32:15 +00:00
clawdhub[bot]
dcf389920f
meta: moark-image-gen v1.0.1 2026-05-01 16:31:57 +00:00
clawdhub[bot]
bf1e4bb32c
skill: moark-image-gen v1.0.1 2026-05-01 16:31:54 +00:00
clawdhub[bot]
f77f1ad276
meta: command-flow v1.0.3 2026-05-01 16:07:22 +00:00
clawdhub[bot]
657ec6fce5
skill: command-flow v1.0.3 2026-05-01 16:07:19 +00:00
clawdhub[bot]
1220386dec
meta: file-compression v1.0.2 2026-05-01 15:33:16 +00:00
clawdhub[bot]
f2669242ee
skill: file-compression v1.0.2 2026-05-01 15:33:13 +00:00
clawdhub[bot]
f6b3ffee52
meta: backup-system v1.0.0 2026-05-01 15:32:58 +00:00
clawdhub[bot]
c031535325
skill: backup-system v1.0.0 2026-05-01 15:32:54 +00:00
clawdhub[bot]
6b09e20232
meta: nuggetz-network v1.5.0 2026-05-01 15:32:46 +00:00
clawdhub[bot]
28d8097c93
skill: nuggetz-network v1.5.0 2026-05-01 15:32:43 +00:00
clawdhub[bot]
88afdbbd5b
meta: js-eyes v2.6.2 2026-05-01 15:32:28 +00:00
clawdhub[bot]
e52d02f481
skill: js-eyes v2.6.2 2026-05-01 15:32:25 +00:00
clawdhub[bot]
0b264233f9
meta: polymarket-wallet-xray v1.1.3 2026-05-01 15:32:04 +00:00
clawdhub[bot]
a700debd4c
skill: polymarket-wallet-xray v1.1.3 2026-05-01 15:32:00 +00:00
clawdhub[bot]
829c125ff7
meta: neo-py2py3-converter v1.0.0 2026-05-01 15:04:45 +00:00
clawdhub[bot]
d36538e406
skill: neo-py2py3-converter v1.0.0 2026-05-01 15:04:42 +00:00
clawdhub[bot]
c2bf12c7ee
meta: neo-py-memory-optimizer v1.0.0 2026-05-01 15:04:30 +00:00
clawdhub[bot]
874204513b
skill: neo-py-memory-optimizer v1.0.0 2026-05-01 15:04:27 +00:00
clawdhub[bot]
9206834af6
meta: neo-docker-to-k8s-manifests v1.0.0 2026-05-01 15:04:15 +00:00
clawdhub[bot]
ac9185c19c
skill: neo-docker-to-k8s-manifests v1.0.0 2026-05-01 15:04:11 +00:00
clawdhub[bot]
05c172b8b4
meta: neo-py-test-creator v1.0.0 2026-05-01 15:03:54 +00:00
clawdhub[bot]
84fb20cc44
skill: neo-py-test-creator v1.0.0 2026-05-01 15:03:51 +00:00
clawdhub[bot]
ee7c2626f6
meta: neo-tf-module-generator v1.0.1 2026-05-01 15:03:39 +00:00
clawdhub[bot]
715ed3fedd
skill: neo-tf-module-generator v1.0.1 2026-05-01 15:03:35 +00:00
clawdhub[bot]
a4ef1a9462
meta: erpclaw v3.5.0 2026-05-01 15:03:20 +00:00
clawdhub[bot]
a2b783d12c
skill: erpclaw v3.5.0 2026-05-01 15:03:17 +00:00
clawdhub[bot]
737423adca
meta: neo-python-to-go-converter v1.0.0 2026-05-01 15:02:27 +00:00
clawdhub[bot]
0f658ad876
skill: neo-python-to-go-converter v1.0.0 2026-05-01 15:02:24 +00:00
clawdhub[bot]
236f542fad
meta: neo-graphql-ts-generator v1.0.0 2026-05-01 15:02:06 +00:00
clawdhub[bot]
8e53501b18
skill: neo-graphql-ts-generator v1.0.0 2026-05-01 15:02:03 +00:00
clawdhub[bot]
4fd7458053
meta: agent-passport-system v5.8.0 2026-05-01 14:32:55 +00:00
clawdhub[bot]
6463f72531
skill: agent-passport-system v5.8.0 2026-05-01 14:32:52 +00:00
clawdhub[bot]
6c271fd237
meta: granola-api v1.0.2 2026-05-01 14:32:28 +00:00
clawdhub[bot]
8ba5c38d86
skill: granola-api v1.0.2 2026-05-01 14:32:25 +00:00
clawdhub[bot]
0847ee697d
meta: broedkrumme-kalibr v1.0.0 2026-05-01 14:32:13 +00:00
clawdhub[bot]
7f83723b9a
skill: broedkrumme-kalibr v1.0.0 2026-05-01 14:32:10 +00:00
clawdhub[bot]
f80fbda666
meta: memory-chromadb v1.0.0 2026-05-01 14:32:00 +00:00
clawdhub[bot]
2121d1a9bd
skill: memory-chromadb v1.0.0 2026-05-01 14:31:57 +00:00
clawdhub[bot]
6a8e3b424a
meta: sentry-api v1.0.1 2026-05-01 14:05:53 +00:00
clawdhub[bot]
7559d758e7
skill: sentry-api v1.0.1 2026-05-01 14:05:50 +00:00
clawdhub[bot]
888d50f43c
meta: posthog-api v1.0.1 2026-05-01 14:05:40 +00:00
clawdhub[bot]
9707dc5910
skill: posthog-api v1.0.1 2026-05-01 14:05:37 +00:00
clawdhub[bot]
6fc47f379c
meta: open-claw-cash v1.21.0 2026-05-01 14:05:29 +00:00
clawdhub[bot]
dcc7ecaa98
skill: open-claw-cash v1.21.0 2026-05-01 14:05:25 +00:00
clawdhub[bot]
c133b20237
meta: spotify-claw v2.0.1 2026-05-01 14:05:15 +00:00
clawdhub[bot]
230c5a911a
skill: spotify-claw v2.0.1 2026-05-01 14:05:12 +00:00
clawdhub[bot]
73e776370d
meta: parcel-tracking v1.0.2 2026-05-01 14:05:02 +00:00
clawdhub[bot]
72eb4bfb9c
skill: parcel-tracking v1.0.2 2026-05-01 14:04:59 +00:00
clawdhub[bot]
4abed1cd7a
delete: skills/bakyang2/kr-crypto-intelligence 2026-05-01 13:36:41 +00:00
clawdhub[bot]
ccfb029c3b
delete: skills/18072937735/smyx-basic-object-detection-analysis 2026-05-01 12:43:47 +00:00
clawdhub[bot]
e8c6ed61d5
delete: skills/ashlexham/referralcodes-referral-referrals-finder 2026-05-01 12:41:25 +00:00
clawdhub[bot]
79524d689a
meta: wallet v1.0.0 2026-05-01 12:32:00 +00:00
clawdhub[bot]
cc02371255
skill: wallet v1.0.0 2026-05-01 12:31:57 +00:00
clawdhub[bot]
dc45643207
meta: redis-cluster-analyzer v1.0.0 2026-05-01 11:46:21 +00:00
clawdhub[bot]
940797c5af
skill: redis-cluster-analyzer v1.0.0 2026-05-01 11:46:18 +00:00
clawdhub[bot]
32eaf13e5a
meta: clawcrm v1.0.8 2026-05-01 11:31:57 +00:00
clawdhub[bot]
de8e4e3137
skill: clawcrm v1.0.8 2026-05-01 11:31:54 +00:00
clawdhub[bot]
7afbb1e7bb
meta: verified-humanizer-new v1.0.0 2026-05-01 11:10:20 +00:00
clawdhub[bot]
12d24cfdc0
skill: verified-humanizer-new v1.0.0 2026-05-01 11:10:17 +00:00
clawdhub[bot]
b5fef91405
meta: here-now v1.15.3 2026-05-01 11:02:32 +00:00
clawdhub[bot]
b568329ff0
skill: here-now v1.15.3 2026-05-01 11:02:29 +00:00
clawdhub[bot]
68307d37f0
meta: lead-magnet-creator v1.0.0 2026-05-01 11:02:19 +00:00
clawdhub[bot]
77b539c354
skill: lead-magnet-creator v1.0.0 2026-05-01 11:02:16 +00:00
clawdhub[bot]
4826a52c9c
meta: german-content-writer v1.0.0 2026-05-01 11:02:08 +00:00
clawdhub[bot]
16334a4a0e
skill: german-content-writer v1.0.0 2026-05-01 11:02:05 +00:00
clawdhub[bot]
0eafc5497b
meta: checkly-cli-skills v1.0.2 2026-05-01 10:32:08 +00:00
clawdhub[bot]
522d04eac5
skill: checkly-cli-skills v1.0.2 2026-05-01 10:32:04 +00:00
clawdhub[bot]
d9b9fe97a7
meta: my-humanizer v1.0.0 2026-05-01 10:17:17 +00:00
clawdhub[bot]
441c2cc55f
skill: my-humanizer v1.0.0 2026-05-01 10:17:13 +00:00
clawdhub[bot]
6e871a90e2
meta: microsoft-teams v1.0.1 2026-05-01 10:04:25 +00:00
clawdhub[bot]
6ed8420037
skill: microsoft-teams v1.0.1 2026-05-01 10:04:22 +00:00
clawdhub[bot]
17634123f7
meta: zvukogram v1.1.6 2026-05-01 10:04:08 +00:00
clawdhub[bot]
3f361ff88d
skill: zvukogram v1.1.6 2026-05-01 10:04:05 +00:00
clawdhub[bot]
71904b4f06
meta: opentweet-x-poster v1.2.1 2026-05-01 10:02:02 +00:00
clawdhub[bot]
a898077392
skill: opentweet-x-poster v1.2.1 2026-05-01 10:01:58 +00:00
clawdhub[bot]
7b3d5bd929
meta: twitterapi-io v3.8.5 2026-05-01 09:33:03 +00:00
clawdhub[bot]
3900b47de7
skill: twitterapi-io v3.8.5 2026-05-01 09:32:59 +00:00
clawdhub[bot]
a6e149f4c1
meta: kalibr v0.2.1 2026-05-01 09:32:47 +00:00
clawdhub[bot]
e5968cd543
skill: kalibr v0.2.1 2026-05-01 09:32:44 +00:00
clawdhub[bot]
9eeb34b630
meta: fox-lights v1.0.3 2026-05-01 09:32:23 +00:00
clawdhub[bot]
e886b883d2
skill: fox-lights v1.0.3 2026-05-01 09:32:20 +00:00
clawdhub[bot]
b592c0f5c7
meta: agent-crypto-wallet-depricated v1.9.9 2026-05-01 09:32:11 +00:00
clawdhub[bot]
ef15d54571
skill: agent-crypto-wallet-depricated v1.9.9 2026-05-01 09:32:06 +00:00
clawdhub[bot]
521c1c3295
meta: pinata-erc-8004 v1.0.7 2026-05-01 09:31:56 +00:00
clawdhub[bot]
fb0a666a3b
skill: pinata-erc-8004 v1.0.7 2026-05-01 09:31:53 +00:00
clawdhub[bot]
c6a96a2a50
meta: agentvoices v1.0.0 2026-05-01 09:02:09 +00:00
clawdhub[bot]
0d06c9a2f7
skill: agentvoices v1.0.0 2026-05-01 09:02:06 +00:00
clawdhub[bot]
63fc2e6ebe
meta: continuance v1.3.0 2026-05-01 08:32:36 +00:00
clawdhub[bot]
1756a36ef2
skill: continuance v1.3.0 2026-05-01 08:32:32 +00:00
clawdhub[bot]
416e8f9945
meta: zoho-bookings v1.0.3 2026-05-01 08:32:21 +00:00
clawdhub[bot]
120973eff0
skill: zoho-bookings v1.0.3 2026-05-01 08:32:18 +00:00
clawdhub[bot]
f4fc42d049
meta: happenstance v1.0.0 2026-05-01 08:31:59 +00:00
clawdhub[bot]
0c579001d9
skill: happenstance v1.0.0 2026-05-01 08:31:56 +00:00
clawdhub[bot]
27a7d1b578
delete: skills/18072937735/smyx-fall-detection-video-analysis 2026-05-01 08:29:53 +00:00
clawdhub[bot]
2925d8652e
delete: skills/baolige2023/wechat-content-scraper-intl 2026-05-01 08:11:47 +00:00
clawdhub[bot]
7ededc39e8
meta: aport-agent-guardrail v1.1.20 2026-05-01 08:02:01 +00:00
clawdhub[bot]
770a905960
skill: aport-agent-guardrail v1.1.20 2026-05-01 08:01:57 +00:00
clawdhub[bot]
0cfe9bac5f
meta: dlazy-banana-pro v1.1.0 2026-05-01 07:47:35 +00:00
clawdhub[bot]
47c7cfe807
skill: dlazy-banana-pro v1.1.0 2026-05-01 07:47:31 +00:00
clawdhub[bot]
2678a4029b
delete: skills/alan-silverstreams/clawnexus 2026-05-01 07:32:45 +00:00
clawdhub[bot]
f47bbea001
meta: polymarket-elon-tweets v1.3.2 2026-05-01 07:32:32 +00:00
clawdhub[bot]
5033def47e
skill: polymarket-elon-tweets v1.3.2 2026-05-01 07:32:29 +00:00
clawdhub[bot]
2df72463dc
meta: agent-brain v1.0.10 2026-05-01 07:32:13 +00:00
clawdhub[bot]
9897c06dd5
skill: agent-brain v1.0.10 2026-05-01 07:32:09 +00:00
clawdhub[bot]
a12e96be34
meta: alex-test v0.1.0 2026-05-01 07:02:48 +00:00
clawdhub[bot]
f3daf586bc
skill: alex-test v0.1.0 2026-05-01 07:02:44 +00:00
clawdhub[bot]
bb73b25bee
meta: escrow-agent v1.0.2 2026-05-01 07:02:35 +00:00
clawdhub[bot]
532f1cbe67
skill: escrow-agent v1.0.2 2026-05-01 07:02:32 +00:00
clawdhub[bot]
fa1a546e43
meta: seedance-guide v1.0.5 2026-05-01 07:02:10 +00:00
clawdhub[bot]
cb154454af
skill: seedance-guide v1.0.5 2026-05-01 07:02:06 +00:00
clawdhub[bot]
c7e7e4d1e8
meta: belong-events v1.0.2 2026-05-01 07:01:58 +00:00
clawdhub[bot]
95d1a40326
skill: belong-events v1.0.2 2026-05-01 07:01:55 +00:00
clawdhub[bot]
d8977bcc0c
delete: skills/2263648274/qwen-auto-register 2026-05-01 06:51:31 +00:00
clawdhub[bot]
524906698c
delete: skills/akkualle/akkualle-seo 2026-05-01 06:36:31 +00:00
clawdhub[bot]
42fdc7f2a9
delete: skills/18072937735/smyx-staff-absence-detection-analysis 2026-05-01 06:34:33 +00:00
clawdhub[bot]
7161cc1486
delete: skills/aeoess/agent-passport-system 2026-05-01 06:15:24 +00:00
clawdhub[bot]
0f0f49d4a6
meta: open-persona v0.21.1 2026-05-01 06:02:27 +00:00
clawdhub[bot]
3801cc917d
skill: open-persona v0.21.1 2026-05-01 06:02:24 +00:00
clawdhub[bot]
79c2e789eb
meta: dropbox-business v1.0.4 2026-05-01 06:02:05 +00:00
clawdhub[bot]
932763a9c3
skill: dropbox-business v1.0.4 2026-05-01 06:02:02 +00:00
clawdhub[bot]
ffdaf27804
delete: skills/adlai88/polymarket-weather-trader 2026-05-01 05:46:05 +00:00
clawdhub[bot]
fd8404db3e
delete: skills/be1human/self-evolve 2026-05-01 05:35:48 +00:00
clawdhub[bot]
efbd521603
meta: agent-ros-bridge v0.3.4 2026-05-01 05:33:46 +00:00
clawdhub[bot]
994880755c
skill: agent-ros-bridge v0.3.4 2026-05-01 05:33:42 +00:00
clawdhub[bot]
3781e850cc
meta: amazon-checkout v2.9.5 2026-05-01 05:33:12 +00:00
clawdhub[bot]
3dc532ec85
skill: amazon-checkout v2.9.5 2026-05-01 05:33:09 +00:00
clawdhub[bot]
ef8c6ef8cc
meta: google-bigquery v1.0.1 2026-05-01 05:32:51 +00:00
clawdhub[bot]
f1db7a8ed5
skill: google-bigquery v1.0.1 2026-05-01 05:32:48 +00:00
clawdhub[bot]
b1e5b912eb
meta: firebase v1.0.1 2026-05-01 05:32:40 +00:00
clawdhub[bot]
2c4755f0b1
skill: firebase v1.0.1 2026-05-01 05:32:37 +00:00
clawdhub[bot]
1416f78c85
meta: snapchat v1.0.1 2026-05-01 05:32:28 +00:00
clawdhub[bot]
969f3717a9
skill: snapchat v1.0.1 2026-05-01 05:32:25 +00:00
clawdhub[bot]
01fa98835f
meta: google-classroom v1.0.3 2026-05-01 05:32:17 +00:00
clawdhub[bot]
a62bc5f64b
skill: google-classroom v1.0.3 2026-05-01 05:32:14 +00:00
clawdhub[bot]
da63c04e44
meta: shop v0.0.28 2026-05-01 05:32:06 +00:00
clawdhub[bot]
152c397e35
skill: shop v0.0.28 2026-05-01 05:32:03 +00:00
clawdhub[bot]
9d6afb0793
meta: sogni-agent v1.5.16 2026-05-01 05:03:37 +00:00
clawdhub[bot]
78010a0f2f
skill: sogni-agent v1.5.16 2026-05-01 05:03:33 +00:00
clawdhub[bot]
7c87ebeba2
meta: polymarket-ai-divergence v2.3.0 2026-05-01 05:03:19 +00:00
clawdhub[bot]
955db42cf7
skill: polymarket-ai-divergence v2.3.0 2026-05-01 05:03:16 +00:00
clawdhub[bot]
303bae09bd
meta: polymarket-mert-sniper v1.2.2 2026-05-01 05:03:06 +00:00
clawdhub[bot]
4f54f17ea0
skill: polymarket-mert-sniper v1.2.2 2026-05-01 05:03:02 +00:00
clawdhub[bot]
e2a438ca6b
meta: polymarket-signal-sniper v1.5.2 2026-05-01 05:02:53 +00:00
clawdhub[bot]
7514db7be1
skill: polymarket-signal-sniper v1.5.2 2026-05-01 05:02:50 +00:00
clawdhub[bot]
ea6dd01241
meta: polymarket-copytrading v1.10.2 2026-05-01 05:02:40 +00:00
clawdhub[bot]
eef22b73e3
skill: polymarket-copytrading v1.10.2 2026-05-01 05:02:37 +00:00
clawdhub[bot]
fe04d1c96b
meta: polymarket-weather-trader v1.19.2 2026-05-01 05:02:26 +00:00
clawdhub[bot]
dfbc839fb9
skill: polymarket-weather-trader v1.19.2 2026-05-01 05:02:23 +00:00
clawdhub[bot]
dea816b4c3
meta: polymarket-fast-loop v1.6.3 2026-05-01 05:02:14 +00:00
clawdhub[bot]
d865b42c8d
skill: polymarket-fast-loop v1.6.3 2026-05-01 05:02:10 +00:00
clawdhub[bot]
37bb046891
meta: stocks v4.2.0 2026-05-01 04:33:51 +00:00
clawdhub[bot]
5b8efbe7dd
skill: stocks v4.2.0 2026-05-01 04:33:48 +00:00
clawdhub[bot]
96f18b0cb2
meta: toggl-track v1.0.1 2026-05-01 04:33:39 +00:00
clawdhub[bot]
2ed16a6824
skill: toggl-track v1.0.1 2026-05-01 04:33:35 +00:00
clawdhub[bot]
d249ad9c61
meta: motion v1.0.1 2026-05-01 04:33:27 +00:00
clawdhub[bot]
84eda72af1
skill: motion v1.0.1 2026-05-01 04:33:24 +00:00
clawdhub[bot]
658f041b0f
meta: getresponse v1.0.1 2026-05-01 04:33:16 +00:00
clawdhub[bot]
040894f9bb
skill: getresponse v1.0.1 2026-05-01 04:33:13 +00:00
clawdhub[bot]
9952fbbe7c
meta: clockify v1.0.1 2026-05-01 04:33:04 +00:00
clawdhub[bot]
493ff8b5aa
skill: clockify v1.0.1 2026-05-01 04:33:01 +00:00
clawdhub[bot]
fbc13fb68d
meta: pinata-api v1.0.10 2026-05-01 04:32:49 +00:00
clawdhub[bot]
59a74a0b26
skill: pinata-api v1.0.10 2026-05-01 04:32:46 +00:00
clawdhub[bot]
140240cff0
meta: creditclaw-wallet v2.9.5 2026-05-01 04:32:37 +00:00
clawdhub[bot]
7e2345ec3c
skill: creditclaw-wallet v2.9.5 2026-05-01 04:32:33 +00:00
clawdhub[bot]
abc39eadb4
meta: shieldcortex v4.12.11 2026-05-01 04:32:10 +00:00
clawdhub[bot]
3cdb0e7fc4
skill: shieldcortex v4.12.11 2026-05-01 04:32:06 +00:00
clawdhub[bot]
df66e7ac86
delete: skills/adlai88/simmer 2026-05-01 04:11:26 +00:00
clawdhub[bot]
a01ea7dcc7
delete: skills/akahello/tianyi-cloud-game 2026-05-01 04:03:21 +00:00
clawdhub[bot]
fc35f7f493
meta: confluence-api v1.0.1 2026-05-01 04:03:10 +00:00
clawdhub[bot]
23bd85a342
skill: confluence-api v1.0.1 2026-05-01 04:03:06 +00:00
clawdhub[bot]
575be4de8e
meta: creative-toolkit v1.0.31 2026-05-01 04:02:58 +00:00
clawdhub[bot]
4679192c2b
skill: creative-toolkit v1.0.31 2026-05-01 04:02:54 +00:00
clawdhub[bot]
af382d7e68
meta: clawboard v1.0.1 2026-05-01 04:02:42 +00:00
clawdhub[bot]
cbbb2a1736
skill: clawboard v1.0.1 2026-05-01 04:02:39 +00:00
clawdhub[bot]
71e9a4de80
meta: brave-api-search v4.2.0 2026-05-01 04:02:27 +00:00
clawdhub[bot]
5d1ea44d47
skill: brave-api-search v4.2.0 2026-05-01 04:02:23 +00:00
clawdhub[bot]
9ba656c598
meta: cal-com v1.0.1 2026-05-01 04:02:02 +00:00
clawdhub[bot]
5468848449
skill: cal-com v1.0.1 2026-05-01 04:01:59 +00:00
clawdhub[bot]
3b4271ff98
meta: podio v1.0.1 2026-05-01 03:33:05 +00:00
clawdhub[bot]
53d0773710
skill: podio v1.0.1 2026-05-01 03:33:01 +00:00
clawdhub[bot]
2e0784328f
meta: netlify-api v1.0.3 2026-05-01 03:32:51 +00:00
clawdhub[bot]
7303a2a712
skill: netlify-api v1.0.3 2026-05-01 03:32:48 +00:00
clawdhub[bot]
6ff7a4c2c9
meta: pdf-co v1.0.2 2026-05-01 03:32:40 +00:00
clawdhub[bot]
b1edc03117
skill: pdf-co v1.0.2 2026-05-01 03:32:37 +00:00
clawdhub[bot]
e1d135b211
meta: clio v1.0.1 2026-05-01 03:32:29 +00:00
clawdhub[bot]
d4e41a4fc7
skill: clio v1.0.1 2026-05-01 03:32:25 +00:00
clawdhub[bot]
e4508c1a97
meta: home-assistant-clawbradge v1.0.1 2026-05-01 03:32:16 +00:00
clawdhub[bot]
e53e744f12
skill: home-assistant-clawbradge v1.0.1 2026-05-01 03:32:12 +00:00
clawdhub[bot]
315e9b9353
meta: garmin-connect-cli v1.8.0 2026-05-01 03:32:00 +00:00
clawdhub[bot]
b83cf60c4e
skill: garmin-connect-cli v1.8.0 2026-05-01 03:31:56 +00:00
clawdhub[bot]
c2d2e9627d
delete: skills/bevanding/wallet-balance 2026-05-01 03:06:45 +00:00
clawdhub[bot]
60e1128056
meta: kakao-company v1.0.0 2026-05-01 03:05:21 +00:00
clawdhub[bot]
f729713066
skill: kakao-company v1.0.0 2026-05-01 03:05:18 +00:00
clawdhub[bot]
5efa1f0906
meta: lemlist v1.0.1 2026-05-01 03:04:52 +00:00
clawdhub[bot]
3e1ce28a61
skill: lemlist v1.0.1 2026-05-01 03:04:49 +00:00
clawdhub[bot]
57748528b2
meta: companycam v1.0.1 2026-05-01 03:04:41 +00:00
clawdhub[bot]
0f4a922488
skill: companycam v1.0.1 2026-05-01 03:04:38 +00:00
clawdhub[bot]
ab22b07da1
meta: mailgun-api v1.0.1 2026-05-01 03:04:31 +00:00
clawdhub[bot]
37d57c8abe
skill: mailgun-api v1.0.1 2026-05-01 03:04:28 +00:00
clawdhub[bot]
a1b100da96
meta: elevenlabs-api v1.0.1 2026-05-01 03:04:20 +00:00
clawdhub[bot]
65eafe6b8d
skill: elevenlabs-api v1.0.1 2026-05-01 03:04:17 +00:00
clawdhub[bot]
4c45bd97a2
meta: coda-api v1.0.1 2026-05-01 03:04:09 +00:00
clawdhub[bot]
67d947c99d
skill: coda-api v1.0.1 2026-05-01 03:04:06 +00:00
clawdhub[bot]
9b84a11bac
meta: instantly v1.0.1 2026-05-01 03:03:57 +00:00
clawdhub[bot]
dbbab95b42
skill: instantly v1.0.1 2026-05-01 03:03:53 +00:00
clawdhub[bot]
a25514ed9e
meta: molt-motion v1.3.0 2026-05-01 03:03:38 +00:00
clawdhub[bot]
bc8b77c10e
skill: molt-motion v1.3.0 2026-05-01 03:03:35 +00:00
clawdhub[bot]
0fa1822e26
meta: everclaw-inference v0.10.0 2026-05-01 03:03:15 +00:00
clawdhub[bot]
d3b8c1b32c
skill: everclaw-inference v0.10.0 2026-05-01 03:03:11 +00:00
clawdhub[bot]
e0aefb38ce
delete: skills/18072937735/smyx-pet-calming-trigger-analysis 2026-05-01 02:43:44 +00:00
clawdhub[bot]
4b6878eecf
meta: harmoniis-skill v1.0.0 2026-05-01 02:32:56 +00:00
clawdhub[bot]
6a721e028d
skill: harmoniis-skill v1.0.0 2026-05-01 02:32:53 +00:00
clawdhub[bot]
5cbba473a4
meta: entity-optimizer v9.9.5 2026-05-01 02:32:38 +00:00
clawdhub[bot]
f60590b4a9
skill: entity-optimizer v9.9.5 2026-05-01 02:32:34 +00:00
clawdhub[bot]
b2da1a5e9d
meta: sendgrid v1.0.1 2026-05-01 02:32:19 +00:00
clawdhub[bot]
c2cba4c1e3
skill: sendgrid v1.0.1 2026-05-01 02:32:16 +00:00
clawdhub[bot]
1bec2169d9
meta: fireflies-api v1.0.1 2026-05-01 02:32:09 +00:00
clawdhub[bot]
4934a2d454
skill: fireflies-api v1.0.1 2026-05-01 02:32:06 +00:00
clawdhub[bot]
58c636f249
meta: beehiiv v1.0.1 2026-05-01 02:31:58 +00:00
clawdhub[bot]
a1b4b359ce
skill: beehiiv v1.0.1 2026-05-01 02:31:55 +00:00
clawdhub[bot]
01c578c1f4
meta: mission-control-dashboard v1.0.0 2026-05-01 02:02:34 +00:00
clawdhub[bot]
55ee77fc8a
skill: mission-control-dashboard v1.0.0 2026-05-01 02:02:30 +00:00
clawdhub[bot]
8fcf917f84
meta: tencentcloud-lighthouse-skill v1.0.1 2026-05-01 02:02:14 +00:00
clawdhub[bot]
d7be11a4d2
skill: tencentcloud-lighthouse-skill v1.0.1 2026-05-01 02:02:10 +00:00
clawdhub[bot]
aef02c67ed
meta: domain-authority-auditor v9.9.5 2026-05-01 01:05:29 +00:00
clawdhub[bot]
b0dab24784
skill: domain-authority-auditor v9.9.5 2026-05-01 01:05:25 +00:00
clawdhub[bot]
7da95ebbd1
meta: clawrouter v0.12.171 2026-05-01 01:05:15 +00:00
clawdhub[bot]
58eb70d6a3
skill: clawrouter v0.12.171 2026-05-01 01:05:11 +00:00
clawdhub[bot]
298671f716
meta: nano-banana-pro-image-gen v0.1.2 2026-05-01 01:02:59 +00:00
clawdhub[bot]
1dbbccd867
skill: nano-banana-pro-image-gen v0.1.2 2026-05-01 01:02:56 +00:00
clawdhub[bot]
fc77d9fc47
meta: wordpress-api v1.0.2 2026-05-01 01:02:40 +00:00
clawdhub[bot]
cb9a0c03e5
skill: wordpress-api v1.0.2 2026-05-01 01:02:37 +00:00
clawdhub[bot]
11ac5fd412
meta: clicksend v1.0.3 2026-05-01 01:02:29 +00:00
clawdhub[bot]
2433fe3f96
skill: clicksend v1.0.3 2026-05-01 01:02:25 +00:00
clawdhub[bot]
05d2310e86
meta: callrail v1.0.2 2026-05-01 01:02:18 +00:00
clawdhub[bot]
a2f6a5a7b4
skill: callrail v1.0.2 2026-05-01 01:02:14 +00:00
clawdhub[bot]
21433cffaa
meta: ai-news-digest v1.0.0 2026-05-01 01:02:02 +00:00
clawdhub[bot]
cb337bdbb1
skill: ai-news-digest v1.0.0 2026-05-01 01:01:59 +00:00
clawdhub[bot]
30262bbac2
meta: agent-analytics v4.0.27 2026-05-01 00:32:49 +00:00
clawdhub[bot]
b15897b888
skill: agent-analytics v4.0.27 2026-05-01 00:32:46 +00:00
clawdhub[bot]
9e116f47d8
meta: twilio-api v1.0.3 2026-05-01 00:32:27 +00:00
clawdhub[bot]
57958042b8
skill: twilio-api v1.0.3 2026-05-01 00:32:24 +00:00
clawdhub[bot]
2038b41377
meta: active-campaign v1.0.7 2026-05-01 00:32:17 +00:00
clawdhub[bot]
101281a007
skill: active-campaign v1.0.7 2026-05-01 00:32:13 +00:00
clawdhub[bot]
e0315e4451
meta: brevo-api v1.0.3 2026-05-01 00:32:02 +00:00
clawdhub[bot]
9c3d968f4a
skill: brevo-api v1.0.3 2026-05-01 00:31:59 +00:00
clawdhub[bot]
b8c0f58cc5
meta: tally-api v1.0.3 2026-04-30 23:33:41 +00:00
clawdhub[bot]
90250d0ce4
skill: tally-api v1.0.3 2026-04-30 23:33:37 +00:00
clawdhub[bot]
26e8aa20bf
meta: systeme v1.0.5 2026-04-30 23:33:28 +00:00
clawdhub[bot]
5e5a0a45e0
skill: systeme v1.0.5 2026-04-30 23:33:25 +00:00
clawdhub[bot]
52354e1df7
meta: quo v1.0.3 2026-04-30 23:33:18 +00:00
clawdhub[bot]
f7fc166699
skill: quo v1.0.3 2026-04-30 23:33:15 +00:00
clawdhub[bot]
4dc75c3228
meta: manychat v1.0.3 2026-04-30 23:33:08 +00:00
clawdhub[bot]
71b7c9c8aa
skill: manychat v1.0.3 2026-04-30 23:33:04 +00:00
clawdhub[bot]
93c9cd2c12
meta: mailerlite v1.0.3 2026-04-30 23:32:57 +00:00
clawdhub[bot]
a714df0175
skill: mailerlite v1.0.3 2026-04-30 23:32:54 +00:00
clawdhub[bot]
c8ac7ca669
meta: cognito-forms v1.0.3 2026-04-30 23:32:45 +00:00
clawdhub[bot]
cc9bda3785
skill: cognito-forms v1.0.3 2026-04-30 23:32:42 +00:00
clawdhub[bot]
7996b34cd1
meta: clickfunnels v1.0.3 2026-04-30 23:32:35 +00:00
clawdhub[bot]
9424ef4baf
skill: clickfunnels v1.0.3 2026-04-30 23:32:32 +00:00
clawdhub[bot]
458e0fa892
meta: vimeo v1.0.4 2026-04-30 23:32:25 +00:00
clawdhub[bot]
d1aa9882da
skill: vimeo v1.0.4 2026-04-30 23:32:22 +00:00
clawdhub[bot]
d5a56a3606
meta: signnow v1.0.3 2026-04-30 23:32:14 +00:00
clawdhub[bot]
b73f2378f3
skill: signnow v1.0.3 2026-04-30 23:32:11 +00:00
clawdhub[bot]
313b95606b
meta: neural-memory v4.53.4 2026-04-30 23:32:02 +00:00
clawdhub[bot]
c71f5edacd
skill: neural-memory v4.53.4 2026-04-30 23:31:59 +00:00
clawdhub[bot]
efb1fac63a
meta: moltlog v0.1.8 2026-04-30 22:36:24 +00:00
clawdhub[bot]
49afa418ff
skill: moltlog v0.1.8 2026-04-30 22:36:20 +00:00
clawdhub[bot]
ee1ef2018e
meta: technical-seo-checker v9.9.5 2026-04-30 22:36:11 +00:00
clawdhub[bot]
68e3200b5f
skill: technical-seo-checker v9.9.5 2026-04-30 22:36:07 +00:00
clawdhub[bot]
ff40beb3ec
meta: on-page-seo-auditor v9.9.5 2026-04-30 22:35:56 +00:00
clawdhub[bot]
de10659350
skill: on-page-seo-auditor v9.9.5 2026-04-30 22:35:53 +00:00
clawdhub[bot]
be20e645b8
meta: internal-linking-optimizer v9.9.5 2026-04-30 22:35:44 +00:00
clawdhub[bot]
198cbe01d4
skill: internal-linking-optimizer v9.9.5 2026-04-30 22:35:40 +00:00
clawdhub[bot]
ab3ee53ad4
meta: content-refresher v9.9.5 2026-04-30 22:35:32 +00:00
clawdhub[bot]
c7df1796e1
skill: content-refresher v9.9.5 2026-04-30 22:35:28 +00:00
clawdhub[bot]
d58f69e17a
meta: backlink-analyzer v9.9.5 2026-04-30 22:35:20 +00:00
clawdhub[bot]
58abb28999
skill: backlink-analyzer v9.9.5 2026-04-30 22:35:16 +00:00
clawdhub[bot]
eefc1cb4f6
meta: memory-management v9.9.5 2026-04-30 22:35:07 +00:00
clawdhub[bot]
58b7ed6b31
skill: memory-management v9.9.5 2026-04-30 22:35:04 +00:00
clawdhub[bot]
101aa3c9c6
meta: content-quality-auditor v9.9.5 2026-04-30 22:34:55 +00:00
clawdhub[bot]
70fabca56b
skill: content-quality-auditor v9.9.5 2026-04-30 22:34:51 +00:00
clawdhub[bot]
d0ace9a30c
meta: schema-markup-generator v9.9.5 2026-04-30 22:34:43 +00:00
clawdhub[bot]
abea9e2b73
skill: schema-markup-generator v9.9.5 2026-04-30 22:34:40 +00:00
clawdhub[bot]
650c19b7fb
meta: meta-tags-optimizer v9.9.5 2026-04-30 22:34:31 +00:00
clawdhub[bot]
14fddd3ceb
skill: meta-tags-optimizer v9.9.5 2026-04-30 22:34:28 +00:00
clawdhub[bot]
f647f78099
meta: geo-content-optimizer v9.9.5 2026-04-30 22:34:18 +00:00
clawdhub[bot]
41880f96e9
skill: geo-content-optimizer v9.9.5 2026-04-30 22:34:15 +00:00
clawdhub[bot]
e5a30becaf
meta: alert-manager v9.9.5 2026-04-30 22:34:05 +00:00
clawdhub[bot]
5be312f3f0
skill: alert-manager v9.9.5 2026-04-30 22:34:02 +00:00
clawdhub[bot]
a36708a9f4
meta: performance-reporter v9.9.5 2026-04-30 22:33:53 +00:00
clawdhub[bot]
882c79daa5
skill: performance-reporter v9.9.5 2026-04-30 22:33:50 +00:00
clawdhub[bot]
422bb7c03e
meta: rank-tracker v9.9.5 2026-04-30 22:33:41 +00:00
clawdhub[bot]
63cb1330af
skill: rank-tracker v9.9.5 2026-04-30 22:33:37 +00:00
clawdhub[bot]
783779fe42
meta: serp-analysis v9.9.5 2026-04-30 22:33:29 +00:00
clawdhub[bot]
61d4859bf8
skill: serp-analysis v9.9.5 2026-04-30 22:33:26 +00:00
clawdhub[bot]
986143b594
meta: content-gap-analysis v9.9.5 2026-04-30 22:33:17 +00:00
clawdhub[bot]
2732dfcaf5
skill: content-gap-analysis v9.9.5 2026-04-30 22:33:14 +00:00
clawdhub[bot]
52eb968ba9
meta: keyword-research v9.9.5 2026-04-30 22:33:05 +00:00
clawdhub[bot]
291616544f
skill: keyword-research v9.9.5 2026-04-30 22:33:01 +00:00
clawdhub[bot]
9d2d018589
meta: competitor-analysis v9.9.5 2026-04-30 22:32:52 +00:00
clawdhub[bot]
dc31204383
skill: competitor-analysis v9.9.5 2026-04-30 22:32:48 +00:00
clawdhub[bot]
6291c48a65
meta: dispatch v1.0.0 2026-04-30 22:32:34 +00:00
clawdhub[bot]
443a204624
skill: dispatch v1.0.0 2026-04-30 22:32:31 +00:00
clawdhub[bot]
9929efed90
meta: create-prediction-markets-1-0-0 v1.0.0 2026-04-30 22:32:19 +00:00
clawdhub[bot]
1d6e636a08
skill: create-prediction-markets-1-0-0 v1.0.0 2026-04-30 22:32:15 +00:00
clawdhub[bot]
7982c024de
meta: echo v1.0.1 2026-04-30 22:31:57 +00:00
clawdhub[bot]
0718738465
skill: echo v1.0.1 2026-04-30 22:31:53 +00:00
clawdhub[bot]
0dd5721921
meta: social v1.3.3 2026-04-30 22:09:13 +00:00
clawdhub[bot]
c57b178dc6
skill: social v1.3.3 2026-04-30 22:09:10 +00:00
clawdhub[bot]
434e8c491e
meta: ai-ppt-generator v1.1.5 2026-04-30 22:09:01 +00:00
clawdhub[bot]
2c98e36ebb
skill: ai-ppt-generator v1.1.5 2026-04-30 22:08:57 +00:00
clawdhub[bot]
055c6a8f15
meta: lb-drizzle-skill v0.1.0 2026-04-30 22:08:46 +00:00
clawdhub[bot]
6864636e8d
skill: lb-drizzle-skill v0.1.0 2026-04-30 22:08:43 +00:00
clawdhub[bot]
dc9c4c9fb9
delete: skills/99rebels/rebels-skill-polisher 2026-04-30 21:48:13 +00:00
clawdhub[bot]
2a8c29060e
meta: openclaw-hi-install v0.1.48 2026-04-30 21:42:31 +00:00
clawdhub[bot]
3f9b1b3774
skill: openclaw-hi-install v0.1.48 2026-04-30 21:42:27 +00:00
clawdhub[bot]
e4a8e6ad57
meta: find-skills-2 v0.1.0 2026-04-30 21:32:16 +00:00
clawdhub[bot]
c151943bf4
skill: find-skills-2 v0.1.0 2026-04-30 21:32:12 +00:00
clawdhub[bot]
fa1aea0167
meta: constant-contact v1.0.4 2026-04-30 21:32:00 +00:00
clawdhub[bot]
dda356ccd0
skill: constant-contact v1.0.4 2026-04-30 21:31:57 +00:00
clawdhub[bot]
89f56da4c1
meta: wati v1.0.2 2026-04-30 21:25:07 +00:00
clawdhub[bot]
37681a4e5f
skill: wati v1.0.2 2026-04-30 21:25:03 +00:00
clawdhub[bot]
c0927d8fa1
meta: moltbillboard v1.6.6 2026-04-30 21:07:58 +00:00
clawdhub[bot]
7a37f5b547
skill: moltbillboard v1.6.6 2026-04-30 21:07:55 +00:00
clawdhub[bot]
3b7813cf60
meta: session-cost v1.0.5 2026-04-30 21:07:42 +00:00
clawdhub[bot]
d71c654b33
skill: session-cost v1.0.5 2026-04-30 21:07:38 +00:00
clawdhub[bot]
30dc0d0914
meta: citedy-seo-agent v3.5.0 2026-04-30 21:07:26 +00:00
clawdhub[bot]
8cb80be717
skill: citedy-seo-agent v3.5.0 2026-04-30 21:07:23 +00:00
clawdhub[bot]
52abc05946
meta: creditclaw v2.9.9 2026-04-30 21:07:13 +00:00
clawdhub[bot]
3f331ed55e
skill: creditclaw v2.9.9 2026-04-30 21:07:09 +00:00
clawdhub[bot]
7db20bdf66
delete: skills/aaayersss/doomscrollr 2026-04-30 21:07:00 +00:00
clawdhub[bot]
3e61dea200
meta: todoist-api v1.0.3 2026-04-30 21:06:47 +00:00
clawdhub[bot]
2fd06b1de3
skill: todoist-api v1.0.3 2026-04-30 21:06:43 +00:00
clawdhub[bot]
e0905c9c31
meta: music-cog v1.0.10 2026-04-30 21:06:33 +00:00
clawdhub[bot]
9336be8508
skill: music-cog v1.0.10 2026-04-30 21:06:29 +00:00
clawdhub[bot]
a9f4650c96
meta: cine-cog v1.0.11 2026-04-30 21:06:20 +00:00
clawdhub[bot]
8c268261ce
skill: cine-cog v1.0.11 2026-04-30 21:06:15 +00:00
clawdhub[bot]
bb8d707d42
meta: lb-nextjs16-skill v16.1.6 2026-04-30 21:06:05 +00:00
clawdhub[bot]
62e4f82ceb
skill: lb-nextjs16-skill v16.1.6 2026-04-30 21:06:00 +00:00
clawdhub[bot]
40a1426d03
meta: moltiumv2 v2.0.2 2026-04-30 21:03:12 +00:00
clawdhub[bot]
3ba4464938
skill: moltiumv2 v2.0.2 2026-04-30 21:03:09 +00:00
clawdhub[bot]
cc1e9368fc
meta: moltiumv2-lite v2.0.2 2026-04-30 21:02:00 +00:00
clawdhub[bot]
112e1116c0
skill: moltiumv2-lite v2.0.2 2026-04-30 21:01:57 +00:00
clawdhub[bot]
67b357b9f4
meta: kimi-delegation-skill v1.0.0 2026-04-30 20:32:20 +00:00
clawdhub[bot]
e72c09f122
skill: kimi-delegation-skill v1.0.0 2026-04-30 20:32:16 +00:00
clawdhub[bot]
bd1a1bd2b0
meta: tools-marketplace v8.0.13 2026-04-30 20:02:22 +00:00
clawdhub[bot]
1160118140
skill: tools-marketplace v8.0.13 2026-04-30 20:02:18 +00:00
clawdhub[bot]
f284e55069
meta: biofirewall v0.1.0 2026-04-30 20:02:05 +00:00
clawdhub[bot]
66b516d28b
skill: biofirewall v0.1.0 2026-04-30 20:02:01 +00:00
clawdhub[bot]
24f18c6d6c
meta: openclaw-security-monitor v5.3.2 2026-04-30 19:35:47 +00:00
clawdhub[bot]
0b58887968
skill: openclaw-security-monitor v5.3.2 2026-04-30 19:35:43 +00:00
clawdhub[bot]
371d2ce1b8
meta: ga4-analytics-search-indexing v1.0.0 2026-04-30 19:34:52 +00:00
clawdhub[bot]
0a043adfc1
skill: ga4-analytics-search-indexing v1.0.0 2026-04-30 19:34:49 +00:00
clawdhub[bot]
2074bd1719
meta: ga4-multi-property v1.0.0 2026-04-30 19:34:32 +00:00
clawdhub[bot]
b11b5d8e4e
skill: ga4-multi-property v1.0.0 2026-04-30 19:34:29 +00:00
clawdhub[bot]
19f93ef131
meta: ga4-analytics-search-indexing-skill v1.0.0 2026-04-30 19:34:10 +00:00
clawdhub[bot]
2e7b42a063
skill: ga4-analytics-search-indexing-skill v1.0.0 2026-04-30 19:34:06 +00:00
clawdhub[bot]
ad33e8cf13
meta: agent-bom-compliance v0.83.4 2026-04-30 19:33:58 +00:00
clawdhub[bot]
e0f10d78c2
skill: agent-bom-compliance v0.83.4 2026-04-30 19:33:54 +00:00
clawdhub[bot]
3f9d6d162c
meta: ga4-analytics-multi-property v1.0.0 2026-04-30 19:33:36 +00:00
clawdhub[bot]
688d7af955
skill: ga4-analytics-multi-property v1.0.0 2026-04-30 19:33:32 +00:00
clawdhub[bot]
e5f4a8af12
meta: btc15-autonomous-market v1.1.1 2026-04-30 19:33:08 +00:00
clawdhub[bot]
09349317be
skill: btc15-autonomous-market v1.1.1 2026-04-30 19:33:04 +00:00
clawdhub[bot]
ea15521941
meta: ai-picture-book v1.1.2 2026-04-30 19:32:52 +00:00
clawdhub[bot]
473e3c27e7
skill: ai-picture-book v1.1.2 2026-04-30 19:32:48 +00:00
clawdhub[bot]
3614e4c611
meta: conversation-summarizer v1.0.0 2026-04-30 19:32:31 +00:00
clawdhub[bot]
a662bc3d13
skill: conversation-summarizer v1.0.0 2026-04-30 19:32:27 +00:00
clawdhub[bot]
28de5a2232
meta: chat-summary-tool v1.0.0 2026-04-30 19:32:16 +00:00
clawdhub[bot]
df72d93866
skill: chat-summary-tool v1.0.0 2026-04-30 19:32:13 +00:00
clawdhub[bot]
36dfdf7aa0
meta: conversation-summary v1.0.1 2026-04-30 19:32:02 +00:00
clawdhub[bot]
a304b63370
skill: conversation-summary v1.0.1 2026-04-30 19:31:58 +00:00
clawdhub[bot]
96a20e55a8
meta: skill-ai-assistant v1.0.0 2026-04-30 19:03:54 +00:00
clawdhub[bot]
2ac3e6eb6b
skill: skill-ai-assistant v1.0.0 2026-04-30 19:03:50 +00:00
clawdhub[bot]
d78f073fea
meta: skill-assistant v1.2.0 2026-04-30 19:03:37 +00:00
clawdhub[bot]
c769653268
skill: skill-assistant v1.2.0 2026-04-30 19:03:33 +00:00
clawdhub[bot]
bd5bb961b1
meta: skill-summary v1.0.0 2026-04-30 19:03:24 +00:00
clawdhub[bot]
5fbcdd0b1d
skill: skill-summary v1.0.0 2026-04-30 19:03:20 +00:00
clawdhub[bot]
b473f038cb
meta: openclaw-summary v1.0.0 2026-04-30 19:03:10 +00:00
clawdhub[bot]
cd420716ac
skill: openclaw-summary v1.0.0 2026-04-30 19:03:06 +00:00
clawdhub[bot]
420a091d56
meta: magic-8-ball v1.0.0 2026-04-30 19:02:48 +00:00
clawdhub[bot]
bee80629ec
skill: magic-8-ball v1.0.0 2026-04-30 19:02:45 +00:00
clawdhub[bot]
e89710381c
meta: research-skill4455 v1.0.0 2026-04-30 19:02:35 +00:00
clawdhub[bot]
2db7d50cd3
skill: research-skill4455 v1.0.0 2026-04-30 19:02:31 +00:00
clawdhub[bot]
699f7517e0
meta: torchmarket v10.7.1 2026-04-30 19:02:10 +00:00
clawdhub[bot]
4ab75e63dd
skill: torchmarket v10.7.1 2026-04-30 19:02:06 +00:00
clawdhub[bot]
ce68b10c61
meta: moltium v1.3.1 2026-04-30 18:35:38 +00:00
clawdhub[bot]
787f9e02ae
skill: moltium v1.3.1 2026-04-30 18:35:33 +00:00
clawdhub[bot]
c7a3b26af3
meta: chitin v1.4.5 2026-04-30 18:35:13 +00:00
clawdhub[bot]
e2dacde537
skill: chitin v1.4.5 2026-04-30 18:35:08 +00:00
clawdhub[bot]
154fd52d6b
meta: fast-io v1.185.0 2026-04-30 18:34:57 +00:00
clawdhub[bot]
2e5e03a68c
skill: fast-io v1.185.0 2026-04-30 18:34:53 +00:00
clawdhub[bot]
fd234762a4
meta: alephnet-node v1.4.0 2026-04-30 18:34:43 +00:00
clawdhub[bot]
e441e6460e
skill: alephnet-node v1.4.0 2026-04-30 18:34:39 +00:00
clawdhub[bot]
d8aeedecd0
delete: skills/bighit1/xiaohongshu-publish-skill 2026-04-30 18:21:12 +00:00
clawdhub[bot]
485ceeb31c
meta: openbroker v1.1.1 2026-04-30 18:03:01 +00:00
clawdhub[bot]
8adba47dc7
skill: openbroker v1.1.1 2026-04-30 18:02:57 +00:00
clawdhub[bot]
33c90607b2
meta: ai-captcha v1.0.0 2026-04-30 18:02:46 +00:00
clawdhub[bot]
3b2a3894c9
skill: ai-captcha v1.0.0 2026-04-30 18:02:42 +00:00
clawdhub[bot]
9de2c0c3b7
meta: fluxa-agent-wallet v1.4.5 2026-04-30 18:02:31 +00:00
clawdhub[bot]
07705e4dd6
skill: fluxa-agent-wallet v1.4.5 2026-04-30 18:02:28 +00:00
clawdhub[bot]
982f8bed1a
meta: firecrawl-skills v1.0.0 2026-04-30 18:01:58 +00:00
clawdhub[bot]
e8db803808
skill: firecrawl-skills v1.0.0 2026-04-30 18:01:55 +00:00
clawdhub[bot]
b016b88404
meta: huo15-xiaohongshu v3.7.0 2026-04-30 17:32:14 +00:00
clawdhub[bot]
c8eed1e6f8
skill: huo15-xiaohongshu v3.7.0 2026-04-30 17:32:10 +00:00
clawdhub[bot]
c58664d210
meta: raygun v1.0.2 2026-04-30 17:31:56 +00:00
clawdhub[bot]
fe45e812a4
skill: raygun v1.0.2 2026-04-30 17:31:51 +00:00
clawdhub[bot]
a9c7e33c7c
delete: skills/arberx/canonry 2026-04-30 17:28:05 +00:00
clawdhub[bot]
8a87571fd2
meta: ontraport-integration v1.0.2 2026-04-30 17:19:58 +00:00
clawdhub[bot]
d1fcc89de8
skill: ontraport-integration v1.0.2 2026-04-30 17:19:54 +00:00
clawdhub[bot]
55e64e28fc
delete: skills/arberx/aeo 2026-04-30 17:19:45 +00:00
clawdhub[bot]
fd5d90d37b
delete: skills/asistent-alex/nexlink 2026-04-30 17:10:34 +00:00
clawdhub[bot]
bb6585e7a5
meta: livekit-integration v1.0.2 2026-04-30 17:08:27 +00:00
clawdhub[bot]
1411b8ae89
skill: livekit-integration v1.0.2 2026-04-30 17:08:24 +00:00
clawdhub[bot]
5b2d10e2f1
delete: skills/aaroncxxx/system-selfcheck 2026-04-30 17:03:27 +00:00
clawdhub[bot]
2376f987e1
meta: google-qx4 v1.0.0 2026-04-30 17:02:04 +00:00
clawdhub[bot]
1e55ba70b7
skill: google-qx4 v1.0.0 2026-04-30 17:02:01 +00:00
clawdhub[bot]
fbfa4bc2e6
meta: implisense-api v1.0.4 2026-04-30 16:59:10 +00:00
clawdhub[bot]
aa45d37a7c
skill: implisense-api v1.0.4 2026-04-30 16:59:07 +00:00
clawdhub[bot]
1586e27bb6
delete: skills/99rebels/context-window-tracker 2026-04-30 16:49:16 +00:00
clawdhub[bot]
4280e14445
delete: skills/18072937735/smyx-outdoor-monitoring-analysis 2026-04-30 16:44:48 +00:00
clawdhub[bot]
2cac908822
meta: youtube-iu v1.0.0 2026-04-30 16:33:07 +00:00
clawdhub[bot]
b5392f4ead
skill: youtube-iu v1.0.0 2026-04-30 16:33:02 +00:00
clawdhub[bot]
59044d4cb4
meta: liminal v1.0.9 2026-04-30 16:32:48 +00:00
clawdhub[bot]
4d4e5e7515
skill: liminal v1.0.9 2026-04-30 16:32:43 +00:00
clawdhub[bot]
8c69171026
meta: proxmox-skill v1.0.2 2026-04-30 16:32:25 +00:00
clawdhub[bot]
1a7241b8ba
skill: proxmox-skill v1.0.2 2026-04-30 16:32:20 +00:00
clawdhub[bot]
b4adc90674
meta: donson-intelligent-editing v1.0.0 2026-04-30 16:32:04 +00:00
clawdhub[bot]
b0e551cb0b
skill: donson-intelligent-editing v1.0.0 2026-04-30 16:32:01 +00:00
clawdhub[bot]
ea1d2c7975
delete: skills/bennybao/hupun-rpa 2026-04-30 16:06:06 +00:00
clawdhub[bot]
e27a89cadf
meta: gitlab-cli-skills v1.13.3 2026-04-30 16:03:27 +00:00
clawdhub[bot]
dbfa83345b
skill: gitlab-cli-skills v1.13.3 2026-04-30 16:03:23 +00:00
clawdhub[bot]
0afc685a66
meta: pumpclaw v1.0.0 2026-04-30 16:02:30 +00:00
clawdhub[bot]
f3830a1ea9
skill: pumpclaw v1.0.0 2026-04-30 16:02:26 +00:00
clawdhub[bot]
b0d97245bd
meta: toon-utils v3.0.0 2026-04-30 16:02:04 +00:00
clawdhub[bot]
6326050b98
skill: toon-utils v3.0.0 2026-04-30 16:02:00 +00:00
clawdhub[bot]
e255c33e06
meta: postman-integration v1.0.4 2026-04-30 15:52:46 +00:00
clawdhub[bot]
16cce5cbcd
skill: postman-integration v1.0.4 2026-04-30 15:52:43 +00:00
clawdhub[bot]
30339e2182
delete: skills/bibaofeng/openclaw-aisa-youtube-aisa 2026-04-30 15:42:32 +00:00
clawdhub[bot]
8c877b91d6
meta: autonomous-skill-orchestrator v1.2.0 2026-04-30 15:39:07 +00:00
clawdhub[bot]
5d84e85c5e
skill: autonomous-skill-orchestrator v1.2.0 2026-04-30 15:39:03 +00:00
clawdhub[bot]
e4df4bf3cb
meta: api-gateway v1.0.85 2026-04-30 15:38:54 +00:00
clawdhub[bot]
0ec29364ee
skill: api-gateway v1.0.85 2026-04-30 15:38:50 +00:00
clawdhub[bot]
6ce23dba89
meta: youtube-full v1.5.0 2026-04-30 15:37:14 +00:00
clawdhub[bot]
c1939f2241
skill: youtube-full v1.5.0 2026-04-30 15:37:10 +00:00
clawdhub[bot]
205c533648
meta: youtube-data v1.5.0 2026-04-30 15:37:01 +00:00
clawdhub[bot]
018c219d3c
skill: youtube-data v1.5.0 2026-04-30 15:36:56 +00:00
clawdhub[bot]
9c61a1c935
meta: youtube-api v1.5.0 2026-04-30 15:36:47 +00:00
clawdhub[bot]
7bc71cbc5e
skill: youtube-api v1.5.0 2026-04-30 15:36:43 +00:00
clawdhub[bot]
0797af616d
meta: youtube-playlist v1.5.0 2026-04-30 15:36:35 +00:00
clawdhub[bot]
d0ab419685
skill: youtube-playlist v1.5.0 2026-04-30 15:36:31 +00:00
clawdhub[bot]
3bbab6872b
meta: youtube-channels v1.5.0 2026-04-30 15:36:22 +00:00
clawdhub[bot]
a6cb05ffa7
skill: youtube-channels v1.5.0 2026-04-30 15:36:18 +00:00
clawdhub[bot]
e36b1e8deb
meta: youtube-search v1.5.0 2026-04-30 15:36:10 +00:00
clawdhub[bot]
77215dfa6d
skill: youtube-search v1.5.0 2026-04-30 15:36:06 +00:00
clawdhub[bot]
9798e49843
meta: video-transcript v1.5.0 2026-04-30 15:35:57 +00:00
clawdhub[bot]
a3117754a2
skill: video-transcript v1.5.0 2026-04-30 15:35:53 +00:00
clawdhub[bot]
c3449df828
meta: subtitles v1.5.0 2026-04-30 15:35:44 +00:00
clawdhub[bot]
41705d6f4a
skill: subtitles v1.5.0 2026-04-30 15:35:40 +00:00
clawdhub[bot]
6e5d7bb54b
meta: captions v1.5.0 2026-04-30 15:35:32 +00:00
clawdhub[bot]
735849f0ca
skill: captions v1.5.0 2026-04-30 15:35:28 +00:00
clawdhub[bot]
73bda96604
meta: yt v1.5.1 2026-04-30 15:35:19 +00:00
clawdhub[bot]
5d8b397601
skill: yt v1.5.1 2026-04-30 15:35:15 +00:00
clawdhub[bot]
2fb7c88c34
meta: transcript v1.5.0 2026-04-30 15:35:05 +00:00
clawdhub[bot]
c01707fa8a
skill: transcript v1.5.0 2026-04-30 15:35:00 +00:00
clawdhub[bot]
612a725ba4
meta: transcriptapi v1.5.0 2026-04-30 15:34:51 +00:00
clawdhub[bot]
1430328e73
skill: transcriptapi v1.5.0 2026-04-30 15:34:47 +00:00
clawdhub[bot]
5309550616
meta: bottube v2.0.0 2026-04-30 15:34:27 +00:00
clawdhub[bot]
3bdda48f15
skill: bottube v2.0.0 2026-04-30 15:34:23 +00:00
clawdhub[bot]
919be2758e
meta: geodb-cities v1.0.5 2026-04-30 15:30:06 +00:00
clawdhub[bot]
fe21c10fb5
skill: geodb-cities v1.0.5 2026-04-30 15:30:02 +00:00
clawdhub[bot]
049ac97f9e
meta: gathercontent v1.0.2 2026-04-30 15:29:45 +00:00
clawdhub[bot]
2909400801
skill: gathercontent v1.0.2 2026-04-30 15:29:40 +00:00
clawdhub[bot]
ca8665cf3b
meta: firmao v1.0.4 2026-04-30 15:28:08 +00:00
clawdhub[bot]
05888543c5
skill: firmao v1.0.4 2026-04-30 15:28:03 +00:00
clawdhub[bot]
a0a1a532fa
delete: skills/airscripts/agentskill 2026-04-30 15:23:00 +00:00
clawdhub[bot]
0b1949744c
meta: keep v0.109.0 2026-04-30 15:05:59 +00:00
clawdhub[bot]
0ceb5782fb
skill: keep v0.109.0 2026-04-30 15:05:55 +00:00
clawdhub[bot]
75c059cf3c
meta: fortnite v1.0.0 2026-04-30 15:05:44 +00:00
clawdhub[bot]
7c4aa76410
skill: fortnite v1.0.0 2026-04-30 15:05:40 +00:00
clawdhub[bot]
3c479b02dd
meta: plan-executor v1.0.0 2026-04-30 15:02:51 +00:00
clawdhub[bot]
171716ab80
skill: plan-executor v1.0.0 2026-04-30 15:02:47 +00:00
clawdhub[bot]
819a1010c5
meta: simmer v1.22.2 2026-04-30 15:02:27 +00:00
clawdhub[bot]
f524e02e4b
skill: simmer v1.22.2 2026-04-30 15:02:23 +00:00
clawdhub[bot]
49193259bd
meta: autonomous-feature-planner v1.0.1 2026-04-30 15:02:14 +00:00
clawdhub[bot]
92c3e4bd0d
skill: autonomous-feature-planner v1.0.1 2026-04-30 15:02:10 +00:00
clawdhub[bot]
13937ba29a
meta: human-optimized-frontend v1.0.0 2026-04-30 15:01:59 +00:00
clawdhub[bot]
8ff4ec6480
skill: human-optimized-frontend v1.0.0 2026-04-30 15:01:55 +00:00
clawdhub[bot]
e1a1ccbdd7
meta: moltoverflow-skill v0.1.0 2026-04-30 14:32:52 +00:00
clawdhub[bot]
06c673bd0b
skill: moltoverflow-skill v0.1.0 2026-04-30 14:32:47 +00:00
clawdhub[bot]
e98a2bbfac
meta: skills-devo v1.0.0 2026-04-30 14:32:25 +00:00
clawdhub[bot]
1fb09f7267
skill: skills-devo v1.0.0 2026-04-30 14:32:21 +00:00
clawdhub[bot]
e178fd9967
meta: mybadskill v1.0.0 2026-04-30 14:32:03 +00:00
clawdhub[bot]
5a2cf94a94
skill: mybadskill v1.0.0 2026-04-30 14:31:59 +00:00
clawdhub[bot]
76b357bc5e
delete: skills/18072937735/smyx-package-detection-analysis 2026-04-30 13:41:37 +00:00
clawdhub[bot]
cde0b37575
delete: skills/almured/almured-connection 2026-04-30 13:36:40 +00:00
clawdhub[bot]
20650b57fd
meta: preview-markdown v0.1.0 2026-04-30 13:33:38 +00:00
clawdhub[bot]
bf1f9f20dc
skill: preview-markdown v0.1.0 2026-04-30 13:33:33 +00:00
clawdhub[bot]
230b85b0d2
meta: resend-skills v3.3.3 2026-04-30 13:33:13 +00:00
clawdhub[bot]
3cb73344bc
skill: resend-skills v3.3.3 2026-04-30 13:33:09 +00:00
clawdhub[bot]
c9327225ea
meta: react-email-skills v2.0.0 2026-04-30 13:32:51 +00:00
clawdhub[bot]
3c4e8a17eb
skill: react-email-skills v2.0.0 2026-04-30 13:32:47 +00:00
clawdhub[bot]
2a708d0d87
meta: email-best-practices v1.0.2 2026-04-30 13:32:36 +00:00
clawdhub[bot]
ada3bf4a67
skill: email-best-practices v1.0.2 2026-04-30 13:32:32 +00:00
clawdhub[bot]
3080bc629f
meta: byterover-test v1.2.1 2026-04-30 13:32:03 +00:00
clawdhub[bot]
03b24892ca
skill: byterover-test v1.2.1 2026-04-30 13:31:59 +00:00
clawdhub[bot]
81d6af065f
delete: skills/abstrct/structs-streaming 2026-04-30 13:10:23 +00:00
clawdhub[bot]
d8fd19e738
delete: skills/abstrct/structs-onboarding 2026-04-30 13:10:15 +00:00
clawdhub[bot]
69e24ab6b4
delete: skills/abstrct/structs-diplomacy 2026-04-30 13:10:04 +00:00
clawdhub[bot]
c39e21f93c
delete: skills/abstrct/structsd-install 2026-04-30 13:09:56 +00:00
clawdhub[bot]
6c0475b240
delete: skills/binggg/cloudbase 2026-04-30 12:30:22 +00:00
clawdhub[bot]
645b9a6c6a
delete: skills/18072937735/smyx-bird-recognition-analysis 2026-04-30 11:44:37 +00:00
clawdhub[bot]
dd1ba69f29
meta: claude-team v1.5.0 2026-04-30 11:32:37 +00:00
clawdhub[bot]
0c3f4f3511
skill: claude-team v1.5.0 2026-04-30 11:32:33 +00:00
clawdhub[bot]
70e18d13a1
meta: douyin-messager v1.1.8 2026-04-30 11:32:05 +00:00
clawdhub[bot]
81e174c995
skill: douyin-messager v1.1.8 2026-04-30 11:32:01 +00:00
clawdhub[bot]
6d51a14b91
meta: denso v1.0.0 2026-04-30 11:05:55 +00:00
clawdhub[bot]
3995e34469
skill: denso v1.0.0 2026-04-30 11:05:51 +00:00
clawdhub[bot]
aab2c60eb1
meta: okx-cex-market v1.3.2 2026-04-30 11:04:52 +00:00
clawdhub[bot]
a6a14ff279
skill: okx-cex-market v1.3.2 2026-04-30 11:04:48 +00:00
clawdhub[bot]
808d1e872e
meta: okx-cex-trade v1.3.2 2026-04-30 11:04:35 +00:00
clawdhub[bot]
46afee6bb3
skill: okx-cex-trade v1.3.2 2026-04-30 11:04:32 +00:00
clawdhub[bot]
66b014bc3b
meta: seo-content-writer v9.9.5 2026-04-30 11:04:13 +00:00
clawdhub[bot]
f288d62dbe
skill: seo-content-writer v9.9.5 2026-04-30 11:04:09 +00:00
clawdhub[bot]
814192600f
meta: eva-soul-by-openclaw v2.5.0 2026-04-30 11:03:49 +00:00
clawdhub[bot]
809de164a8
skill: eva-soul-by-openclaw v2.5.0 2026-04-30 11:03:46 +00:00
clawdhub[bot]
0499063531
meta: waimai v1.0.2 2026-04-30 11:03:16 +00:00
clawdhub[bot]
05018a219b
skill: waimai v1.0.2 2026-04-30 11:03:12 +00:00
clawdhub[bot]
38f4888305
meta: self-improving-agent v3.0.18 2026-04-30 11:03:01 +00:00
clawdhub[bot]
a10d3914c7
skill: self-improving-agent v3.0.18 2026-04-30 11:02:57 +00:00
clawdhub[bot]
c9341b64bc
meta: tencent-docs v1.0.31 2026-04-30 11:02:40 +00:00
clawdhub[bot]
6bbfe7adc1
skill: tencent-docs v1.0.31 2026-04-30 11:02:36 +00:00
clawdhub[bot]
865c84b233
meta: kiln v1.0.0 2026-04-30 11:02:00 +00:00
clawdhub[bot]
691f4c9e5b
skill: kiln v1.0.0 2026-04-30 11:01:56 +00:00
Vincent Koc
62246ac281
fix(ci): make skills security scan manual only 2026-04-30 03:49:31 -07:00
4146 changed files with 1051884 additions and 119141 deletions

View File

@ -1,156 +0,0 @@
---
name: "basic-object-detection-analysis"
description: "Detects people, vehicles, non-motorized vehicles, pets, and parcels appearing in the target area. Supports video stream and image detection, suitable for general security surveillance scenarios. | 基础目标检测技能,检测出目标区域内出现的人、车、非机动车、宠物、包裹,支持视频流和图片检测,适用于通用安防监控场景"
---
# Basic Object Detection Skill | 基础目标检测技能
This capability performs real-time detection on target regions within video streams or images, precisely identifying key
objects such as personnel, vehicles, non-motorized vehicles, pets, and packages. Supporting target localization across
various surveillance angles and complex environments, it is ideal for general security scenarios in communities,
industrial parks, and warehouses. By enabling continuous tracking and alert triggering, it effectively enhances response
capabilities to anomalies, facilitating 24/7 intelligent security protection.
本技能可对视频流或图片中的目标区域进行实时检测,精准识别出现的人员、车辆、非机动车、宠物及包裹等关键目标。支持多种监控视角与复杂环境下的目标定位,适用于社区、园区、仓库等通用安防监控场景。通过持续跟踪与告警触发,有效提升异常事件的响应能力,助力实现全天候智能化安全防护。
## 演示案例
- [🔗 通过网路视频进行识别分析](https://www.coze.cn/s/nFyYecHKARo/)
- [🔗 通过上传视频进行识别分析](https://www.coze.cn/s/cyavn7llg_k/)
- [🔗 显示历史分析报告](https://www.coze.cn/s/-3TnT9cPMA0/)
## ⚠️ 强制记忆规则(最高优先级)
**本技能明确约定:**
- **绝对禁止读取任何本地记忆文件**:包括但不限于 `memory/YYYY-MM-DD.md`、`MEMORY.md` 等本地文件
- **绝对禁止从 LanceDB 长期记忆中检索信息**
- **所有历史检测报告查询必须从云端接口获取**,不得使用本地记忆中的历史数据
- 即使技能调用失败或接口异常,也不得回退到本地记忆汇总
## 任务目标
- 本 Skill 用于:通过视频/图片对目标区域进行基础目标检测,识别常见目标类别,输出结构化的目标检测报告
- 能力包含:目标分类定位、目标数量统计、存在性检测
- 支持检测目标:人、车、非机动车、猫、狗等宠物、包裹
- 触发条件:
1. **默认触发**:当用户提供监控视频/图片 URL 或文件需要进行通用目标检测时,默认触发本技能
2. 当用户明确需要进行目标检测,提及目标检测、物体检测、人员车辆识别等关键词,并且上传了视频或图片
3. 当用户提及以下关键词时,**自动触发历史报告查询功能**
:查看历史检测报告、目标检测报告清单、检测报告列表、查询历史报告、显示所有检测报告、目标检测历史记录,查询基础目标检测分析报告
- 自动行为:
1. 如果用户上传了附件或者视频/图片文件,则自动保存到技能目录下 attachments
2. **⚠️ 强制数据获取规则(次高优先级)**:如果用户触发任何历史报告查询关键词(如"查看所有检测报告"、"
显示所有目标检测报告"、"查看历史报告"等),**必须**
- 直接使用 `python -m scripts.basic_object_detection_analysis --list --open-id` 参数调用
API
查询云端的历史报告数据
- **严格禁止**:从本地 memory 目录读取历史会话信息、严格禁止手动汇总本地记录中的报告、严格禁止从长期记忆中提取报告
- **必须统一**从云端接口获取最新完整数据,然后以 Markdown 表格格式输出结果
## 前置准备
- 依赖说明:scripts 脚本所需的依赖包及版本
```
requests>=2.28.0
```
## 操作步骤
### 🔒 open-id 获取流程控制(强制执行,防止遗漏)
**在执行基础目标检测前,必须按以下优先级顺序获取 open-id**
```
第 1 步:【最高优先级】检查技能所在目录的配置文件(优先)
路径skills/smyx_common/scripts/config.yaml相对于技能根目录
完整路径示例:${OPENCLAW_WORKSPACE}/skills/{当前技能目录}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置/api-key 为空)
第 2 步:检查 workspace 公共目录的配置文件
路径:${OPENCLAW_WORKSPACE}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置)
第 3 步:检查用户是否在消息中明确提供了 open-id
↓ (未提供)
第 4 步:❗ 必须暂停执行,明确提示用户提供用户名或手机号作为 open-id
```
**⚠️ 关键约束:**
- **禁止**自行假设,自行推导,自行生成 open-id 值(如 openclaw-control-ui、default、object123 等)
- **禁止**跳过 open-id 验证直接调用 API
- **必须**在获取到有效 open-id 后才能继续执行分析
- 如果用户拒绝提供 open-id说明用途用于保存和查询目标检测报告记录并询问是否继续
---
- 标准流程:
1. **准备媒体输入**
- 提供监控视频文件路径、网络视频 URL 或现场图片
- 确保监控画面完整覆盖监测区域,画面稳定
2. **获取 open-id强制执行**
- 按上述流程控制获取 open-id
- 如无法获取,必须提示用户提供用户名或手机号
3. **执行基础目标检测**
- 调用 `-m scripts.basic_object_detection_analysis` 处理素材(**必须在技能根目录下运行脚本**
- 参数说明:
- `--input`: 本地视频/图片文件路径(使用 multipart/form-data 方式上传)
- `--url`: 网络视频/图片 URL 地址API 服务自动下载)
- `--media-type`: 媒体类型可选值video/image默认 video
- `--confidence-threshold`: 置信度阈值,低于该分值不输出,默认 0.5
- `--open-id`: 当前用户的 open-id必填按上述流程获取
- `--list`: 显示基础目标检测历史分析报告列表清单(可以输入起始日期参数过滤数据范围)
- `--api-key`: API 访问密钥(可选)
- `--api-url`: API 服务地址(可选,使用默认值)
- `--detail`: 输出详细程度basic/standard/json默认 json
- `--output`: 结果输出文件路径(可选)
4. **查看分析结果**
- 接收结构化的基础目标检测报告
- 包含:检测基本信息、各类目标数量、目标位置统计
## 资源索引
- 必要脚本:见 [scripts/basic_object_detection_analysis.py](scripts/basic_object_detection_analysis.py)(用途:调用 API
进行基础目标检测,本地文件使用 multipart/form-data 方式上传,网络 URL 由 API 服务自动下载)
- 配置文件:见 [scripts/config.py](scripts/config.py)(用途:配置 API 地址、默认参数和媒体格式限制)
- 领域参考:见 [references/api_doc.md](references/api_doc.md)(何时读取:需要了解 API 接口详细规范和错误码时)
## 注意事项
- 仅在需要时读取参考文档,保持上下文简洁
- 支持格式:视频支持 mp4/avi/mov 格式,图片支持 jpg/png/jpeg 格式,最大 100MB
- API 密钥可选,如果通过参数传入则必须确保调用鉴权成功,否则忽略鉴权
- 分析结果仅供安防管理参考,具体处置请按单位相关规定执行
- 禁止临时生成脚本,只能用技能本身的脚本
- 传入的网络地址参数不需要下载本地默认地址都是公网地址api 服务会自动下载
- 当显示历史检测报告清单的时候,从数据 json 中提取字段 reportImageUrl 作为超链接地址,使用 Markdown 表格格式输出,包含"
报告名称"、"检测时间"、"目标总数"、"点击查看"四列,其中"报告名称"列使用`基础目标检测报告-{记录id}`形式拼接, "点击查看"列使用
`[🔗 查看报告](reportImageUrl)`
格式的超链接,用户点击即可直接跳转到对应的完整报告页面。
- 表格输出示例:
| 报告名称 | 检测时间 | 目标总数 | 点击查看 |
|----------|----------|----------|----------|
| 基础目标检测报告-20260312172200001 | 2026-03-12 17:22:00 | 5 | [🔗 查看报告](https://example.com/report?id=xxx) |
## 使用示例
```bash
# 检测本地监控视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.basic_object_detection_analysis --input /path/to/monitor.mp4 --media-type video --open-id openclaw-control-ui
# 检测现场图片调整置信度阈值以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.basic_object_detection_analysis --input /path/to/scene.jpg --media-type image --confidence-threshold 0.6 --open-id openclaw-control-ui
# 检测网络监控视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.basic_object_detection_analysis --url https://example.com/monitor.mp4 --media-type video --open-id openclaw-control-ui
# 显示历史检测报告/显示检测报告清单列表/显示历史目标检测报告(自动触发关键词:查看历史检测报告、历史报告、检测报告清单等)
python -m scripts.basic_object_detection_analysis --list --open-id openclaw-control-ui
# 输出精简报告
python -m scripts.basic_object_detection_analysis --input video.mp4 --media-type video --open-id your-open-id --detail basic
# 保存结果到文件
python -m scripts.basic_object_detection_analysis --input video.mp4 --media-type video --open-id your-open-id --output result.json
```

View File

@ -1,11 +0,0 @@
{
"owner": "18072937735",
"slug": "smyx-basic-object-detection-analysis",
"displayName": "Basic Object Detection Skill | 基础目标检测技能",
"latest": {
"version": "1.0.0",
"publishedAt": 1776142106790,
"commit": "https://github.com/openclaw/skills/commit/67260d50b68c81917e31a7a65dbe0b6ac7a7bdb3"
},
"history": []
}

View File

@ -1,21 +0,0 @@
# API 接口文档
此处用于存放宠物健康分析 API 的接口文档,待后续补充。
## 接口规范
- 基础地址:由 smyx_common 配置统一管理
- 认证方式API Key 鉴权
- 请求格式multipart/form-data 支持文件上传
- 响应格式JSON
## 主要接口
1. `/web/health-analysis/v2/start-health-analysis` - 启动健康分析任务
2. `/web/health-analysis/v2/get-health-analysis-result` - 获取分析结果
3. `/web/health-analysis/page-health-analysis-result` - 分页查询历史报告
4. `/health/order/api/getReportDetailExport?id={id}` - 导出完整报告
## 场景代码
- `OPEN_PET_HEALTH_ANALYSIS` - 开放平台宠物健康分析

View File

@ -1 +0,0 @@
# Pet Analysis scripts package

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.smyx_common.scripts.util import RequestUtil
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, *args, **argss):
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"data_as_params": True
}
# params.setdefault("scene", scene_code)
# 添加宠物类型参数
if ConstantEnum.DEFAULT_PET_TYPE:
params.setdefault("petType", ConstantEnum.DEFAULT_PET_TYPE)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"data_as_params": True})

View File

@ -1,224 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
from skills.smyx_common.scripts.util import RequestUtil
# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB
def validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > MAX_FILE_SIZE_MB:
raise ValueError(f"文件过大,最大支持 {MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
def analyze_media(input_path=None, url=None, media_type=None, confidence_threshold=None, api_url=None, api_key=None,
output_level=None):
"""调用API进行基础目标检测"""
if not input_path and not url:
raise ValueError("必须提供本地媒体路径(--input)或网络媒体URL(--url)")
# 设置参数
if media_type:
ConstantEnum.DEFAULT_MEDIA_TYPE = media_type
if confidence_threshold:
ConstantEnum.DEFAULT_CONFIDENCE_THRESHOLD = confidence_threshold
try:
input_path = input_path or url
# 携带额外参数
params = {}
if confidence_threshold:
params["confidence_threshold"] = confidence_threshold
return skill.get_output_analysis(input_path, params)
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def show_analyze_list(open_id, start_time=None, end_time=None):
# if not open_id:
# raise ValueError("必须提供本用户的OpenId/UserId")
try:
output_content = skill.get_output_analysis_list()
return output_content
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def get_analysis_export_url(request_id=None):
"""调用API分析视频"""
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
def format_result(result, output_level="standard", media_type="video", confidence_threshold=0.5):
"""格式化输出结果"""
category_map = {
"person": "",
"car": "",
"non-motor": "非机动车",
"cat": "",
"dog": "",
"pet": "宠物",
"package": "包裹"
}
if output_level == "json":
result_id = None
if result is not None:
result_json = result
result_id = result_json.get('id', {})
result_json = json.dumps(result_json.get('objectDetectionResponse', {}), ensure_ascii=False, indent=2)
else:
return "⚠️ 暂无分析结果"
return f"""
📊 基础目标检测分析结构化结果
{result_json}
""", result_id
elif output_level == "basic":
# 精简输出
data = result.get('data', {})
detection = data.get('detection', {})
return f"""
📊 基础目标检测报告
{'=' * 40}
置信度阈值: {confidence_threshold}
检测到目标总数: {detection.get('total_count', 0)}
"""
elif output_level == "standard":
# 标准输出
data = result.get('data', {})
detection = data.get('detection', {})
objects = "\n".join([
f" 📦 {category_map.get(obj.get('category'), obj.get('category'))}: {obj.get('count', 0)} 个,平均置信度: {obj.get('avg_confidence', 0)}"
for obj in detection.get('category_stats', [])])
return f"""
📊 基础目标检测分析报告
{'=' * 50}
检测时间: {data.get('detection_time', '未知')}
📹 素材类型: {media_type}
🎯 置信度阈值: {confidence_threshold}
🔍 检测结果:
检测到目标总数: {detection.get('total_count', 0)}
各类目标统计:
{objects if objects else ' 未检测到目标'}
{'=' * 50}
> 本报告仅供安防管理参考具体处置请按单位相关规定执行
"""
else:
# 完整输出JSON格式
return json.dumps(result, ensure_ascii=False, indent=2)
def main():
parser = argparse.ArgumentParser(description="基础目标检测工具")
parser.add_argument("--input", help="本地视频/图片文件路径")
parser.add_argument("--url", help="网络视频/图片的URL地址")
parser.add_argument("--media-type", choices=["video", "image"], default=ConstantEnum.DEFAULT__MEDIA_TYPE,
help="媒体类型video(视频流/视频文件), image(图片),默认 video")
parser.add_argument("--confidence-threshold", type=float, default=ConstantEnum.DEFAULT__CONFIDENCE_THRESHOLD,
help="置信度阈值,低于该分值不输出,默认 0.5")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示基础目标检测列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
# 设置 Python 进程内的环境变量
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在进行基础目标检测,请稍候...")
output_content = analyze_media(
input_path=args.input,
url=args.url,
media_type=args.media_type,
confidence_threshold=args.confidence_threshold,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 基础目标检测失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python3
# 基础目标检测技能配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ConstantEnum as ConstantEnumBase
from skills.face_analysis.scripts.config import ApiEnum as ApiEnumParent, ConstantEnum as ConstantEnumParent, \
SceneCodeEnum, ApiEnumCommonAiMixin
class ApiEnum(ApiEnumCommonAiMixin, ApiEnumParent):
pass
class ConstantEnum(ConstantEnumParent):
DEFAULT__MEDIA_TYPE = "video"
DEFAULT__CONFIDENCE_THRESHOLD = 0.5
@classmethod
def init(cls, config=None):
super().init(config)
ConstantEnumParent.DEFAULT__SCENE_CODE = SceneCodeEnum.BASIC_OBJECT_DETECTION_ANALYSIS.value

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import json
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.face_analysis.scripts.skill import Skill as SkillParent
from skills.smyx_common.scripts.util import JsonUtil
class Skill(SkillParent):
def __init__(self):
super().__init__()
def get_output_analysis_content_head(self, result=None):
return f"📊 基础目标检测分析结构化结果"
def get_output_analysis_content_foot(self, result):
pass
skill = Skill()

View File

@ -1,86 +0,0 @@
# 中医面诊分析工具 (face-analysis)
## 技能介绍
这是一个基于AI的中医面诊分析技能可以通过面部视频自动分析健康状况返回结构化的诊断结果和养生建议。
## 快速开始
### 1. 配置API信息
编辑 `scripts/config.py`设置你的API地址和密钥
```python
DEFAULT_API_URL = "https://your-api-server.com/api/v1/face-analysis"
DEFAULT_API_KEY = "your-api-key-here"
```
### 2. 分析本地视频
```bash
python scripts/face_analysis.py --input /path/to/your/video.mp4
```
### 3. 分析网络视频
```bash
python scripts/face_analysis.py --url https://example.com/video.mp4
```
## 功能特性
- ✅ 支持本地MP4视频上传
- ✅ 支持网络视频URL分析
- ✅ 三种输出详细程度:精简/标准/完整
- ✅ 结构化JSON结果输出
- ✅ 自动保存结果到文件
- ✅ 内置视频格式和大小校验
## 目录结构
```
face-analysis/
├── SKILL.md # 技能说明文件(系统自动加载)
├── README.md # 本说明文件
├── scripts/
│ ├── face_analysis.py # 主程序
│ └── config.py # 配置文件
├── references/
│ ├── api_doc.md # API接口文档
│ ├── tcm_theory.md # 中医面诊理论参考
│ └── faq.md # 常见问题
└── assets/
└── template.json # 返回结果模板
```
## 使用示例
### 标准输出
```
📊 中医面诊分析报告
==================================================
⏰ 分析时间: 2026-03-10 15:30:00
🎯 人脸检测: success (置信度: 95分)
🔍 诊断结果:
整体体质: 平和质
脏腑状况:
liver: 正常
heart: 轻微火旺
spleen: 略虚
lung: 正常
kidney: 正常
面色分析: 微黄
对应提示: 脾胃功能略弱
⚠️ 健康警示:
⚠️ 注意休息,避免熬夜
💡 养生建议:
💡 饮食清淡,减少辛辣食物摄入
💡 保持规律作息每晚11点前入睡
💡 适当进行有氧运动,如散步、太极拳
==================================================
```
### 输出到JSON文件
```bash
python scripts/face_analysis.py --input video.mp4 --detail full --output result.json
```
## 注意事项
1. 视频要求清晰正面面部光线充足时长5-30秒为宜
2. 支持格式mp4、avi、mov最大100MB
3. API需要自行部署或接入第三方服务
4. 结果仅供参考,不能替代专业医生诊断

View File

@ -1,69 +0,0 @@
# API接口文档
## 接口地址
`POST https://your-api-server.com/api/v1/face-analysis`
## 请求头
| 字段 | 必选 | 说明 |
|------|------|------|
| X-API-Key | 是 | API访问密钥 |
| Content-Type | 是 | multipart/form-data文件上传或 application/jsonURL模式 |
## 请求参数
### 1. 文件上传模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video | file | 是 | MP4视频文件 |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
### 2. URL模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video_url | string | 是 | 可公开访问的视频URL |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
## 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"analysis_time": "2026-03-10 15:30:00",
"face_detection": {
"status": "success",
"face_count": 1,
"quality_score": 95
},
"diagnosis": {
"overall_constitution": "平和质",
"organ_condition": {
"liver": "正常",
"heart": "轻微火旺",
"spleen": "略虚",
"lung": "正常",
"kidney": "正常"
},
"color_analysis": {
"complexion": "微黄",
"correspondence": "脾胃功能略弱"
}
},
"health_warnings": [
"注意休息,避免熬夜"
],
"suggestions": [
"饮食清淡,减少辛辣食物摄入"
]
}
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 400 | 请求参数错误 |
| 401 | API密钥无效 |
| 403 | 权限不足 |
| 413 | 文件过大 |
| 415 | 不支持的文件格式 |
| 500 | 服务器内部错误 |

View File

@ -1,3 +0,0 @@
pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3

View File

@ -1,52 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
scene_code and params.setdefault("sceneCode", scene_code)
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
scene_code and params.setdefault("sceneCode", scene_code)
params.setdefault("appCategory", ConstantEnum.DEFAULT__APP_CATEGORY)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
ConstantEnum.DEFAULT__SCENE_CODE and data.setdefault("sceneCode", ConstantEnum.DEFAULT__SCENE_CODE)
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python3
# 中医面诊分析工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase, ConstantEnum as ConstantEnumBase
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumBase):
ANALYSIS_URL = "/web/health-analysis/v2/start-health-analysis"
ANALYSIS_RESULT_URL = "/web/health-analysis/get-health-analysis-result"
PAGE_URL = "/web/health-analysis/page-health-analysis-result"
DETAIL_EXPORT_URL = ApiEnumBase.BASE_URL_HEALTH + "/health/order/api/getReportDetailExport?id="
@classmethod
def init(cls, config=None):
super().init(config)
class ApiEnumCommonAiMixin:
@classmethod
def init(cls, config=None):
parent = super()
if hasattr(parent, "init"):
parent.init(config)
ApiEnum.ANALYSIS_URL = "/web/ai-analysis/v2/start-common-ai-analysis"
ApiEnum.ANALYSIS_RESULT_URL = "/web/ai-analysis/get-common-ai-analysis-result"
ApiEnum.PAGE_URL = "/web/ai-analysis/page-common-ai-analysis-result"
class ConstantEnum(ConstantEnumBase):
DEFAULT__APP_CATEGORY = "PEI_NI_AN"

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
# import_path_common()
from skills.smyx_common.scripts.util import RequestUtil
# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB
def validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > MAX_FILE_SIZE_MB:
raise ValueError(f"文件过大,最大支持 {MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
def analyze_video(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
"""调用API分析视频"""
if not input_path and not url:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
try:
input_path = input_path or url
return skill.get_output_analysis(input_path)
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def show_analyze_list(open_id, start_time=None, end_time=None):
# if not open_id:
# raise ValueError("必须提供本用户的OpenId/UserId")
try:
output_content = skill.get_output_analysis_list()
return output_content
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def get_analysis_export_url(request_id=None):
"""调用API分析视频"""
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
def format_result(result, output_level="standard"):
"""格式化输出结果"""
if output_level == "json":
result_id = None
# if result.get('success'):
if result is not None:
result_json = result
result_id = result_json.get('id', {})
result_json = json.dumps(result_json.get('faceAnalysisResponse', {}), ensure_ascii=False, indent=2)
else:
# result_json = json.dumps(result, ensure_ascii=False, indent=2)
return "⚠️ 暂无分析结果"
return f"""
📊 面诊分析结构化结果
{result_json}
""", result_id
elif output_level == "basic":
# 精简输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
return f"""
📊 面诊分析结果
{'=' * 40}
整体体质: {diagnosis.get('overall_constitution', '未知')}
主要状况: {', '.join([f'{k}: {v}' for k, v in diagnosis.get('organ_condition', {}).items() if v != '正常'])}
健康提示: {data.get('health_warnings', ['无特殊警示'])[0] if data.get('health_warnings') else '无特殊警示'}
"""
elif output_level == "standard":
# 标准输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
face_detection = data.get('face_detection', {})
organ_status = "\n".join([f" {k}: {v}" for k, v in diagnosis.get('organ_condition', {}).items()])
warnings = "\n".join([f" ⚠️ {item}" for item in data.get('health_warnings', [])])
suggestions = "\n".join([f" 💡 {item}" for item in data.get('suggestions', [])])
return f"""
📊 中医面诊分析报告
{'=' * 50}
分析时间: {data.get('analysis_time', '未知')}
🎯 人脸检测: {face_detection.get('status', '未知')} (置信度: {face_detection.get('quality_score', 0)})
🔍 诊断结果:
整体体质: {diagnosis.get('overall_constitution', '未知')}
脏腑状况:
{organ_status}
面色分析: {diagnosis.get('color_analysis', {}).get('complexion', '未知')}
对应提示: {diagnosis.get('color_analysis', {}).get('correspondence', '未知')}
健康警示:
{warnings}
💡 养生建议:
{suggestions}
{'=' * 50}
"""
else:
# 完整输出JSON格式
return json.dumps(result, ensure_ascii=False, indent=2)
def main():
parser = argparse.ArgumentParser(description="中医面诊分析工具")
parser.add_argument("--input", help="本地MP4视频文件路径")
parser.add_argument("--url", help="网络视频MP4的URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示面诊视频历史列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在分析面诊视频,请稍候...")
output_content = analyze_video(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 面诊分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,267 +0,0 @@
#!/usr/bin/env python3
import datetime
import os
import sys
from .config import ApiEnum, ConstantEnum
from .api_service import ApiService
from skills.smyx_common.scripts.util import CommonUtil, JsonUtil
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase
from skills.smyx_common.scripts.base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def get_output_analysis_content_body(self, result=None):
result_json = result
result_json_pure_text = result_json.get("pureText")
if result_json_pure_text:
result_json = JsonUtil.parse(result_json_pure_text, result_json_pure_text)
result_json_common_ai_response = result_json.get("commonAiResponse")
if result_json_common_ai_response:
result_json = result_json_common_ai_response
result_json_health_ai_response = result_json.get("healthAiResponse")
if result_json_health_ai_response:
result_json = result_json_health_ai_response
result_json = JsonUtil.stringify(result_json, result_json)
return result_json
def get_output_analysis_content_head(self, result=None):
return f"📊 面诊分析结构化结果"
def get_output_analysis_content_foot(self, result):
result_id = result.get('id', {})
output_content_export_url = ApiEnum.DETAIL_EXPORT_URL + result_id
return f"🔗 获取报告导出图片链接: {output_content_export_url}"
def get_output_analysis_content(self, result):
if result is not None:
output_content = self.get_output_analysis_content_body(result) or ""
output_content_head = self.get_output_analysis_content_head(result)
output_content_foot = self.get_output_analysis_content_foot(result)
# d
if output_content_head:
output_content = f"""
{output_content_head}
""" + output_content
if output_content_foot:
output_content += f"""
{output_content_foot}
"""
else:
output_content = "⚠️ 暂无分析结果"
return output_content
def get_output_analysis(self, input_path, params={}):
response = self.get_analysis(
input_path, params
)
def _analysis_result():
return self.analysis_result(
data=response
)
new_response = CommonUtil.polling(_analysis_result,
check_condition=lambda res: res.get('needPageRefresh') is False, interval=5,
max_attempts=24)
output_content = self.get_output_analysis_content(new_response)
return output_content
def get_analysis(self, input_path, params={}):
import mimetypes
def _validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in ConstantEnum.SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(ConstantEnum.SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > ConstantEnum.MAX_FILE_SIZE_MB:
raise ValueError(
f"文件过大,最大支持 {ConstantEnum.MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
files = None
if not input_path:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
if (input_path.startswith("http://") or input_path.startswith("https://")):
params.update({
"videoUrl": input_path
})
else:
_validate_file(input_path)
# 自动检测 MIME 类型
mime_type, _ = mimetypes.guess_type(input_path)
if mime_type is None:
mime_type = 'application/octet-stream'
# 读取文件内容
with open(input_path, 'rb') as f:
file_content = f.read()
# 构建 multipart/form-data 格式的请求
files = {
'file': (os.path.basename(input_path), file_content, mime_type)
}
response = self.analysis(
params=params,
files=files
)
return response
def get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
response = self.page(pageNum, pageSize, *args, **argss)
if response:
for item in response:
if item.get("commonAiResponse") or item.get("healthAiResponse"):
item["reportImageUrl"] = _get_analysis_export_url(item.get("id"))
response_text = JsonUtil.stringify(response)
if response_text:
return f"""📊 分析报告记录列表(结构化结果)"
{response_text}
"""
else:
return "⚠️ 暂无分析报告记录"
def __get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
# open_id 仅用于本地识别不传给API - 参数已经在argss中page方法会正确处理
open_id = argss.pop('open_id', None)
# if not open_id:
# return "⚠️ 错误:缺少 open_id 参数"
# 获取总页数,然后循环获取所有页
output_all = ""
# 先获取第一页来获取总页数
# page 方法在基类中已经处理过,我们需要兼容两种返回结果:
# 1. 完整响应:{"success": true, "data": {"records": [...], "total": ...}}
# 2. 已经提取好的数据:直接返回 data 对象或 records 列表
response = self.page(pageNum or 1, pageSize or 30, *args, **argss)
if response is None:
return "⚠️ 获取报告列表失败response is None"
# 兼容处理:不同版本的基类返回不同格式
if isinstance(response, list):
# 基类直接返回了 records 列表,无法获取分页信息,直接使用
records = response
total = len(records)
pages = 1
elif isinstance(response, dict):
# 完整响应格式
if not response.get('success'):
error_msg = response.get('errorMsg', '未知错误')
return f"⚠️ 获取报告列表失败:{error_msg}"
data = response.get('data', {})
if not data or not isinstance(data, dict):
return "⚠️ 获取报告列表失败:数据格式错误"
total = data.get('total', 0)
pages = data.get('pages', 1)
records = data.get('records', [])
else:
return f"⚠️ 获取报告列表失败response type={type(response)}"
if not records:
return "⚠️ 暂无面诊分析报告记录"
output_all = f"📋 历史面诊分析报告清单(共 {total} 份)\n\n"
output_all += "| 报告名称 | 分析时间 | 体质判断 | 点击查看 |\n"
output_all += "|----------|----------|----------|----------|\n"
# 处理第一页
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
# 处理剩余页
for current_page in range(2, pages + 1):
response = self.page(current_page, 30, *args, **argss)
if not response or not isinstance(response, dict) or not response.get('success'):
continue
data = response.get('data', {})
if not data or not isinstance(data, dict):
continue
records = data.get('records', [])
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
output_all += "\n> 注:面诊分析结果仅供健康参考,不能替代专业医疗诊断。"
return output_all
skill = Skill()

View File

@ -1,127 +0,0 @@
altgraph==0.17.5
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
APScheduler==3.11.2
astroid==3.1.0
Authlib==1.6.6
blinker==1.4
cachetools==6.2.6
certifi==2026.1.4
cffi==2.0.0
chardet==5.2.0
charset-normalizer==3.4.4
click==8.3.1
coverage==7.13.2
coze-workload-identity==0.1.7
cozeloop==0.1.19
cryptography==3.4.8
Cython==3.2.4
dbus-python==1.2.18
dill==0.4.1
distro==1.7.0
distro-info==1.1+ubuntu0.2
et_xmlfile==2.0.0
fastapi==0.121.2
gitdb==4.0.12
gitignore_parser==0.1.13
GitPython==3.1.45
greenlet==3.3.1
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.2
httpx==0.28.1
httpx-ws==0.8.2
idna==3.11
importlib-metadata==4.6.4
inflect==7.5.0
iniconfig==2.3.0
isort==5.13.2
jeepney==0.7.1
Jinja2==3.1.6
jiter==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
keyring==23.5.0
langchain==1.0.3
langchain-core==1.0.2
langchain-openai==1.0.1
langgraph==1.0.2
langgraph-checkpoint==3.0.0
langgraph-prebuilt==1.0.2
langgraph-sdk==0.2.9
langsmith==0.4.39
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mccabe==0.7.0
mdurl==0.1.2
more-itertools==8.10.0
numpy==2.4.1
oauthlib==3.2.0
openai==2.16.0
openpyxl==3.1.5
orjson==3.11.5
ormsgpack==1.12.2
packaging==25.0
pandas==2.3.3
platformdirs==4.5.1
pluggy==1.6.0
psutil==7.1.3
psycopg2-binary==2.9.11
pycparser==3.0
pydantic==2.12.4
pydantic_core==2.41.5
pydash==8.0.6
Pygments==2.19.2
PyGObject==3.42.1
pyinstaller==6.18.0
pyinstaller-hooks-contrib==2026.0
PyJWT==2.10.1
pylint==3.1.0
PyMySQL==1.1.2
pyparsing==2.4.7
pytest==9.0.1
pytest-asyncio==1.3.0
pytest-cov==7.0.0
pytest-mock==3.15.1
python-apt==2.4.0+ubuntu4.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.3
regex==2026.1.15
requests==2.32.5
requests-toolbelt==1.0.0
rich==14.2.0
SecretStorage==3.3.1
setuptools==80.9.0
six==1.16.0
smmap==5.0.2
sniffio==1.3.1
sqlacodegen==3.2.0
SQLAlchemy==2.0.46
starlette==0.49.3
supervisor==4.2.1
tenacity==9.1.2
tiktoken==0.12.0
tomlkit==0.14.0
tqdm==4.67.1
typeguard==4.4.4
typing-inspection==0.4.2
typing_extensions==4.15.0
tzdata==2025.3
tzlocal==5.3.1
unattended-upgrades==0.1
urllib3==2.6.3
uvicorn==0.38.0
wadllib==1.3.6
watchdog==6.0.0
websockets==15.0.1
wheel==0.45.1
wsproto==1.3.2
xlrd==2.0.2
xxhash==3.6.0
zipp==1.0.0
zstandard==0.25.0

View File

@ -1,8 +0,0 @@
from .util import RequestUtil, CommonUtil, DatetimeUtil
from .base import *
__all__ = [
'RequestUtil',
'CommonUtil',
'BaseUtil'
]

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
from .config import ApiEnum
from .base import BaseApiService
from .util import RequestUtil, CommonUtil
class ApiService(BaseApiService):
def __init__(self):
super().__init__()
def get_download_url(self, tosKey, expireSeconds=3600):
return RequestUtil.http_post(
ApiEnum.GET_DOWNLOAD_URL__URL,
params={
"tosKey": tosKey,
"expireSeconds": expireSeconds * 24
}
)
def page(self, url, pageNum=None, pageSize=None, *args, **argss):
data = args[0] if len(args) > 0 else argss.get('data') if argss.get('data') is not None else {}
if pageNum is None:
pageNum = 1
if pageSize is None:
pageSize = ApiEnum.DEFAULT__PAGE_SIZE
paramsPage = {
'pageNum': int(pageNum),
'pageSize': int(pageSize)
}
data.update({
"page": paramsPage
})
if not CommonUtil.is_empty(data):
if (len(args) == 0):
argss.setdefault("data", data)
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def list(self, url=None, *args, **argss):
if url is not None:
argss["url"] = url
return self.page(1, ApiEnum.DEFAULT__PAGE_SIZE_MAX, *args, **argss)
def add(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def edit(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def delete(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def http_post(self, url=None, *args, **argss):
return RequestUtil.http_post(
url,
*args, **argss
)
def http_put(self, url=None, *args, **argss):
return RequestUtil.http_put(
url,
*args, **argss
)
def http_get(self, url=None, *args, **argss):
return RequestUtil.http_get(
url,
*args, **argss
)
return response
def http_delete(self, url=None, *args, **argss):
return RequestUtil.http_delete(
url,
*args, **argss
)
return response

View File

@ -1,33 +0,0 @@
class BaseUtil:
pass
class BaseMixin:
pass
class BaseDao:
pass
class BaseService:
def __init__(self):
super().__init__()
class BaseApiService(BaseService):
INSTANCE = None
def __init__(self):
super().__init__()
@classmethod
def get_instance(cls):
if cls.INSTANCE is None:
cls.INSTANCE = cls()
return cls.INSTANCE
class BaseSkill:
def __init__(self):
super().__init__()

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "http://192.168.1.234:9601/smyx-open-api"
base-url-open-h5: "http://192.168.1.234:4100"
base-url-health: "http://192.168.1.234:8080/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "https://livemonitortest.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitortest.lifeemergence.com"
base-url-health: "https://healthtest.lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,235 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from enum import Enum
from typing import Dict
import inspect
import yaml
import platform
class YamlUtil:
@staticmethod
def load(path, config: Dict = {}) -> Dict:
try:
if not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
return config
with open(path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f) or {}
for key, value in config.items():
if key not in config:
config[key] = value
return config
except:
pass
return config
@staticmethod
def save(path, config: Dict) -> Dict:
try:
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
except:
pass
return config
class BaseEnum:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
clsModule = cls.__module__
cls_path = inspect.getfile(cls)
clsFullName = f"{cls.__module__}.{cls.__name__}"
cls_dirpath = os.path.dirname(cls_path) # .../src
clsModulePath = clsModule.replace(".", "\\")
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src
config_path = os.path.join(cls_dirpath, "config.yaml")
config = YamlUtil.load(config_path)
cls.init(config)
env = config.get("env")
if env:
env_config_path = os.path.join(cls_dirpath, f"config-{env}.yaml")
env_config = YamlUtil.load(env_config_path)
cls.init(env_config)
@classmethod
def init(cls, config=None):
clsName = cls.__name__
clsConfig = config and config.get(clsName)
if clsConfig:
for config_key, config_value in clsConfig.items():
new_config_key = config_key = config_key.upper().replace("-", "_")
if hasattr(cls, new_config_key):
setattr(cls, new_config_key, config_value)
class ApiEnum(BaseEnum):
API_KEY = None
API_SECRET_KEY = None
DATABASE_URL = ""
BASE_URL_OPEN_API = ""
BASE_URL_OPEN_H5 = ""
BASE_URL_HEALTH = ""
OPEN_TOKEN = ""
TOKEN = ""
DEFAULT__REQUEST_TIMEOUT = 120
DEFAULT__PAGE_SIZE = 5
DEFAULT__PAGE_SIZE_MAX = 65536
GET_DOWNLOAD_URL__URL = BASE_URL_OPEN_API + "/api/tos/get-download-url"
class ConstantEnum(BaseEnum):
class SourceEnum(Enum):
ARK_CLAW = "ARK_CLAW"
JVS_CLAW = "JVS_CLAW"
LIGHT_CLAW = "LIGHT_CLAW"
WUHONG = "WUHONG"
COZE = "COZE"
SKILL_HUB = "SKILL_HUB"
CLAW_HUB = "CLAW_HUB"
FEISHU = "FEISHU"
DINGTALK = "DINGTALK"
WEIXIN = "WEIXIN"
YUANBAO = "YUANBAO"
WECOM = "WECOM"
QQBOT = "QQBOT"
APP__ID = ""
APP__SOURCE = SourceEnum.CLAW_HUB.value
IS_DEBUG = False
CURRENT__OPEN_ID = ""
CURRENT__USER_NAME = ""
CURRENT__TENTANT_CODE = ""
FEISHU_APP__ID = ""
FEISHU_APP__SECRET = ""
FEISHU_APP__RECEIVE_ID = ""
DEFAULT__SCENE_CODE = ""
DEFAULT__SKILL_HUB_NAME = APP__SOURCE
DEFAULT__SKILL_PLATFORM_NAME = ""
DEFAULT__OUTPUT_LEVEL = "json"
SUPPORTED_FORMATS = ["mp4", "avi", "mov"]
MAX_FILE_SIZE_MB = 10
@staticmethod
def is_debug():
return platform.system() != 'Linux' and ConstantEnum.IS_DEBUG
@classmethod
def init(cls, config=None):
super().init(config)
openclaw_sender_open_id = os.environ.get("OPENCLAW_SENDER_OPEN_ID")
openclaw_sender_username = os.environ.get("OPENCLAW_SENDER_USERNAME")
feishu_open_id = os.environ.get("FEISHU_OPEN_ID")
if openclaw_sender_open_id:
cls.CURRENT__OPEN_ID = openclaw_sender_open_id
if openclaw_sender_username:
cls.CURRENT__USER_NAME = openclaw_sender_username
if feishu_open_id:
cls.FEISHU_APP__RECEIVE_ID = feishu_open_id
class SceneCodeEnum(Enum):
# 开放 #
OPEN_HEALTH_AI_ANALYSIS = "OPEN_HEALTH_AI_ANALYSIS"
OPEN_PERSON_RISK_ANALYSIS = "OPEN_PERSON_RISK_ANALYSIS"
# 智眸 #
PUBLIC_AREA_AI_ANALYSIS = "PUBLIC_AREA_AI_ANALYSIS"
PERSONNEL_LEAVE_POST_MONITORING = "PERSONNEL_LEAVE_POST_MONITORING"
CRAWL_MONITOR = "CRAWL_MONITOR"
# 陪你安 #
PEI_NI_AN_DEFAULT = "PEI_NI_AN_DEFAULT"
PET_ANALYSIS = "PET_ANALYSIS"
CRAWL_ANALYSIS = "CRAWL_ANALYSIS"
AQUARIUM_ANALYSIS = "AQUARIUM_ANALYSIS"
PSYCHOLOGY_ANALYSIS = "PSYCHOLOGY_ANALYSIS"
AUTISM_ANALYSIS = "AUTISM_ANALYSIS"
DIET_ANALYSIS = "DIET_ANALYSIS"
DRIVE_ANALYSIS = "DRIVE_ANALYSIS"
SPORT_ANALYSIS = "SPORT_ANALYSIS"
EMOTION_ANALYSIS = "EMOTION_ANALYSIS"
STUDY_ANALYSIS = "STUDY_ANALYSIS"
INFANT_SAFETY_MONITORING_ANALYSIS = "INFANT_SAFETY_MONITORING"
PHONE_USAGE_MONITORING_ANALYSIS = "PHONE_USAGE_MONITORING"
INCONTINENCE_ALERT_ANALYSIS = "INCONTINENCE_ALERT"
RESPIRATORY_SYMPTOM_RECOGNITION_ANALYSIS = "RESPIRATORY_SYMPTOM_RECOGNITION"
ELECTRIC_VEHICLE_DETECTION_ANALYSIS = "ELECTRIC_VEHICLE_DETECTION"
SMOKING_DETECTION_ANALYSIS = "SMOKING_DETECTION"
PET_DETECTION_FEEDER_ANALYSIS = "PET_DETECTION_FEEDER"
PET_HEALTH_MONITORING_ANALYSIS = "PET_HEALTH_MONITORING"
STROKE_RISK_SCREENING_ANALYSIS = "STROKE_RISK_SCREENING"
HUMAN_DETECTION_ANALYSIS = "HUMAN_DETECTION"
STRANGER_RECOGNITION_ANALYSIS = "STRANGER_RECOGNITION"
FOCUS_ANALYSIS_ANALYSIS = "FOCUS_ANALYSIS"
HUMAN_POSTURE_RECOGNITION_ANALYSIS = "HUMAN_POSTURE_RECOGNITION"
HUMAN_EMOTION_RECOGNITION_ANALYSIS = "HUMAN_EMOTION_RECOGNITION"
FIRE_SMOKE_DETECTION_ANALYSIS = "FIRE_SMOKE_DETECTION"
BASIC_OBJECT_DETECTION_ANALYSIS = "BASIC_OBJECT_DETECTION"
CHILD_DANGEROUS_BEHAVIOR_RECOGNITION_ANALYSIS = "CHILD_DANGEROUS_BEHAVIOR_RECOGNITION"
PET_RESTRICTED_AREA_WARNING_ANALYSIS = "PET_RESTRICTED_AREA_WARNING"
SLEEP_QUALITY_ANALYSIS_ANALYSIS = "SLEEP_QUALITY_ANALYSIS"
PET_DETECTION_ANALYSIS = "PET_DETECTION"
PSYCHOLOGICAL_STRESS_ASSESSMENT_ANALYSIS = "PSYCHOLOGICAL_STRESS_ASSESSMENT"
VISUAL_QA_ANALYSIS = "VISUAL_QA"
PET_BODY_HEALTH_ANALYSIS = "PET_BODY_HEALTH_ANALYSIS"
PET_BEHAVIOR_DETECTION_ANALYSIS = "PET_BEHAVIOR_DETECTION"
INFANT_SUFFOCATION_WARNING_ANALYSIS = "INFANT_SUFFOCATION_WARNING"
STRANGER_APPROACH_WARNING_ANALYSIS = "STRANGER_APPROACH_WARNING"
IMAGE_QUALITY_DETECTION_ANALYSIS = "IMAGE_QUALITY_DETECTION"
CHILD_EMOTION_RECOGNITION_ANALYSIS = "CHILD_EMOTION_RECOGNITION"
OUTDOOR_MONITORING_ANALYSIS = "OUTDOOR_MONITORING"
FALL_DETECTION_IMAGE_ANALYSIS = "FALL_DETECTION_IMAGE"
CUSTOM_TIMELAPSE_ANALYSIS = "CUSTOM_TIMELAPSE"
CONTACTLESS_VITAL_SIGNS_MONITORING_ANALYSIS = "CONTACTLESS_VITAL_SIGNS_MONITORING"
VIDEO_SEARCH_ANALYSIS = "VIDEO_SEARCH"
FAMILIAR_PERSON_RECOGNITION_ANALYSIS = "FAMILIAR_PERSON_RECOGNITION"
TCM_CONSTITUTION_RECOGNITION_ANALYSIS = "TCM_CONSTITUTION_RECOGNITION"
CONTACTLESS_HEALTH_RISK_DETECTION_ANALYSIS = "CONTACTLESS_HEALTH_RISK_DETECTION"
UNACCOMPANIED_MONITORING_ANALYSIS = "UNACCOMPANIED_MONITORING"
ELDERLY_FALL_DETECTION_ANALYSIS = "ELDERLY_FALL_DETECTION"
PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION_ANALYSIS = "PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION"
PET_BREED_INDIVIDUAL_RECOGNITION_ANALYSIS = "PET_BREED_INDIVIDUAL_RECOGNITION"
ELDERLY_BED_EXIT_WANDERING_MONITORING_ANALYSIS = "ELDERLY_BED_EXIT_WANDERING_MONITORING"
ARRHYTHMIA_EARLY_WARNING_ANALYSIS = "ARRHYTHMIA_EARLY_WARNING"
FIRE_DETECTION_ANALYSIS = "FIRE_DETECTION"
VISUAL_SUMMARY_ANALYSIS = "VISUAL_SUMMARY"
PACKAGE_DETECTION_ANALYSIS = "PACKAGE_DETECTION"
INFANT_BLANKET_KICK_MONITORING_ANALYSIS = "INFANT_BLANKET_KICK_MONITORING"
PET_CALMING_TRIGGER_ANALYSIS = "PET_CALMING_TRIGGER"
CAT_FACE_RECOGNITION_ANALYSIS = "CAT_FACE_RECOGNITION"
INFANT_SLEEP_MONITORING_ANALYSIS = "INFANT_SLEEP_MONITORING"
VIRTUAL_FENCE_INTRUSION_WARNING_ANALYSIS = "VIRTUAL_FENCE_INTRUSION_WARNING"
FALL_DETECTION_VIDEO_ANALYSIS = "FALL_DETECTION_VIDEO"
INFANT_CRY_ANALYSIS = "INFANT_CRY_ANALYSIS"
PET_VOCAL_EMOTION_ANALYSIS = "PET_VOCAL_EMOTION_ANALYSIS"
BIRD_RECOGNITION_ANALYSIS = "BIRD_RECOGNITION"
FRAUD_CALL_IDENTIFICATION = "FRAUD_CALL_IDENTIFICATION"

View File

@ -1,18 +0,0 @@
env: prod
ApiEnum:
api-key:
api-secret-key:
database-url:
base-url-open-api: "https://open.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitor.lifeemergence.com"
base-url-health: "https://lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: false
app--id: x1a3s4nwy1s2r4se
current--tentant-code: "PEI_NI_AN"
feishu-app--id: cli_a93d769369badcb1
feishu-app--secret:
default--skill-platform-name: ARK_CLAW
# default--scene-code: PEI_NI_AN_DEFAULT

View File

@ -1,348 +0,0 @@
#!/usr/bin/env python3
"""
本地化轻量级数据库封装
使用SQLite + SQLAlchemy ORM
支持基础CRUD操作通过继承BaseDao快速实现各表的Dao层
"""
import datetime
import sys
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
from sqlalchemy import create_engine, Column, Integer, String, DateTime, func, Select, Table, MetaData, select, or_
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.sql.expression import text
from skills.smyx_common.scripts.config import ConstantEnum, ApiEnum
from skills.smyx_common.scripts.util import StringUtil, DatetimeUtil, FileUtil
from skills.smyx_common.scripts.base import BaseMixin, BaseDao
# 基础模型类
Base = declarative_base()
# 泛型类型,用于返回对应模型实例
T = TypeVar('T', bound=Base)
meta = MetaData()
DATABASE_URL = ApiEnum.DATABASE_URL
class BaseModelMixin(BaseMixin):
@classmethod
def load(cls, source: dict):
"""
获取源枚举
:param source:
:return: User
"""
column_names = cls.__table__.columns.keys()
user_dict = {k: source.get(StringUtil.snake_to_camel(k)) for k in column_names}
user_dict["create_time"] = DatetimeUtil.parse(user_dict["create_time"])
user_dict["update_time"] = DatetimeUtil.parse(user_dict["update_time"])
model = cls(**user_dict)
return model
class Dao(BaseDao):
"""
基础Dao类提供通用的CRUD操作
子类只需配置__model__和__tablename__即可使用
"""
__model__: Type[T] = None # 对应的模型类,子类必须配置
__tablename__: str = None # 表名,子类必须配置
def get_db_path(self, db_path):
import os
cwd = os.getcwd()
workspace = os.path.dirname(cwd)
workspace = os.path.dirname(workspace)
workspace = os.environ.get('OPENCLAW_WORKSPACE', workspace)
parent_dir = os.path.join(workspace, "data")
FileUtil.mkdir(parent_dir)
db_path = os.path.join(parent_dir, db_path)
return db_path
def __init__(self, db_path: str = None):
"""
初始化Dao
:param db_path: SQLite数据库文件路径
"""
if not db_path:
db_path = "smyx-common-claw.db"
db_path = self.get_db_path(db_path)
self.engine = create_engine(f"sqlite:///{db_path}", echo=False)
# 创建会话工厂
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
# 初始化表结构
self._create_tables()
self._alter_tables()
def _create_tables(self) -> None:
"""创建所有表结构"""
Base.metadata.create_all(bind=self.engine)
def _alter_tables(self) -> None:
"""创建所有表结构"""
sql_statement = "ALTER TABLE sys_user ADD COLUMN source_id INT;"
# 3. 执行语句
try:
with self.engine.connect() as connection:
connection.execute(text(sql_statement))
connection.commit() # 对于数据定义语言(DDL),需要显式提交
except Exception as e:
connection.rollback()
if len(e.args) and "duplicate column name" in e.args[0]:
pass
else:
raise
def get_session(self) -> Session:
"""获取数据库会话"""
return self.SessionLocal()
def save(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
try:
return self.add(
model
)
except Exception as e:
return self.update(
model
)
def add(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
session = self.get_session()
try:
session.add(model)
session.commit()
session.refresh(model)
return model
finally:
session.close()
def create(self, **kwargs) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
instance = self.__model__(**kwargs)
return self.add(instance)
def get_by_id(self, record_id: int) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
return session.query(self.__model__).filter(self.__model__.id == record_id).first()
finally:
session.close()
def get_by_username(self, username: str) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None) # 关键:使用 .is_(None) 来判断 SQL 的 NULL
)
return session.query(self.__model__).filter(self.__model__.username == username,
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None)
# 关键:使用 .is_(None) 来判断 SQL 的 NULL
)).first()
finally:
session.close()
def list(self, filters: Optional[Dict[str, Any]] = None, limit: Optional[int] = None,
offset: Optional[int] = None) -> List[T]:
"""
查询记录列表
:param filters: 过滤条件字典{"name": "张三", "age": 18}
:param limit: 最大返回数量
:param offset: 偏移量
:return: 模型实例列表
"""
session = self.get_session()
try:
query = session.query(self.__model__)
# .where(self.__model__.id != 2, self.__model__.id == 1))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
if offset:
query = query.offset(offset)
if limit:
query = query.limit(limit)
return query.all()
finally:
session.close()
def update(self, model) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == model.id).first()
if not instance:
return None
column_names = self.__model__.__table__.columns.keys()
for key in column_names:
value = getattr(model, key)
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def modify(self, record_id: int, **kwargs) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def update_by_username(self, username: str, **kwargs) -> Optional[T]:
"""
更新记录
:param username: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.username == username).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def delete(self, record_id: int) -> bool:
"""
删除记录
:param record_id: 记录ID
:return: 删除成功返回True失败返回False
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return False
session.delete(instance)
session.commit()
return True
finally:
session.close()
def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
"""
统计记录数量
:param filters: 过滤条件字典
:return: 记录数量
"""
session = self.get_session()
try:
query = session.query(func.count(self.__model__.id))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
return query.scalar()
finally:
session.close()
class User(Base, BaseModelMixin):
"""用户模型"""
__tablename__ = "sys_user"
id = Column(String(32), primary_key=True, index=True)
source_id = Column(String(32), comment="源头id")
username = Column(String(100), unique=True, index=True, nullable=False, comment="用户名")
email = Column(String(45), unique=True, index=True, comment="邮箱")
birthday = Column(DateTime, unique=True, index=True, comment="邮箱")
sex = Column(Integer, comment="性别")
age = Column(Integer, comment="年龄")
token = Column(String(500), comment="token")
open_token = Column(String(1000), comment="开放token")
source = Column(String(50), comment="token")
del_flag = Column(Integer, comment="是否删除", default=0)
create_time = Column(DateTime, default=func.now(), comment="创建时间")
update_time = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")
SourceEnum = ConstantEnum.SourceEnum
class UserDao(Dao):
"""用户Dao继承BaseDao即可拥有所有基础CRUD功能"""
__model__ = User
__tablename__ = "users"
if __name__ == "__main__":
pass

View File

@ -1,82 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
from .config import ApiEnum as ApiEnumBase, ConstantEnum
from .base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from .util import FileUtil
from .api_service import ApiService
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
class AgentSkill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def ai_chat(self, prompt: str, session_id: str = None, timeout: int = 120):
"""
通过 subprocess 调用 openclaw agent 命令
Args:
prompt: 分析提示
session_id: 会话 ID可选
timeout: 超时时间
Returns:
分析结果或会话 ID
"""
import uuid
# 生成唯一会话 ID
if not session_id:
entry_script = sys.argv[0]
abs_entry_script = os.path.abspath(entry_script)
main_name = FileUtil.get_name(abs_entry_script)
session_id = f"{main_name}--{uuid.uuid4()}"
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent (会话:{session_id})..., prompt:{prompt}")
# 构建命令
cmd = [
"openclaw",
"agent",
"-m", str(prompt),
"--session-id", session_id,
"--thinking", "minimal",
"--timeout", str(timeout)
]
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行命令{' '.join(cmd)}")
try:
# 执行命令
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 10
)
if result.stderr:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{result.stderr}")
return
output = result.stdout
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行成功:{output}")
return output
except subprocess.TimeoutExpired as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 超时({timeout}秒),任务可能仍在后台运行:{e}")
except Exception as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{e}")
skill = Skill()

View File

@ -1,413 +0,0 @@
#!/usr/bin/env python3
import json
import os
import traceback
import requests
from .config import ApiEnum, ConstantEnum, sys, YamlUtil
from .base import BaseUtil
import time
import logging
from typing import Any, Callable, Optional, TypeVar, Dict
import pydash as _
if ConstantEnum.is_debug():
import http.client
# 【关键代码】开启调试模式
http.client.HTTPConnection.debuglevel = 1
# 可选:如果你希望日志更整洁,可以配合 logging 模块(否则打印会比较乱)
import logging
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
class StringUtil(BaseUtil):
@staticmethod
def camel_to_snake(name):
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
@staticmethod
def snake_to_pascal(name):
import re
name = re.sub(r'^([a-z])', lambda m: m.group(1).upper(), name)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
@staticmethod
def snake_to_camel(name):
import re
# 逻辑:匹配 '_[a-z]' (下划线+小写字母),将其替换为对应的大写字母(去掉下划线)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
class FileUtil(BaseUtil):
@staticmethod
def get_fullname(path):
try:
return os.path.basename(path)
except Exception as e:
CommonUtil.trace_exception_stack(e)
return ""
@staticmethod
def get_name(path):
try:
return os.path.splitext(os.path.basename(path))[0]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def get_ext(path):
try:
return os.path.splitext(os.path.basename(path))[1]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def open(path):
try:
return open(path, 'w', encoding='utf-8')
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def mkdir(path):
try:
os.makedirs(path, exist_ok=True)
except Exception as e:
CommonUtil.trace_exception_stack(e)
class JsonUtil(BaseUtil):
@staticmethod
def stringify(json_obj, default_str=""):
try:
return json.dumps(json_obj, ensure_ascii=False, indent=2)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_str
@staticmethod
def parse(json_str, default_json={}):
try:
return json.loads(json_str)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_json
class CommonUtil(BaseUtil):
@staticmethod
def trace_exception_stack(e):
if ConstantEnum.is_debug():
print(f"❌ 错误描述: {str(e)}, 堆栈跟踪:")
traceback.print_stack()
@staticmethod
def polling(
action: Callable[[], Any],
check_condition: Callable[[Any], bool],
on_success: Optional[Callable[[Any], None]] = None,
on_retry: Optional[Callable[[Any, int], None]] = None,
on_error: Optional[Callable[[Exception], None]] = None,
interval: float = 1.0,
max_attempts: int = 5,
description: str = "轮询任务"
) -> Optional[Any]:
"""
通用的轮询处理函数
:param action:
[必填] 执行动作的回调函数
例如发送 HTTP 请求查询数据库状态等
必须返回一个结果对象供 check_condition 使用
:param check_condition:
[必填] 检查是否结束的回调函数
接收 action 的返回值返回 True 表示满足结束条件False 表示继续轮询
例如lambda res: res.get('need_refresh') is False
:param on_success:
[可选] check_condition 返回 True 时执行的回调通常用于记录日志或处理最终数据
:param on_retry:
[可选] 当需要继续轮询时执行的回调
参数(当前结果, 当前尝试次数)可用于打印进度
:param on_error:
[可选] action 抛出异常时执行的回调
参数(异常对象)
:param interval:
每次轮询之间的等待时间
:param max_attempts:
最大尝试次数防止死循环
:param description:
任务描述用于日志输出
:return:
如果成功返回 action 的最后一次返回值如果超时或失败返回 None
"""
attempts = 0
print(f"🚀 开始执行 [{description}]...")
while attempts < max_attempts:
attempts += 1
try:
# 1. 执行动作
result = action()
last_result = result
# 2. 检查条件
if check_condition(result):
print(f"✅ [{description}] 成功!条件已满足 (尝试次数: {attempts}, 耗时{interval * attempts}秒)")
if on_success:
on_success(result)
return result
# 3. 条件未满足,准备重试
if on_retry:
on_retry(result, attempts)
else:
# 默认日志行为
print(
f"⏳ [{description}] 条件未满足,{interval}秒后重试... ({attempts}/{max_attempts}, 耗时{interval * attempts}秒)")
time.sleep(interval)
except Exception as e:
# 4. 异常处理
if on_error:
on_error(e)
else:
# 默认错误行为:打印错误并继续
logging.error(f"❌ [{description}] 发生异常: {e}")
print(f"⚠️ [{description}] 遇到错误,{interval}秒后重试...")
time.sleep(interval)
# 5. 超时处理
print(f"⚠️ [{description}] 失败:达到最大尝试次数 ({max_attempts}),强制停止。")
return None
@staticmethod
def is_empty(data):
# 1. 如果是 None (对应 JSON 的 null)
if data is None:
return True
# 2. 如果是字典或列表,且长度为 0 (对应 {} 或 [])
if isinstance(data, (dict, list)) and len(data) == 0:
return True
from datetime import date, datetime
class DatetimeUtil(BaseUtil):
FORMAT__DATETIME = "%Y-%m-%d %H:%M:%S"
@staticmethod
def now_str():
return DatetimeUtil.format(DatetimeUtil.now())
@staticmethod
def today_str():
return DatetimeUtil.format_date(DatetimeUtil.today())
@staticmethod
def now():
return datetime.now()
@staticmethod
def today():
return DatetimeUtil.now().replace(hour=0, minute=0, second=0, microsecond=0)
@staticmethod
def format(date):
return date.strftime('%Y-%m-%d %H:%M:%S') if type(date) == datetime else date
@staticmethod
def format_date(date):
return date.strftime('%Y-%m-%d') if type(date) == datetime else date
@staticmethod
def parse(date_str):
if type(date_str) == int:
return datetime.fromtimestamp(date_str)
return datetime.strptime(date_str, DatetimeUtil.FORMAT__DATETIME) if type(date_str) == str else date_str
@staticmethod
def timestamp(date=now()):
return int(date.timestamp() * 1000)
class RequestUtil(BaseUtil):
BASE_URL = ApiEnum.BASE_URL_OPEN_API
AUTHORIZATION_RETRY_COUNT_MAX = 3
authorization_retry_count = 0
@classmethod
def http_post(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("post", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_put(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("put", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_delete(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("delete", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_get(cls, url, params=None, headers=None, *args, **argss):
return cls.http_request("get", url, params=params, headers=headers, *args, **argss)
@classmethod
def http_request(cls, method, url, data=None, params=None, headers=None, options=None, *args,
timeout=ApiEnum.DEFAULT__REQUEST_TIMEOUT, **argss):
def _get_or_create_user(username):
_url = ApiEnum.BASE_URL_HEALTH + "/sys/phoneLogin"
open_id = username
_data = {
"silent": 1,
"register": 1,
"openId": open_id,
"mobile": username
}
try:
_response = requests.post(_url, json=_data)
if _response.status_code == 200:
_response_json = _response.json()
if _response_json and _response_json.get("success"):
return _response_json and _response_json.get("result")
except Exception as _e:
CommonUtil.trace_exception_stack(_e)
return {}
try:
headers = headers or {}
if not url.startswith("https://") and not url.startswith("http://"):
url = cls.BASE_URL + url
headers['App-Id'] = ConstantEnum.APP__ID
# ConstantEnum.CURRENT__USER_NAME = ConstantEnum.CURRENT__OPEN_ID = "ou_86fdd8e0d5f116c18a9dd550abefe6d2"
current__user_name = ApiEnum.API_SECRET_KEY or ConstantEnum.CURRENT__USER_NAME or ConstantEnum.CURRENT__OPEN_ID
if (not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN) and current__user_name:
try:
from .dao import UserDao, User
user_dao = UserDao()
found_user = user_dao.get_by_username(current__user_name)
if found_user:
ApiEnum.TOKEN = found_user.token
ApiEnum.OPEN_TOKEN = found_user.open_token
if not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN:
new_current_user = _get_or_create_user(current__user_name)
if new_current_user:
ApiEnum.TOKEN = new_current_user.get("token")
ApiEnum.OPEN_TOKEN = new_current_user.get("openToken")
current_user_info = new_current_user.get("userInfo")
if current_user_info:
current_user_info["token"] = new_current_user.get("token")
current_user_info["openToken"] = new_current_user.get(
"openToken")
user_model = User.load(current_user_info)
user = user_dao.save(
user_model
)
except Exception as e:
CommonUtil.trace_exception_stack(e)
raise
headers.setdefault("X-Access-Token", ApiEnum.TOKEN)
headers.setdefault("X-Api-Key", ApiEnum.API_SECRET_KEY)
headers.setdefault("Authorization", ApiEnum.OPEN_TOKEN)
data = data or {}
params = params or {}
options = options or {}
ConstantEnum.CURRENT__TENTANT_CODE and data.setdefault('tenantCode', ConstantEnum.CURRENT__TENTANT_CODE)
ConstantEnum.DEFAULT__SKILL_HUB_NAME and data.setdefault('skillHubName',
ConstantEnum.DEFAULT__SKILL_HUB_NAME)
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME and data.setdefault('skillPlatform',
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME)
if current__user_name:
data.setdefault('pnaUserName', current__user_name)
if bool(options.get("dataAsParams")):
params.update(data)
print(f"🔄 请求拦截, URL:{url}", "method", method, "params", params, "data", data, "headers", headers,
"options", options,
"timeout",
timeout)
response = requests.request(method, url, *args, json=data, params=params, headers=headers,
timeout=int(timeout), **argss)
response_text = response.text if ConstantEnum.is_debug() else response
if response.status_code == 401 and cls.authorization_retry_count < cls.AUTHORIZATION_RETRY_COUNT_MAX:
print(f"❌ 请求拦截, 鉴权:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
ApiEnum.TOKEN = ApiEnum.OPEN_TOKEN = None
if found_user:
found_user.token = found_user.open_token = None
user_dao.update(found_user)
cls.authorization_retry_count += 1
return cls.http_request(method, url, data, params, headers, options, *args, timeout=timeout, **argss)
elif response.status_code != 200:
raise requests.exceptions.RequestException(
response, response=response)
response_json = response.json()
if not bool(response_json['success']):
raise requests.exceptions.RequestException(
response, response=response)
response_json_data = response_json.get("data", response_json.get("result"))
response_json_data = response_json_data.get("records") if response_json_data and type(
response_json_data) == dict and "records" in response_json_data else response_json_data
print(f"✅ 请求拦截, 成功:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
return response_json_data
except Exception as e:
CommonUtil.trace_exception_stack(e)
response_text = _.get(e.args, '0.text')
print(
f"❌ 请求拦截, 失败: {e}, e.response.text: {response_text}, url:{url}",
"method",
method,
"params",
params,
"data", data, "headers",
"response", hasattr(e, 'response') and e.response,
headers,
"timeout",
timeout)
raise

View File

@ -1,165 +0,0 @@
---
name: "bird-recognition-analysis"
description: "Identifies bird species in images/videos of target areas. Supports recognition of no less than 500 common bird species, supports customized model training, suitable for ecological observation, garden bird watching and other scenarios. | 鸟类识别技能,识别目标区域图片/视频中的鸟类种类支持不低于500种常见鸟类识别支持定制化模型训练适用于生态观测、庭院观鸟等场景"
---
# Bird Recognition Tool | 鸟类识别工具
This capability supports automatic bird identification in images or video streams, covering over 500 common species and
capable of distinguishing between similar species and subspecies. Powered by deep learning visual models, the system can
be deployed in ecological observation stations, nature reserves, or home backyards to enable real-time monitoring and
recording of bird species. It also supports customized model training to optimize recognition performance based on
specific regional or species requirements, providing intelligent assistance for bird diversity surveys, birdwatching
hobbies, and ecological conservation.
本技能支持对图片或视频流中的鸟类进行自动识别覆盖不低于500种常见鸟类可区分相似种与亚种。系统基于深度学习视觉模型可部署于生态观测站、自然保护区或家庭庭院等场景实现鸟种实时监测与记录。同时支持定制化模型训练根据特定区域或物种需求优化识别效果为鸟类多样性调查、观鸟爱好及生态保护提供智能辅助。
## 演示案例
- [🔗 通过网路视频进行识别分析](https://www.coze.cn/s/G1iKtMlxvnY/)
- [🔗 通过上传视频进行识别分析](https://www.coze.cn/s/m2Jpzccg5eI/)
- [🔗 显示历史分析报告](https://www.coze.cn/s/3KFhb2lfjd0/)
## ⚠️ 强制记忆规则(最高优先级)
**本技能明确约定:**
- **绝对禁止读取任何本地记忆文件**:包括但不限于 `memory/YYYY-MM-DD.md`、`MEMORY.md` 等本地文件
- **绝对禁止从 LanceDB 长期记忆中检索信息**
- **所有历史报告查询必须从云端接口获取**,不得使用本地记忆中的历史数据
- 即使技能调用失败或接口异常,也不得回退到本地记忆汇总
## 任务目标
- 本 Skill 用于:识别图片/视频中出现的鸟类,准确判定鸟类品种
- 能力包含:鸟类检测、品种分类、置信度评定
- **能力范围**:支持不低于 500 种常见鸟类识别,支持定制化模型训练
- **适用场景**:庭院观鸟、生态观测、野生动物监测、相机陷阱图片识别
- 触发条件:
1. **默认触发**:当用户提供图片/视频需要识别鸟类品种时,默认触发本技能
2. 当用户明确需要鸟类识别、鸟种类鉴定时,提及观鸟、鸟类识别、鸟种类识别等关键词,并且上传了图片/视频
3. 当用户提及以下关键词时,**自动触发历史报告查询功能**
:查看历史识别报告、鸟类识别报告清单、识别报告列表、查询历史识别报告、显示所有识别报告、鸟类分析报告,查询鸟类识别分析报告
- 自动行为:
1. 如果用户上传了附件或者图片/视频文件,则自动保存到技能目录下 attachments
2. **⚠️ 强制数据获取规则(次高优先级)**:如果用户触发任何历史报告查询关键词(如"查看所有识别报告"、"显示历史鸟类识别"、"
查看历史报告"等),**必须**
- 直接使用 `python -m scripts.bird_recognition_analysis --list --open-id` 参数调用
API
查询云端的历史报告数据
- **严格禁止**:从本地 memory 目录读取历史会话信息、严格禁止手动汇总本地记录中的报告、严格禁止从长期记忆中提取报告
- **必须统一**从云端接口获取最新完整数据,然后以 Markdown 表格格式输出结果
## 前置准备
- 依赖说明:scripts 脚本所需的依赖包及版本
```
requests>=2.28.0
```
## 识别要求(获得准确结果的前提)
为了获得准确的鸟类识别,请确保:
1. **鸟类完整清晰可见**,避免过度遮挡和远距离模糊拍摄
2. **光照充足**,色彩自然,便于品种特征识别
- 如果是视频,建议截取鸟类清晰停留的片段上传
## 操作步骤
### 🔒 open-id 获取流程控制(强制执行,防止遗漏)
**在执行鸟类识别分析前,必须按以下优先级顺序获取 open-id**
```
第 1 步:【最高优先级】检查技能所在目录的配置文件(优先)
路径skills/smyx_common/scripts/config.yaml相对于技能根目录
完整路径示例:${OPENCLAW_WORKSPACE}/skills/{当前技能目录}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置/api-key 为空)
第 2 步:检查 workspace 公共目录的配置文件
路径:${OPENCLAW_WORKSPACE}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置)
第 3 步:检查用户是否在消息中明确提供了 open-id
↓ (未提供)
第 4 步:❗ 必须暂停执行,明确提示用户提供用户名或手机号作为 open-id
```
**⚠️ 关键约束:**
- **禁止**自行假设,自行推导,自行生成 open-id 值(如 openclaw-control-ui、default、userC113、user123 等)
- **禁止**跳过 open-id 验证直接调用 API
- **必须**在获取到有效 open-id 后才能继续执行分析
- 如果用户拒绝提供 open-id说明用途用于保存和查询历史报告记录并询问是否继续
---
- 标准流程:
1. **准备鸟类图片/视频输入**
- 提供本地文件路径或网络 URL
- 确保鸟类清晰可见
2. **获取 open-id强制执行**
- 按上述流程控制获取 open-id
- 如无法获取,必须提示用户提供用户名或手机号
3. **执行鸟类识别分析**
- 调用 `-m scripts.bird_recognition_analysis` 处理输入(**必须在技能根目录下运行脚本**
- 参数说明:
- `--input`: 本地图片/视频文件路径(使用 multipart/form-data 方式上传)
- `--url`: 网络图片/视频 URL 地址API 服务自动下载)
- `--open-id`: 当前用户的 open-id必填按上述流程获取
- `--list`: 显示历史鸟类识别分析报告列表清单(可以输入起始日期参数过滤数据范围)
- `--api-key`: API 访问密钥(可选)
- `--api-url`: API 服务地址(可选,使用默认值)
- `--detail`: 输出详细程度basic/standard/json默认 json
- `--output`: 结果输出文件路径(可选)
4. **查看分析结果**
- 接收结构化的鸟类识别分析报告
- 包含:输入基本信息、检测到的鸟类数量、每个鸟类品种、置信度、科普小知识
## 资源索引
- 必要脚本:见 [scripts/bird_recognition_analysis.py](scripts/bird_recognition_analysis.py)(用途:调用 API 进行鸟类识别分析,本地文件使用
multipart/form-data 方式上传,网络 URL 由 API 服务自动下载)
- 配置文件:见 [scripts/config.py](scripts/config.py)(用途:配置 API 地址、默认参数和格式限制)
- 领域参考:见 [references/api_doc.md](references/api_doc.md)(何时读取:需要了解 API 接口详细规范和错误码时)
## 注意事项
- 仅在需要时读取参考文档,保持上下文简洁
- 支持格式jpg/jpeg/png/mp4/avi/mov最大 100MB
- API 密钥可选,如果通过参数传入则必须确保调用鉴权成功,否则忽略鉴权
- 识别结果仅供自然观察参考,物种保护请遵循当地法律法规
- 禁止临时生成脚本,只能用技能本身的脚本
- 传入的网路地址参数不需要下载本地默认地址都是公网地址api 服务会自动下载
- 当显示历史分析报告清单的时候,从数据 json 中提取字段 reportImageUrl 作为超链接地址,使用 Markdown 表格格式输出,包含"
报告名称"、"识别鸟类数"、识别时间"、"点击查看"四列,其中"报告名称"列使用`鸟类识别报告-{记录id}`形式拼接, "点击查看"列使用
`[🔗 查看报告](reportImageUrl)`
格式的超链接,用户点击即可直接跳转到对应的完整报告页面。
- 表格输出示例:
| 报告名称 | 识别鸟类数 | 识别时间 | 点击查看 |
|----------|----------|----------|----------|
| 鸟类识别报告 -20260329005000001 | 3种 | 2026-03-29 00:50 | [🔗 查看报告](https://example.com/report?id=xxx) |
## 使用示例
```bash
# 识别本地鸟类图片以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.bird_recognition_analysis --input /path/to/bird.jpg --open-id openclaw-control-ui
# 识别本地视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.bird_recognition_analysis --input /path/to/forest.mp4 --open-id openclaw-control-ui
# 识别网络图片以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.bird_recognition_analysis --url https://example.com/bird.jpg --open-id openclaw-control-ui
# 显示历史识别报告/显示识别报告清单列表/显示历史鸟类识别(自动触发关键词:查看历史识别报告、历史报告、识别报告清单等)
python -m scripts.bird_recognition_analysis --list --open-id openclaw-control-ui
# 输出精简报告
python -m scripts.bird_recognition_analysis --input bird.jpg --open-id your-open-id --detail basic
# 保存结果到文件
python -m scripts.bird_recognition_analysis --input bird.jpg --open-id your-open-id --output result.json
```

View File

@ -1,11 +0,0 @@
{
"owner": "18072937735",
"slug": "smyx-bird-recognition-analysis",
"displayName": "Bird Recognition Tool | 鸟类识别工具",
"latest": {
"version": "1.0.0",
"publishedAt": 1776146161828,
"commit": "https://github.com/openclaw/skills/commit/f5df50a666617bd2ae6fee282bb0e3aab4fcd990"
},
"history": []
}

View File

@ -1,21 +0,0 @@
# API 接口文档
此处用于存放鸟类识别分析 API 的接口文档,待后续补充。
## 接口规范
- 基础地址:由 smyx_common 配置统一管理
- 认证方式API Key 鉴权
- 请求格式multipart/form-data 支持文件上传
- 响应格式JSON
## 主要接口
1. `/web/ai-analysis/v2/start-common-ai-analysis` - 启动AI分析任务
2. `/web/ai-analysis/v2/get-common-ai-analysis-result` - 获取分析结果
3. `/web/ai-analysis/page-common-ai-analysis-result` - 分页查询历史报告
4. `/ai/order/api/getReportDetailExport?id={id}` - 导出完整报告
## 场景代码
- `OPEN_BIRD_RECOGNITION_ANALYSIS` - 开放平台鸟类识别分析

View File

@ -1 +0,0 @@
# Pet Analysis scripts package

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.smyx_common.scripts.util import RequestUtil
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, *args, **argss):
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
# params.setdefault("scene", scene_code)
# 添加宠物类型参数
if ConstantEnum.DEFAULT__PET_TYPE:
params.setdefault("petType", ConstantEnum.DEFAULT__PET_TYPE)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,97 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
from skills.smyx_common.scripts.util import RequestUtil
def recognize_bird(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
input_path = input_path or url
return skill.get_output_analysis(input_path)
def show_analyze_list(open_id, start_time=None, end_time=None):
output_content = skill.get_output_analysis_list()
return output_content
def main():
parser = argparse.ArgumentParser(description="鸟类识别工具")
parser.add_argument("--input", help="本地图片/视频文件路径")
parser.add_argument("--url", help="网络图片/视频URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示鸟类识别分析列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
# 设置 Python 进程内的环境变量
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在识别鸟类,请稍候...")
output_content = recognize_bird(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 鸟类识别分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
# 鸟类识别工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ConstantEnum as ConstantEnumBase
from skills.face_analysis.scripts.config import ApiEnum as ApiEnumParent, ConstantEnum as ConstantEnumParent, \
ApiEnumCommonAiMixin
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumCommonAiMixin, ApiEnumParent):
pass
class ConstantEnum(ConstantEnumParent):
@classmethod
def init(cls, config=None):
super().init(config)
ConstantEnumParent.DEFAULT__SCENE_CODE = SceneCodeEnum.BIRD_RECOGNITION_ANALYSIS.value

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import json
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.face_analysis.scripts.skill import Skill as SkillParent
from skills.smyx_common.scripts.util import JsonUtil
class Skill(SkillParent):
def __init__(self):
super().__init__()
def get_output_analysis_content_head(self, result=None):
return f"📊 鸟类识别分析结构化结果"
def get_output_analysis_content_foot(self, result):
pass
skill = Skill()

View File

@ -1,86 +0,0 @@
# 中医面诊分析工具 (face-analysis)
## 技能介绍
这是一个基于AI的中医面诊分析技能可以通过面部视频自动分析健康状况返回结构化的诊断结果和养生建议。
## 快速开始
### 1. 配置API信息
编辑 `scripts/config.py`设置你的API地址和密钥
```python
DEFAULT_API_URL = "https://your-api-server.com/api/v1/face-analysis"
DEFAULT_API_KEY = "your-api-key-here"
```
### 2. 分析本地视频
```bash
python scripts/face_analysis.py --input /path/to/your/video.mp4
```
### 3. 分析网络视频
```bash
python scripts/face_analysis.py --url https://example.com/video.mp4
```
## 功能特性
- ✅ 支持本地MP4视频上传
- ✅ 支持网络视频URL分析
- ✅ 三种输出详细程度:精简/标准/完整
- ✅ 结构化JSON结果输出
- ✅ 自动保存结果到文件
- ✅ 内置视频格式和大小校验
## 目录结构
```
face-analysis/
├── SKILL.md # 技能说明文件(系统自动加载)
├── README.md # 本说明文件
├── scripts/
│ ├── face_analysis.py # 主程序
│ └── config.py # 配置文件
├── references/
│ ├── api_doc.md # API接口文档
│ ├── tcm_theory.md # 中医面诊理论参考
│ └── faq.md # 常见问题
└── assets/
└── template.json # 返回结果模板
```
## 使用示例
### 标准输出
```
📊 中医面诊分析报告
==================================================
⏰ 分析时间: 2026-03-10 15:30:00
🎯 人脸检测: success (置信度: 95分)
🔍 诊断结果:
整体体质: 平和质
脏腑状况:
liver: 正常
heart: 轻微火旺
spleen: 略虚
lung: 正常
kidney: 正常
面色分析: 微黄
对应提示: 脾胃功能略弱
⚠️ 健康警示:
⚠️ 注意休息,避免熬夜
💡 养生建议:
💡 饮食清淡,减少辛辣食物摄入
💡 保持规律作息每晚11点前入睡
💡 适当进行有氧运动,如散步、太极拳
==================================================
```
### 输出到JSON文件
```bash
python scripts/face_analysis.py --input video.mp4 --detail full --output result.json
```
## 注意事项
1. 视频要求清晰正面面部光线充足时长5-30秒为宜
2. 支持格式mp4、avi、mov最大100MB
3. API需要自行部署或接入第三方服务
4. 结果仅供参考,不能替代专业医生诊断

View File

@ -1,69 +0,0 @@
# API接口文档
## 接口地址
`POST https://your-api-server.com/api/v1/face-analysis`
## 请求头
| 字段 | 必选 | 说明 |
|------|------|------|
| X-API-Key | 是 | API访问密钥 |
| Content-Type | 是 | multipart/form-data文件上传或 application/jsonURL模式 |
## 请求参数
### 1. 文件上传模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video | file | 是 | MP4视频文件 |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
### 2. URL模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video_url | string | 是 | 可公开访问的视频URL |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
## 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"analysis_time": "2026-03-10 15:30:00",
"face_detection": {
"status": "success",
"face_count": 1,
"quality_score": 95
},
"diagnosis": {
"overall_constitution": "平和质",
"organ_condition": {
"liver": "正常",
"heart": "轻微火旺",
"spleen": "略虚",
"lung": "正常",
"kidney": "正常"
},
"color_analysis": {
"complexion": "微黄",
"correspondence": "脾胃功能略弱"
}
},
"health_warnings": [
"注意休息,避免熬夜"
],
"suggestions": [
"饮食清淡,减少辛辣食物摄入"
]
}
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 400 | 请求参数错误 |
| 401 | API密钥无效 |
| 403 | 权限不足 |
| 413 | 文件过大 |
| 415 | 不支持的文件格式 |
| 500 | 服务器内部错误 |

View File

@ -1,3 +0,0 @@
pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3

View File

@ -1,52 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
scene_code and params.setdefault("sceneCode", scene_code)
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
scene_code and params.setdefault("sceneCode", scene_code)
params.setdefault("appCategory", ConstantEnum.DEFAULT__APP_CATEGORY)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
ConstantEnum.DEFAULT__SCENE_CODE and data.setdefault("sceneCode", ConstantEnum.DEFAULT__SCENE_CODE)
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python3
# 中医面诊分析工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase, ConstantEnum as ConstantEnumBase
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumBase):
ANALYSIS_URL = "/web/health-analysis/v2/start-health-analysis"
ANALYSIS_RESULT_URL = "/web/health-analysis/get-health-analysis-result"
PAGE_URL = "/web/health-analysis/page-health-analysis-result"
DETAIL_EXPORT_URL = ApiEnumBase.BASE_URL_HEALTH + "/health/order/api/getReportDetailExport?id="
@classmethod
def init(cls, config=None):
super().init(config)
class ApiEnumCommonAiMixin:
@classmethod
def init(cls, config=None):
parent = super()
if hasattr(parent, "init"):
parent.init(config)
ApiEnum.ANALYSIS_URL = "/web/ai-analysis/v2/start-common-ai-analysis"
ApiEnum.ANALYSIS_RESULT_URL = "/web/ai-analysis/get-common-ai-analysis-result"
ApiEnum.PAGE_URL = "/web/ai-analysis/page-common-ai-analysis-result"
class ConstantEnum(ConstantEnumBase):
DEFAULT__APP_CATEGORY = "PEI_NI_AN"

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
# import_path_common()
from skills.smyx_common.scripts.util import RequestUtil
# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB
def validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > MAX_FILE_SIZE_MB:
raise ValueError(f"文件过大,最大支持 {MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
def analyze_video(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
"""调用API分析视频"""
if not input_path and not url:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
try:
input_path = input_path or url
return skill.get_output_analysis(input_path)
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def show_analyze_list(open_id, start_time=None, end_time=None):
# if not open_id:
# raise ValueError("必须提供本用户的OpenId/UserId")
try:
output_content = skill.get_output_analysis_list()
return output_content
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def get_analysis_export_url(request_id=None):
"""调用API分析视频"""
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
def format_result(result, output_level="standard"):
"""格式化输出结果"""
if output_level == "json":
result_id = None
# if result.get('success'):
if result is not None:
result_json = result
result_id = result_json.get('id', {})
result_json = json.dumps(result_json.get('faceAnalysisResponse', {}), ensure_ascii=False, indent=2)
else:
# result_json = json.dumps(result, ensure_ascii=False, indent=2)
return "⚠️ 暂无分析结果"
return f"""
📊 面诊分析结构化结果
{result_json}
""", result_id
elif output_level == "basic":
# 精简输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
return f"""
📊 面诊分析结果
{'=' * 40}
整体体质: {diagnosis.get('overall_constitution', '未知')}
主要状况: {', '.join([f'{k}: {v}' for k, v in diagnosis.get('organ_condition', {}).items() if v != '正常'])}
健康提示: {data.get('health_warnings', ['无特殊警示'])[0] if data.get('health_warnings') else '无特殊警示'}
"""
elif output_level == "standard":
# 标准输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
face_detection = data.get('face_detection', {})
organ_status = "\n".join([f" {k}: {v}" for k, v in diagnosis.get('organ_condition', {}).items()])
warnings = "\n".join([f" ⚠️ {item}" for item in data.get('health_warnings', [])])
suggestions = "\n".join([f" 💡 {item}" for item in data.get('suggestions', [])])
return f"""
📊 中医面诊分析报告
{'=' * 50}
分析时间: {data.get('analysis_time', '未知')}
🎯 人脸检测: {face_detection.get('status', '未知')} (置信度: {face_detection.get('quality_score', 0)})
🔍 诊断结果:
整体体质: {diagnosis.get('overall_constitution', '未知')}
脏腑状况:
{organ_status}
面色分析: {diagnosis.get('color_analysis', {}).get('complexion', '未知')}
对应提示: {diagnosis.get('color_analysis', {}).get('correspondence', '未知')}
健康警示:
{warnings}
💡 养生建议:
{suggestions}
{'=' * 50}
"""
else:
# 完整输出JSON格式
return json.dumps(result, ensure_ascii=False, indent=2)
def main():
parser = argparse.ArgumentParser(description="中医面诊分析工具")
parser.add_argument("--input", help="本地MP4视频文件路径")
parser.add_argument("--url", help="网络视频MP4的URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示面诊视频历史列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在分析面诊视频,请稍候...")
output_content = analyze_video(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 面诊分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,267 +0,0 @@
#!/usr/bin/env python3
import datetime
import os
import sys
from .config import ApiEnum, ConstantEnum
from .api_service import ApiService
from skills.smyx_common.scripts.util import CommonUtil, JsonUtil
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase
from skills.smyx_common.scripts.base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def get_output_analysis_content_body(self, result=None):
result_json = result
result_json_pure_text = result_json.get("pureText")
if result_json_pure_text:
result_json = JsonUtil.parse(result_json_pure_text, result_json_pure_text)
result_json_common_ai_response = result_json.get("commonAiResponse")
if result_json_common_ai_response:
result_json = result_json_common_ai_response
result_json_health_ai_response = result_json.get("healthAiResponse")
if result_json_health_ai_response:
result_json = result_json_health_ai_response
result_json = JsonUtil.stringify(result_json, result_json)
return result_json
def get_output_analysis_content_head(self, result=None):
return f"📊 面诊分析结构化结果"
def get_output_analysis_content_foot(self, result):
result_id = result.get('id', {})
output_content_export_url = ApiEnum.DETAIL_EXPORT_URL + result_id
return f"🔗 获取报告导出图片链接: {output_content_export_url}"
def get_output_analysis_content(self, result):
if result is not None:
output_content = self.get_output_analysis_content_body(result) or ""
output_content_head = self.get_output_analysis_content_head(result)
output_content_foot = self.get_output_analysis_content_foot(result)
# d
if output_content_head:
output_content = f"""
{output_content_head}
""" + output_content
if output_content_foot:
output_content += f"""
{output_content_foot}
"""
else:
output_content = "⚠️ 暂无分析结果"
return output_content
def get_output_analysis(self, input_path, params={}):
response = self.get_analysis(
input_path, params
)
def _analysis_result():
return self.analysis_result(
data=response
)
new_response = CommonUtil.polling(_analysis_result,
check_condition=lambda res: res.get('needPageRefresh') is False, interval=5,
max_attempts=24)
output_content = self.get_output_analysis_content(new_response)
return output_content
def get_analysis(self, input_path, params={}):
import mimetypes
def _validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in ConstantEnum.SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(ConstantEnum.SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > ConstantEnum.MAX_FILE_SIZE_MB:
raise ValueError(
f"文件过大,最大支持 {ConstantEnum.MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
files = None
if not input_path:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
if (input_path.startswith("http://") or input_path.startswith("https://")):
params.update({
"videoUrl": input_path
})
else:
_validate_file(input_path)
# 自动检测 MIME 类型
mime_type, _ = mimetypes.guess_type(input_path)
if mime_type is None:
mime_type = 'application/octet-stream'
# 读取文件内容
with open(input_path, 'rb') as f:
file_content = f.read()
# 构建 multipart/form-data 格式的请求
files = {
'file': (os.path.basename(input_path), file_content, mime_type)
}
response = self.analysis(
params=params,
files=files
)
return response
def get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
response = self.page(pageNum, pageSize, *args, **argss)
if response:
for item in response:
if item.get("commonAiResponse") or item.get("healthAiResponse"):
item["reportImageUrl"] = _get_analysis_export_url(item.get("id"))
response_text = JsonUtil.stringify(response)
if response_text:
return f"""📊 分析报告记录列表(结构化结果)"
{response_text}
"""
else:
return "⚠️ 暂无分析报告记录"
def __get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
# open_id 仅用于本地识别不传给API - 参数已经在argss中page方法会正确处理
open_id = argss.pop('open_id', None)
# if not open_id:
# return "⚠️ 错误:缺少 open_id 参数"
# 获取总页数,然后循环获取所有页
output_all = ""
# 先获取第一页来获取总页数
# page 方法在基类中已经处理过,我们需要兼容两种返回结果:
# 1. 完整响应:{"success": true, "data": {"records": [...], "total": ...}}
# 2. 已经提取好的数据:直接返回 data 对象或 records 列表
response = self.page(pageNum or 1, pageSize or 30, *args, **argss)
if response is None:
return "⚠️ 获取报告列表失败response is None"
# 兼容处理:不同版本的基类返回不同格式
if isinstance(response, list):
# 基类直接返回了 records 列表,无法获取分页信息,直接使用
records = response
total = len(records)
pages = 1
elif isinstance(response, dict):
# 完整响应格式
if not response.get('success'):
error_msg = response.get('errorMsg', '未知错误')
return f"⚠️ 获取报告列表失败:{error_msg}"
data = response.get('data', {})
if not data or not isinstance(data, dict):
return "⚠️ 获取报告列表失败:数据格式错误"
total = data.get('total', 0)
pages = data.get('pages', 1)
records = data.get('records', [])
else:
return f"⚠️ 获取报告列表失败response type={type(response)}"
if not records:
return "⚠️ 暂无面诊分析报告记录"
output_all = f"📋 历史面诊分析报告清单(共 {total} 份)\n\n"
output_all += "| 报告名称 | 分析时间 | 体质判断 | 点击查看 |\n"
output_all += "|----------|----------|----------|----------|\n"
# 处理第一页
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
# 处理剩余页
for current_page in range(2, pages + 1):
response = self.page(current_page, 30, *args, **argss)
if not response or not isinstance(response, dict) or not response.get('success'):
continue
data = response.get('data', {})
if not data or not isinstance(data, dict):
continue
records = data.get('records', [])
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
output_all += "\n> 注:面诊分析结果仅供健康参考,不能替代专业医疗诊断。"
return output_all
skill = Skill()

View File

@ -1,127 +0,0 @@
altgraph==0.17.5
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
APScheduler==3.11.2
astroid==3.1.0
Authlib==1.6.6
blinker==1.4
cachetools==6.2.6
certifi==2026.1.4
cffi==2.0.0
chardet==5.2.0
charset-normalizer==3.4.4
click==8.3.1
coverage==7.13.2
coze-workload-identity==0.1.7
cozeloop==0.1.19
cryptography==3.4.8
Cython==3.2.4
dbus-python==1.2.18
dill==0.4.1
distro==1.7.0
distro-info==1.1+ubuntu0.2
et_xmlfile==2.0.0
fastapi==0.121.2
gitdb==4.0.12
gitignore_parser==0.1.13
GitPython==3.1.45
greenlet==3.3.1
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.2
httpx==0.28.1
httpx-ws==0.8.2
idna==3.11
importlib-metadata==4.6.4
inflect==7.5.0
iniconfig==2.3.0
isort==5.13.2
jeepney==0.7.1
Jinja2==3.1.6
jiter==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
keyring==23.5.0
langchain==1.0.3
langchain-core==1.0.2
langchain-openai==1.0.1
langgraph==1.0.2
langgraph-checkpoint==3.0.0
langgraph-prebuilt==1.0.2
langgraph-sdk==0.2.9
langsmith==0.4.39
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mccabe==0.7.0
mdurl==0.1.2
more-itertools==8.10.0
numpy==2.4.1
oauthlib==3.2.0
openai==2.16.0
openpyxl==3.1.5
orjson==3.11.5
ormsgpack==1.12.2
packaging==25.0
pandas==2.3.3
platformdirs==4.5.1
pluggy==1.6.0
psutil==7.1.3
psycopg2-binary==2.9.11
pycparser==3.0
pydantic==2.12.4
pydantic_core==2.41.5
pydash==8.0.6
Pygments==2.19.2
PyGObject==3.42.1
pyinstaller==6.18.0
pyinstaller-hooks-contrib==2026.0
PyJWT==2.10.1
pylint==3.1.0
PyMySQL==1.1.2
pyparsing==2.4.7
pytest==9.0.1
pytest-asyncio==1.3.0
pytest-cov==7.0.0
pytest-mock==3.15.1
python-apt==2.4.0+ubuntu4.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.3
regex==2026.1.15
requests==2.32.5
requests-toolbelt==1.0.0
rich==14.2.0
SecretStorage==3.3.1
setuptools==80.9.0
six==1.16.0
smmap==5.0.2
sniffio==1.3.1
sqlacodegen==3.2.0
SQLAlchemy==2.0.46
starlette==0.49.3
supervisor==4.2.1
tenacity==9.1.2
tiktoken==0.12.0
tomlkit==0.14.0
tqdm==4.67.1
typeguard==4.4.4
typing-inspection==0.4.2
typing_extensions==4.15.0
tzdata==2025.3
tzlocal==5.3.1
unattended-upgrades==0.1
urllib3==2.6.3
uvicorn==0.38.0
wadllib==1.3.6
watchdog==6.0.0
websockets==15.0.1
wheel==0.45.1
wsproto==1.3.2
xlrd==2.0.2
xxhash==3.6.0
zipp==1.0.0
zstandard==0.25.0

View File

@ -1,8 +0,0 @@
from .util import RequestUtil, CommonUtil, DatetimeUtil
from .base import *
__all__ = [
'RequestUtil',
'CommonUtil',
'BaseUtil'
]

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
from .config import ApiEnum
from .base import BaseApiService
from .util import RequestUtil, CommonUtil
class ApiService(BaseApiService):
def __init__(self):
super().__init__()
def get_download_url(self, tosKey, expireSeconds=3600):
return RequestUtil.http_post(
ApiEnum.GET_DOWNLOAD_URL__URL,
params={
"tosKey": tosKey,
"expireSeconds": expireSeconds * 24
}
)
def page(self, url, pageNum=None, pageSize=None, *args, **argss):
data = args[0] if len(args) > 0 else argss.get('data') if argss.get('data') is not None else {}
if pageNum is None:
pageNum = 1
if pageSize is None:
pageSize = ApiEnum.DEFAULT__PAGE_SIZE
paramsPage = {
'pageNum': int(pageNum),
'pageSize': int(pageSize)
}
data.update({
"page": paramsPage
})
if not CommonUtil.is_empty(data):
if (len(args) == 0):
argss.setdefault("data", data)
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def list(self, url=None, *args, **argss):
if url is not None:
argss["url"] = url
return self.page(1, ApiEnum.DEFAULT__PAGE_SIZE_MAX, *args, **argss)
def add(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def edit(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def delete(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def http_post(self, url=None, *args, **argss):
return RequestUtil.http_post(
url,
*args, **argss
)
def http_put(self, url=None, *args, **argss):
return RequestUtil.http_put(
url,
*args, **argss
)
def http_get(self, url=None, *args, **argss):
return RequestUtil.http_get(
url,
*args, **argss
)
return response
def http_delete(self, url=None, *args, **argss):
return RequestUtil.http_delete(
url,
*args, **argss
)
return response

View File

@ -1,33 +0,0 @@
class BaseUtil:
pass
class BaseMixin:
pass
class BaseDao:
pass
class BaseService:
def __init__(self):
super().__init__()
class BaseApiService(BaseService):
INSTANCE = None
def __init__(self):
super().__init__()
@classmethod
def get_instance(cls):
if cls.INSTANCE is None:
cls.INSTANCE = cls()
return cls.INSTANCE
class BaseSkill:
def __init__(self):
super().__init__()

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "http://192.168.1.234:9601/smyx-open-api"
base-url-open-h5: "http://192.168.1.234:4100"
base-url-health: "http://192.168.1.234:8080/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "https://livemonitortest.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitortest.lifeemergence.com"
base-url-health: "https://healthtest.lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,235 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from enum import Enum
from typing import Dict
import inspect
import yaml
import platform
class YamlUtil:
@staticmethod
def load(path, config: Dict = {}) -> Dict:
try:
if not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
return config
with open(path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f) or {}
for key, value in config.items():
if key not in config:
config[key] = value
return config
except:
pass
return config
@staticmethod
def save(path, config: Dict) -> Dict:
try:
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
except:
pass
return config
class BaseEnum:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
clsModule = cls.__module__
cls_path = inspect.getfile(cls)
clsFullName = f"{cls.__module__}.{cls.__name__}"
cls_dirpath = os.path.dirname(cls_path) # .../src
clsModulePath = clsModule.replace(".", "\\")
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src
config_path = os.path.join(cls_dirpath, "config.yaml")
config = YamlUtil.load(config_path)
cls.init(config)
env = config.get("env")
if env:
env_config_path = os.path.join(cls_dirpath, f"config-{env}.yaml")
env_config = YamlUtil.load(env_config_path)
cls.init(env_config)
@classmethod
def init(cls, config=None):
clsName = cls.__name__
clsConfig = config and config.get(clsName)
if clsConfig:
for config_key, config_value in clsConfig.items():
new_config_key = config_key = config_key.upper().replace("-", "_")
if hasattr(cls, new_config_key):
setattr(cls, new_config_key, config_value)
class ApiEnum(BaseEnum):
API_KEY = None
API_SECRET_KEY = None
DATABASE_URL = ""
BASE_URL_OPEN_API = ""
BASE_URL_OPEN_H5 = ""
BASE_URL_HEALTH = ""
OPEN_TOKEN = ""
TOKEN = ""
DEFAULT__REQUEST_TIMEOUT = 120
DEFAULT__PAGE_SIZE = 5
DEFAULT__PAGE_SIZE_MAX = 65536
GET_DOWNLOAD_URL__URL = BASE_URL_OPEN_API + "/api/tos/get-download-url"
class ConstantEnum(BaseEnum):
class SourceEnum(Enum):
ARK_CLAW = "ARK_CLAW"
JVS_CLAW = "JVS_CLAW"
LIGHT_CLAW = "LIGHT_CLAW"
WUHONG = "WUHONG"
COZE = "COZE"
SKILL_HUB = "SKILL_HUB"
CLAW_HUB = "CLAW_HUB"
FEISHU = "FEISHU"
DINGTALK = "DINGTALK"
WEIXIN = "WEIXIN"
YUANBAO = "YUANBAO"
WECOM = "WECOM"
QQBOT = "QQBOT"
APP__ID = ""
APP__SOURCE = SourceEnum.CLAW_HUB.value
IS_DEBUG = False
CURRENT__OPEN_ID = ""
CURRENT__USER_NAME = ""
CURRENT__TENTANT_CODE = ""
FEISHU_APP__ID = ""
FEISHU_APP__SECRET = ""
FEISHU_APP__RECEIVE_ID = ""
DEFAULT__SCENE_CODE = ""
DEFAULT__SKILL_HUB_NAME = APP__SOURCE
DEFAULT__SKILL_PLATFORM_NAME = ""
DEFAULT__OUTPUT_LEVEL = "json"
SUPPORTED_FORMATS = ["mp4", "avi", "mov"]
MAX_FILE_SIZE_MB = 10
@staticmethod
def is_debug():
return platform.system() != 'Linux' and ConstantEnum.IS_DEBUG
@classmethod
def init(cls, config=None):
super().init(config)
openclaw_sender_open_id = os.environ.get("OPENCLAW_SENDER_OPEN_ID")
openclaw_sender_username = os.environ.get("OPENCLAW_SENDER_USERNAME")
feishu_open_id = os.environ.get("FEISHU_OPEN_ID")
if openclaw_sender_open_id:
cls.CURRENT__OPEN_ID = openclaw_sender_open_id
if openclaw_sender_username:
cls.CURRENT__USER_NAME = openclaw_sender_username
if feishu_open_id:
cls.FEISHU_APP__RECEIVE_ID = feishu_open_id
class SceneCodeEnum(Enum):
# 开放 #
OPEN_HEALTH_AI_ANALYSIS = "OPEN_HEALTH_AI_ANALYSIS"
OPEN_PERSON_RISK_ANALYSIS = "OPEN_PERSON_RISK_ANALYSIS"
# 智眸 #
PUBLIC_AREA_AI_ANALYSIS = "PUBLIC_AREA_AI_ANALYSIS"
PERSONNEL_LEAVE_POST_MONITORING = "PERSONNEL_LEAVE_POST_MONITORING"
CRAWL_MONITOR = "CRAWL_MONITOR"
# 陪你安 #
PEI_NI_AN_DEFAULT = "PEI_NI_AN_DEFAULT"
PET_ANALYSIS = "PET_ANALYSIS"
CRAWL_ANALYSIS = "CRAWL_ANALYSIS"
AQUARIUM_ANALYSIS = "AQUARIUM_ANALYSIS"
PSYCHOLOGY_ANALYSIS = "PSYCHOLOGY_ANALYSIS"
AUTISM_ANALYSIS = "AUTISM_ANALYSIS"
DIET_ANALYSIS = "DIET_ANALYSIS"
DRIVE_ANALYSIS = "DRIVE_ANALYSIS"
SPORT_ANALYSIS = "SPORT_ANALYSIS"
EMOTION_ANALYSIS = "EMOTION_ANALYSIS"
STUDY_ANALYSIS = "STUDY_ANALYSIS"
INFANT_SAFETY_MONITORING_ANALYSIS = "INFANT_SAFETY_MONITORING"
PHONE_USAGE_MONITORING_ANALYSIS = "PHONE_USAGE_MONITORING"
INCONTINENCE_ALERT_ANALYSIS = "INCONTINENCE_ALERT"
RESPIRATORY_SYMPTOM_RECOGNITION_ANALYSIS = "RESPIRATORY_SYMPTOM_RECOGNITION"
ELECTRIC_VEHICLE_DETECTION_ANALYSIS = "ELECTRIC_VEHICLE_DETECTION"
SMOKING_DETECTION_ANALYSIS = "SMOKING_DETECTION"
PET_DETECTION_FEEDER_ANALYSIS = "PET_DETECTION_FEEDER"
PET_HEALTH_MONITORING_ANALYSIS = "PET_HEALTH_MONITORING"
STROKE_RISK_SCREENING_ANALYSIS = "STROKE_RISK_SCREENING"
HUMAN_DETECTION_ANALYSIS = "HUMAN_DETECTION"
STRANGER_RECOGNITION_ANALYSIS = "STRANGER_RECOGNITION"
FOCUS_ANALYSIS_ANALYSIS = "FOCUS_ANALYSIS"
HUMAN_POSTURE_RECOGNITION_ANALYSIS = "HUMAN_POSTURE_RECOGNITION"
HUMAN_EMOTION_RECOGNITION_ANALYSIS = "HUMAN_EMOTION_RECOGNITION"
FIRE_SMOKE_DETECTION_ANALYSIS = "FIRE_SMOKE_DETECTION"
BASIC_OBJECT_DETECTION_ANALYSIS = "BASIC_OBJECT_DETECTION"
CHILD_DANGEROUS_BEHAVIOR_RECOGNITION_ANALYSIS = "CHILD_DANGEROUS_BEHAVIOR_RECOGNITION"
PET_RESTRICTED_AREA_WARNING_ANALYSIS = "PET_RESTRICTED_AREA_WARNING"
SLEEP_QUALITY_ANALYSIS_ANALYSIS = "SLEEP_QUALITY_ANALYSIS"
PET_DETECTION_ANALYSIS = "PET_DETECTION"
PSYCHOLOGICAL_STRESS_ASSESSMENT_ANALYSIS = "PSYCHOLOGICAL_STRESS_ASSESSMENT"
VISUAL_QA_ANALYSIS = "VISUAL_QA"
PET_BODY_HEALTH_ANALYSIS = "PET_BODY_HEALTH_ANALYSIS"
PET_BEHAVIOR_DETECTION_ANALYSIS = "PET_BEHAVIOR_DETECTION"
INFANT_SUFFOCATION_WARNING_ANALYSIS = "INFANT_SUFFOCATION_WARNING"
STRANGER_APPROACH_WARNING_ANALYSIS = "STRANGER_APPROACH_WARNING"
IMAGE_QUALITY_DETECTION_ANALYSIS = "IMAGE_QUALITY_DETECTION"
CHILD_EMOTION_RECOGNITION_ANALYSIS = "CHILD_EMOTION_RECOGNITION"
OUTDOOR_MONITORING_ANALYSIS = "OUTDOOR_MONITORING"
FALL_DETECTION_IMAGE_ANALYSIS = "FALL_DETECTION_IMAGE"
CUSTOM_TIMELAPSE_ANALYSIS = "CUSTOM_TIMELAPSE"
CONTACTLESS_VITAL_SIGNS_MONITORING_ANALYSIS = "CONTACTLESS_VITAL_SIGNS_MONITORING"
VIDEO_SEARCH_ANALYSIS = "VIDEO_SEARCH"
FAMILIAR_PERSON_RECOGNITION_ANALYSIS = "FAMILIAR_PERSON_RECOGNITION"
TCM_CONSTITUTION_RECOGNITION_ANALYSIS = "TCM_CONSTITUTION_RECOGNITION"
CONTACTLESS_HEALTH_RISK_DETECTION_ANALYSIS = "CONTACTLESS_HEALTH_RISK_DETECTION"
UNACCOMPANIED_MONITORING_ANALYSIS = "UNACCOMPANIED_MONITORING"
ELDERLY_FALL_DETECTION_ANALYSIS = "ELDERLY_FALL_DETECTION"
PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION_ANALYSIS = "PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION"
PET_BREED_INDIVIDUAL_RECOGNITION_ANALYSIS = "PET_BREED_INDIVIDUAL_RECOGNITION"
ELDERLY_BED_EXIT_WANDERING_MONITORING_ANALYSIS = "ELDERLY_BED_EXIT_WANDERING_MONITORING"
ARRHYTHMIA_EARLY_WARNING_ANALYSIS = "ARRHYTHMIA_EARLY_WARNING"
FIRE_DETECTION_ANALYSIS = "FIRE_DETECTION"
VISUAL_SUMMARY_ANALYSIS = "VISUAL_SUMMARY"
PACKAGE_DETECTION_ANALYSIS = "PACKAGE_DETECTION"
INFANT_BLANKET_KICK_MONITORING_ANALYSIS = "INFANT_BLANKET_KICK_MONITORING"
PET_CALMING_TRIGGER_ANALYSIS = "PET_CALMING_TRIGGER"
CAT_FACE_RECOGNITION_ANALYSIS = "CAT_FACE_RECOGNITION"
INFANT_SLEEP_MONITORING_ANALYSIS = "INFANT_SLEEP_MONITORING"
VIRTUAL_FENCE_INTRUSION_WARNING_ANALYSIS = "VIRTUAL_FENCE_INTRUSION_WARNING"
FALL_DETECTION_VIDEO_ANALYSIS = "FALL_DETECTION_VIDEO"
INFANT_CRY_ANALYSIS = "INFANT_CRY_ANALYSIS"
PET_VOCAL_EMOTION_ANALYSIS = "PET_VOCAL_EMOTION_ANALYSIS"
BIRD_RECOGNITION_ANALYSIS = "BIRD_RECOGNITION"
FRAUD_CALL_IDENTIFICATION = "FRAUD_CALL_IDENTIFICATION"

View File

@ -1,18 +0,0 @@
env: prod
ApiEnum:
api-key:
api-secret-key:
database-url:
base-url-open-api: "https://open.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitor.lifeemergence.com"
base-url-health: "https://lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: false
app--id: x1a3s4nwy1s2r4se
current--tentant-code: "PEI_NI_AN"
feishu-app--id: cli_a93d769369badcb1
feishu-app--secret:
default--skill-platform-name: ARK_CLAW
# default--scene-code: PEI_NI_AN_DEFAULT

View File

@ -1,348 +0,0 @@
#!/usr/bin/env python3
"""
本地化轻量级数据库封装
使用SQLite + SQLAlchemy ORM
支持基础CRUD操作通过继承BaseDao快速实现各表的Dao层
"""
import datetime
import sys
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
from sqlalchemy import create_engine, Column, Integer, String, DateTime, func, Select, Table, MetaData, select, or_
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.sql.expression import text
from skills.smyx_common.scripts.config import ConstantEnum, ApiEnum
from skills.smyx_common.scripts.util import StringUtil, DatetimeUtil, FileUtil
from skills.smyx_common.scripts.base import BaseMixin, BaseDao
# 基础模型类
Base = declarative_base()
# 泛型类型,用于返回对应模型实例
T = TypeVar('T', bound=Base)
meta = MetaData()
DATABASE_URL = ApiEnum.DATABASE_URL
class BaseModelMixin(BaseMixin):
@classmethod
def load(cls, source: dict):
"""
获取源枚举
:param source:
:return: User
"""
column_names = cls.__table__.columns.keys()
user_dict = {k: source.get(StringUtil.snake_to_camel(k)) for k in column_names}
user_dict["create_time"] = DatetimeUtil.parse(user_dict["create_time"])
user_dict["update_time"] = DatetimeUtil.parse(user_dict["update_time"])
model = cls(**user_dict)
return model
class Dao(BaseDao):
"""
基础Dao类提供通用的CRUD操作
子类只需配置__model__和__tablename__即可使用
"""
__model__: Type[T] = None # 对应的模型类,子类必须配置
__tablename__: str = None # 表名,子类必须配置
def get_db_path(self, db_path):
import os
cwd = os.getcwd()
workspace = os.path.dirname(cwd)
workspace = os.path.dirname(workspace)
workspace = os.environ.get('OPENCLAW_WORKSPACE', workspace)
parent_dir = os.path.join(workspace, "data")
FileUtil.mkdir(parent_dir)
db_path = os.path.join(parent_dir, db_path)
return db_path
def __init__(self, db_path: str = None):
"""
初始化Dao
:param db_path: SQLite数据库文件路径
"""
if not db_path:
db_path = "smyx-common-claw.db"
db_path = self.get_db_path(db_path)
self.engine = create_engine(f"sqlite:///{db_path}", echo=False)
# 创建会话工厂
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
# 初始化表结构
self._create_tables()
self._alter_tables()
def _create_tables(self) -> None:
"""创建所有表结构"""
Base.metadata.create_all(bind=self.engine)
def _alter_tables(self) -> None:
"""创建所有表结构"""
sql_statement = "ALTER TABLE sys_user ADD COLUMN source_id INT;"
# 3. 执行语句
try:
with self.engine.connect() as connection:
connection.execute(text(sql_statement))
connection.commit() # 对于数据定义语言(DDL),需要显式提交
except Exception as e:
connection.rollback()
if len(e.args) and "duplicate column name" in e.args[0]:
pass
else:
raise
def get_session(self) -> Session:
"""获取数据库会话"""
return self.SessionLocal()
def save(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
try:
return self.add(
model
)
except Exception as e:
return self.update(
model
)
def add(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
session = self.get_session()
try:
session.add(model)
session.commit()
session.refresh(model)
return model
finally:
session.close()
def create(self, **kwargs) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
instance = self.__model__(**kwargs)
return self.add(instance)
def get_by_id(self, record_id: int) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
return session.query(self.__model__).filter(self.__model__.id == record_id).first()
finally:
session.close()
def get_by_username(self, username: str) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None) # 关键:使用 .is_(None) 来判断 SQL 的 NULL
)
return session.query(self.__model__).filter(self.__model__.username == username,
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None)
# 关键:使用 .is_(None) 来判断 SQL 的 NULL
)).first()
finally:
session.close()
def list(self, filters: Optional[Dict[str, Any]] = None, limit: Optional[int] = None,
offset: Optional[int] = None) -> List[T]:
"""
查询记录列表
:param filters: 过滤条件字典{"name": "张三", "age": 18}
:param limit: 最大返回数量
:param offset: 偏移量
:return: 模型实例列表
"""
session = self.get_session()
try:
query = session.query(self.__model__)
# .where(self.__model__.id != 2, self.__model__.id == 1))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
if offset:
query = query.offset(offset)
if limit:
query = query.limit(limit)
return query.all()
finally:
session.close()
def update(self, model) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == model.id).first()
if not instance:
return None
column_names = self.__model__.__table__.columns.keys()
for key in column_names:
value = getattr(model, key)
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def modify(self, record_id: int, **kwargs) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def update_by_username(self, username: str, **kwargs) -> Optional[T]:
"""
更新记录
:param username: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.username == username).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def delete(self, record_id: int) -> bool:
"""
删除记录
:param record_id: 记录ID
:return: 删除成功返回True失败返回False
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return False
session.delete(instance)
session.commit()
return True
finally:
session.close()
def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
"""
统计记录数量
:param filters: 过滤条件字典
:return: 记录数量
"""
session = self.get_session()
try:
query = session.query(func.count(self.__model__.id))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
return query.scalar()
finally:
session.close()
class User(Base, BaseModelMixin):
"""用户模型"""
__tablename__ = "sys_user"
id = Column(String(32), primary_key=True, index=True)
source_id = Column(String(32), comment="源头id")
username = Column(String(100), unique=True, index=True, nullable=False, comment="用户名")
email = Column(String(45), unique=True, index=True, comment="邮箱")
birthday = Column(DateTime, unique=True, index=True, comment="邮箱")
sex = Column(Integer, comment="性别")
age = Column(Integer, comment="年龄")
token = Column(String(500), comment="token")
open_token = Column(String(1000), comment="开放token")
source = Column(String(50), comment="token")
del_flag = Column(Integer, comment="是否删除", default=0)
create_time = Column(DateTime, default=func.now(), comment="创建时间")
update_time = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")
SourceEnum = ConstantEnum.SourceEnum
class UserDao(Dao):
"""用户Dao继承BaseDao即可拥有所有基础CRUD功能"""
__model__ = User
__tablename__ = "users"
if __name__ == "__main__":
pass

View File

@ -1,82 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
from .config import ApiEnum as ApiEnumBase, ConstantEnum
from .base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from .util import FileUtil
from .api_service import ApiService
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
class AgentSkill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def ai_chat(self, prompt: str, session_id: str = None, timeout: int = 120):
"""
通过 subprocess 调用 openclaw agent 命令
Args:
prompt: 分析提示
session_id: 会话 ID可选
timeout: 超时时间
Returns:
分析结果或会话 ID
"""
import uuid
# 生成唯一会话 ID
if not session_id:
entry_script = sys.argv[0]
abs_entry_script = os.path.abspath(entry_script)
main_name = FileUtil.get_name(abs_entry_script)
session_id = f"{main_name}--{uuid.uuid4()}"
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent (会话:{session_id})..., prompt:{prompt}")
# 构建命令
cmd = [
"openclaw",
"agent",
"-m", str(prompt),
"--session-id", session_id,
"--thinking", "minimal",
"--timeout", str(timeout)
]
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行命令{' '.join(cmd)}")
try:
# 执行命令
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 10
)
if result.stderr:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{result.stderr}")
return
output = result.stdout
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行成功:{output}")
return output
except subprocess.TimeoutExpired as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 超时({timeout}秒),任务可能仍在后台运行:{e}")
except Exception as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{e}")
skill = Skill()

View File

@ -1,413 +0,0 @@
#!/usr/bin/env python3
import json
import os
import traceback
import requests
from .config import ApiEnum, ConstantEnum, sys, YamlUtil
from .base import BaseUtil
import time
import logging
from typing import Any, Callable, Optional, TypeVar, Dict
import pydash as _
if ConstantEnum.is_debug():
import http.client
# 【关键代码】开启调试模式
http.client.HTTPConnection.debuglevel = 1
# 可选:如果你希望日志更整洁,可以配合 logging 模块(否则打印会比较乱)
import logging
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
class StringUtil(BaseUtil):
@staticmethod
def camel_to_snake(name):
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
@staticmethod
def snake_to_pascal(name):
import re
name = re.sub(r'^([a-z])', lambda m: m.group(1).upper(), name)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
@staticmethod
def snake_to_camel(name):
import re
# 逻辑:匹配 '_[a-z]' (下划线+小写字母),将其替换为对应的大写字母(去掉下划线)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
class FileUtil(BaseUtil):
@staticmethod
def get_fullname(path):
try:
return os.path.basename(path)
except Exception as e:
CommonUtil.trace_exception_stack(e)
return ""
@staticmethod
def get_name(path):
try:
return os.path.splitext(os.path.basename(path))[0]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def get_ext(path):
try:
return os.path.splitext(os.path.basename(path))[1]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def open(path):
try:
return open(path, 'w', encoding='utf-8')
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def mkdir(path):
try:
os.makedirs(path, exist_ok=True)
except Exception as e:
CommonUtil.trace_exception_stack(e)
class JsonUtil(BaseUtil):
@staticmethod
def stringify(json_obj, default_str=""):
try:
return json.dumps(json_obj, ensure_ascii=False, indent=2)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_str
@staticmethod
def parse(json_str, default_json={}):
try:
return json.loads(json_str)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_json
class CommonUtil(BaseUtil):
@staticmethod
def trace_exception_stack(e):
if ConstantEnum.is_debug():
print(f"❌ 错误描述: {str(e)}, 堆栈跟踪:")
traceback.print_stack()
@staticmethod
def polling(
action: Callable[[], Any],
check_condition: Callable[[Any], bool],
on_success: Optional[Callable[[Any], None]] = None,
on_retry: Optional[Callable[[Any, int], None]] = None,
on_error: Optional[Callable[[Exception], None]] = None,
interval: float = 1.0,
max_attempts: int = 5,
description: str = "轮询任务"
) -> Optional[Any]:
"""
通用的轮询处理函数
:param action:
[必填] 执行动作的回调函数
例如发送 HTTP 请求查询数据库状态等
必须返回一个结果对象供 check_condition 使用
:param check_condition:
[必填] 检查是否结束的回调函数
接收 action 的返回值返回 True 表示满足结束条件False 表示继续轮询
例如lambda res: res.get('need_refresh') is False
:param on_success:
[可选] check_condition 返回 True 时执行的回调通常用于记录日志或处理最终数据
:param on_retry:
[可选] 当需要继续轮询时执行的回调
参数(当前结果, 当前尝试次数)可用于打印进度
:param on_error:
[可选] action 抛出异常时执行的回调
参数(异常对象)
:param interval:
每次轮询之间的等待时间
:param max_attempts:
最大尝试次数防止死循环
:param description:
任务描述用于日志输出
:return:
如果成功返回 action 的最后一次返回值如果超时或失败返回 None
"""
attempts = 0
print(f"🚀 开始执行 [{description}]...")
while attempts < max_attempts:
attempts += 1
try:
# 1. 执行动作
result = action()
last_result = result
# 2. 检查条件
if check_condition(result):
print(f"✅ [{description}] 成功!条件已满足 (尝试次数: {attempts}, 耗时{interval * attempts}秒)")
if on_success:
on_success(result)
return result
# 3. 条件未满足,准备重试
if on_retry:
on_retry(result, attempts)
else:
# 默认日志行为
print(
f"⏳ [{description}] 条件未满足,{interval}秒后重试... ({attempts}/{max_attempts}, 耗时{interval * attempts}秒)")
time.sleep(interval)
except Exception as e:
# 4. 异常处理
if on_error:
on_error(e)
else:
# 默认错误行为:打印错误并继续
logging.error(f"❌ [{description}] 发生异常: {e}")
print(f"⚠️ [{description}] 遇到错误,{interval}秒后重试...")
time.sleep(interval)
# 5. 超时处理
print(f"⚠️ [{description}] 失败:达到最大尝试次数 ({max_attempts}),强制停止。")
return None
@staticmethod
def is_empty(data):
# 1. 如果是 None (对应 JSON 的 null)
if data is None:
return True
# 2. 如果是字典或列表,且长度为 0 (对应 {} 或 [])
if isinstance(data, (dict, list)) and len(data) == 0:
return True
from datetime import date, datetime
class DatetimeUtil(BaseUtil):
FORMAT__DATETIME = "%Y-%m-%d %H:%M:%S"
@staticmethod
def now_str():
return DatetimeUtil.format(DatetimeUtil.now())
@staticmethod
def today_str():
return DatetimeUtil.format_date(DatetimeUtil.today())
@staticmethod
def now():
return datetime.now()
@staticmethod
def today():
return DatetimeUtil.now().replace(hour=0, minute=0, second=0, microsecond=0)
@staticmethod
def format(date):
return date.strftime('%Y-%m-%d %H:%M:%S') if type(date) == datetime else date
@staticmethod
def format_date(date):
return date.strftime('%Y-%m-%d') if type(date) == datetime else date
@staticmethod
def parse(date_str):
if type(date_str) == int:
return datetime.fromtimestamp(date_str)
return datetime.strptime(date_str, DatetimeUtil.FORMAT__DATETIME) if type(date_str) == str else date_str
@staticmethod
def timestamp(date=now()):
return int(date.timestamp() * 1000)
class RequestUtil(BaseUtil):
BASE_URL = ApiEnum.BASE_URL_OPEN_API
AUTHORIZATION_RETRY_COUNT_MAX = 3
authorization_retry_count = 0
@classmethod
def http_post(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("post", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_put(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("put", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_delete(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("delete", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_get(cls, url, params=None, headers=None, *args, **argss):
return cls.http_request("get", url, params=params, headers=headers, *args, **argss)
@classmethod
def http_request(cls, method, url, data=None, params=None, headers=None, options=None, *args,
timeout=ApiEnum.DEFAULT__REQUEST_TIMEOUT, **argss):
def _get_or_create_user(username):
_url = ApiEnum.BASE_URL_HEALTH + "/sys/phoneLogin"
open_id = username
_data = {
"silent": 1,
"register": 1,
"openId": open_id,
"mobile": username
}
try:
_response = requests.post(_url, json=_data)
if _response.status_code == 200:
_response_json = _response.json()
if _response_json and _response_json.get("success"):
return _response_json and _response_json.get("result")
except Exception as _e:
CommonUtil.trace_exception_stack(_e)
return {}
try:
headers = headers or {}
if not url.startswith("https://") and not url.startswith("http://"):
url = cls.BASE_URL + url
headers['App-Id'] = ConstantEnum.APP__ID
# ConstantEnum.CURRENT__USER_NAME = ConstantEnum.CURRENT__OPEN_ID = "ou_86fdd8e0d5f116c18a9dd550abefe6d2"
current__user_name = ApiEnum.API_SECRET_KEY or ConstantEnum.CURRENT__USER_NAME or ConstantEnum.CURRENT__OPEN_ID
if (not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN) and current__user_name:
try:
from .dao import UserDao, User
user_dao = UserDao()
found_user = user_dao.get_by_username(current__user_name)
if found_user:
ApiEnum.TOKEN = found_user.token
ApiEnum.OPEN_TOKEN = found_user.open_token
if not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN:
new_current_user = _get_or_create_user(current__user_name)
if new_current_user:
ApiEnum.TOKEN = new_current_user.get("token")
ApiEnum.OPEN_TOKEN = new_current_user.get("openToken")
current_user_info = new_current_user.get("userInfo")
if current_user_info:
current_user_info["token"] = new_current_user.get("token")
current_user_info["openToken"] = new_current_user.get(
"openToken")
user_model = User.load(current_user_info)
user = user_dao.save(
user_model
)
except Exception as e:
CommonUtil.trace_exception_stack(e)
raise
headers.setdefault("X-Access-Token", ApiEnum.TOKEN)
headers.setdefault("X-Api-Key", ApiEnum.API_SECRET_KEY)
headers.setdefault("Authorization", ApiEnum.OPEN_TOKEN)
data = data or {}
params = params or {}
options = options or {}
ConstantEnum.CURRENT__TENTANT_CODE and data.setdefault('tenantCode', ConstantEnum.CURRENT__TENTANT_CODE)
ConstantEnum.DEFAULT__SKILL_HUB_NAME and data.setdefault('skillHubName',
ConstantEnum.DEFAULT__SKILL_HUB_NAME)
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME and data.setdefault('skillPlatform',
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME)
if current__user_name:
data.setdefault('pnaUserName', current__user_name)
if bool(options.get("dataAsParams")):
params.update(data)
print(f"🔄 请求拦截, URL:{url}", "method", method, "params", params, "data", data, "headers", headers,
"options", options,
"timeout",
timeout)
response = requests.request(method, url, *args, json=data, params=params, headers=headers,
timeout=int(timeout), **argss)
response_text = response.text if ConstantEnum.is_debug() else response
if response.status_code == 401 and cls.authorization_retry_count < cls.AUTHORIZATION_RETRY_COUNT_MAX:
print(f"❌ 请求拦截, 鉴权:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
ApiEnum.TOKEN = ApiEnum.OPEN_TOKEN = None
if found_user:
found_user.token = found_user.open_token = None
user_dao.update(found_user)
cls.authorization_retry_count += 1
return cls.http_request(method, url, data, params, headers, options, *args, timeout=timeout, **argss)
elif response.status_code != 200:
raise requests.exceptions.RequestException(
response, response=response)
response_json = response.json()
if not bool(response_json['success']):
raise requests.exceptions.RequestException(
response, response=response)
response_json_data = response_json.get("data", response_json.get("result"))
response_json_data = response_json_data.get("records") if response_json_data and type(
response_json_data) == dict and "records" in response_json_data else response_json_data
print(f"✅ 请求拦截, 成功:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
return response_json_data
except Exception as e:
CommonUtil.trace_exception_stack(e)
response_text = _.get(e.args, '0.text')
print(
f"❌ 请求拦截, 失败: {e}, e.response.text: {response_text}, url:{url}",
"method",
method,
"params",
params,
"data", data, "headers",
"response", hasattr(e, 'response') and e.response,
headers,
"timeout",
timeout)
raise

View File

@ -1,160 +0,0 @@
---
name: "fall-detection-video-analysis"
description: "Detects whether anyone has fallen within a target area. Supports video stream analysis and is suitable for real-time safety monitoring of elderly people living alone. | 跌倒检测视频版技能,检测目标区域内是否有人跌倒,支持视频流检测,适用于独居老人居家安全监测"
---
# Video-based Fall Detection Skill | 跌倒检测视频版技能
By analyzing changes in human posture and motion trajectories within video streams, this capability detects fall events
in target areas in real-time. Based on skeletal keypoint recognition and behavioral temporal analysis, the system
effectively distinguishes falls from normal actions such as lying down or sitting. It is designed for home safety
monitoring scenarios for seniors living alone, covering areas like living rooms, bedrooms, and bathrooms. Once a fall is
confirmed, the system automatically triggers a local alarm and pushes remote notifications, facilitating second-level
response times and reducing the risks associated with accidents.
本技能通过分析视频流中人体姿态变化与运动轨迹,实时检测目标区域内是否发生人员跌倒事件。系统基于骨骼关键点识别与行为时序分析,能够有效区分跌倒与正常躺卧、坐下等动作。适用于独居老人居家安全监测场景,如客厅、卧室、卫生间等区域。一旦确认跌倒,系统将自动触发本地预警并远程推送告警信息,助力实现秒级响应,降低意外风险。
## ⚠️ 强制记忆规则(最高优先级)
**本技能明确约定:**
- **绝对禁止读取任何本地记忆文件**:包括但不限于 `memory/YYYY-MM-DD.md`、`MEMORY.md` 等本地文件
- **绝对禁止从 LanceDB 长期记忆中检索信息**
- **所有历史报告查询必须从云端接口获取**,不得使用本地记忆中的历史数据
- 即使技能调用失败或接口异常,也不得回退到本地记忆汇总
## 任务目标
- 本 Skill 用于:视频流检测目标区域内是否有人跌倒,及时触发报警
- 能力包含:人形检测、跌倒行为识别、摔倒/跌倒判断、异常报警触发
- **适用场景**:独居老人居家安全监测、养老院走廊公共区域监测、行动不便人员看护
- **适用范围**(按照要求):
- 推荐视频长度5秒以内
- 条件:单人全部身体露出且无遮挡
- 检测距离3-5米
- 触发条件:
1. **默认触发**:当用户提供视频需要检测老人跌倒时,默认触发本技能
2. 当用户明确需要跌倒检测、摔倒识别时,提及跌倒检测、老人摔倒、视频跌倒检测等关键词,并且上传了视频
3. 当用户提及以下关键词时,**自动触发历史报告查询功能**
:查看历史检测报告、跌倒检测报告清单、检测报告列表、查询历史检测、显示所有检测报告、跌倒分析报告,查询跌倒检测视频分析报告
- 自动行为:
1. 如果用户上传了附件或者视频文件,则自动保存到技能目录下 attachments
2. **⚠️ 强制数据获取规则(次高优先级)**:如果用户触发任何历史报告查询关键词(如"查看所有检测报告"、"显示历史跌倒"、"
查看历史报告"等),**必须**
- 直接使用 `python -m scripts.fall_detection_video_analysis --list --open-id` 参数调用 API
查询云端的历史报告数据
- **严格禁止**:从本地 memory 目录读取历史会话信息、严格禁止手动汇总本地记录中的报告、严格禁止从长期记忆中提取报告
- **必须统一**从云端接口获取最新完整数据,然后以 Markdown 表格格式输出结果
## 前置准备
- 依赖说明:scripts 脚本所需的依赖包及版本
```
requests>=2.28.0
```
## 检测要求(获得准确结果的前提)
为了获得准确的跌倒检测,请确保:
1. **摄像头固定位置**,覆盖目标监测区域(如客厅、走廊、卫生间)
2. **检测距离保持在 3-5 米**,目标人物全身完整出镜
3. **单人场景**,避免多人重叠遮挡,保证跌倒行为清晰可见
4. 建议视频长度在 **5秒以内**,过长视频建议分段检测
## 操作步骤
### 🔒 open-id 获取流程控制(强制执行,防止遗漏)
**在执行跌倒检测视频分析前,必须按以下优先级顺序获取 open-id**
```
第 1 步:【最高优先级】检查技能所在目录的配置文件(优先)
路径skills/smyx_common/scripts/config.yaml相对于技能根目录
完整路径示例:${OPENCLAW_WORKSPACE}/skills/{当前技能目录}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置/api-key 为空)
第 2 步:检查 workspace 公共目录的配置文件
路径:${OPENCLAW_WORKSPACE}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置)
第 3 步:检查用户是否在消息中明确提供了 open-id
↓ (未提供)
第 4 步:❗ 必须暂停执行,明确提示用户提供用户名或手机号作为 open-id
```
**⚠️ 关键约束:**
- **禁止**自行假设,自行推导,自行生成 open-id 值(如 openclaw-control-ui、default、fallvideo123、detectfall456 等)
- **禁止**跳过 open-id 验证直接调用 API
- **必须**在获取到有效 open-id 后才能继续执行分析
- 如果用户拒绝提供 open-id说明用途用于保存和查询检测报告记录并询问是否继续
---
- 标准流程:
1. **准备监控视频输入**
- 提供本地视频文件路径或网络视频 URL
- 确保符合5秒以内、单人全身出镜、无遮挡、3-5米距离
2. **获取 open-id强制执行**
- 按上述流程控制获取 open-id
- 如无法获取,必须提示用户提供用户名或手机号
3. **执行跌倒检测视频分析**
- 调用 `-m scripts.fall_detection_video_analysis` 处理视频(**必须在技能根目录下运行脚本**
- 参数说明:
- `--input`: 本地视频文件路径(使用 multipart/form-data 方式上传)
- `--url`: 网络视频 URL 地址API 服务自动下载)
- `--open-id`: 当前用户的 open-id必填按上述流程获取
- `--list`: 显示历史跌倒检测视频分析报告列表清单(可以输入起始日期参数过滤数据范围)
- `--api-key`: API 访问密钥(可选)
- `--api-url`: API 服务地址(可选,使用默认值)
- `--detail`: 输出详细程度basic/standard/json默认 json
- `--output`: 结果输出文件路径(可选)
4. **查看分析结果**
- 接收结构化的跌倒检测视频分析报告
- 包含:视频基本信息、检测结果、是否跌倒、跌倒位置、置信度、是否需要报警
## 资源索引
- 必要脚本:见 [scripts/fall_detection_video_analysis.py](scripts/fall_detection_video_analysis.py)(用途:调用 API
进行跌倒检测视频分析,本地文件使用 multipart/form-data 方式上传,网络 URL 由 API 服务自动下载)
- 配置文件:见 [scripts/config.py](scripts/config.py)(用途:配置 API 地址、默认参数和格式限制)
- 领域参考:见 [references/api_doc.md](references/api_doc.md)(何时读取:需要了解 API 接口详细规范和错误码时)
## 注意事项
- 仅在需要时读取参考文档,保持上下文简洁
- 支持格式mp4/avi/mov最大 100MB
- API 密钥可选,如果通过参数传入则必须确保调用鉴权成功,否则忽略鉴权
- **⚠️ 重要提醒**:本检测结果仅供安全预警参考,不能替代人工确认,发现跌倒报警请立即联系家人或医护人员现场确认
- 禁止临时生成脚本,只能用技能本身的脚本
- 传入的网路地址参数不需要下载本地默认地址都是公网地址api 服务会自动下载
- 当显示历史分析报告清单的时候,从数据 json 中提取字段 reportImageUrl 作为超链接地址,使用 Markdown 表格格式输出,包含"
报告名称"、"检测结果"、"是否报警"、"检测时间"、"点击查看"五列,其中"报告名称"列使用`跌倒检测视频报告-{记录id}`形式拼接, "
点击查看"列使用
`[🔗 查看报告](reportImageUrl)`
格式的超链接,用户点击即可直接跳转到对应的完整报告页面。
- 表格输出示例:
| 报告名称 | 检测结果 | 是否报警 | 检测时间 | 点击查看 |
|----------|----------|----------|----------|----------|
| 跌倒检测视频报告 -20260329003600001 | 未检测到跌倒 | 否 | 2026-03-29 00:
36 | [🔗 查看报告](https://example.com/report?id=xxx) |
## 使用示例
```bash
# 检测本地监控视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.fall_detection_video_analysis --input /path/to/fall_detect.mp4 --open-id openclaw-control-ui
# 检测网络视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.fall_detection_video_analysis --url https://example.com/detect.mp4 --open-id openclaw-control-ui
# 显示历史检测报告/显示检测报告清单列表/显示历史跌倒检测(自动触发关键词:查看历史检测报告、历史报告、检测报告清单等)
python -m scripts.fall_detection_video_analysis --list --open-id openclaw-control-ui
# 输出精简报告
python -m scripts.fall_detection_video_analysis --input fall_detect.mp4 --open-id your-open-id --detail basic
# 保存结果到文件
python -m scripts.fall_detection_video_analysis --input fall_detect.mp4 --open-id your-open-id --output result.json
```

View File

@ -1,11 +0,0 @@
{
"owner": "18072937735",
"slug": "smyx-fall-detection-video-analysis",
"displayName": "Video-based Fall Detection Skill | 跌倒检测视频版技能",
"latest": {
"version": "1.0.0",
"publishedAt": 1776257210180,
"commit": "https://github.com/openclaw/skills/commit/b0fcda692ea20ee98eed52d76845ba4687cefc48"
},
"history": []
}

View File

@ -1,21 +0,0 @@
# API 接口文档
此处用于存放跌倒检测视频分析 API 的接口文档,待后续补充。
## 接口规范
- 基础地址:由 smyx_common 配置统一管理
- 认证方式API Key 鉴权
- 请求格式multipart/form-data 支持文件上传
- 响应格式JSON
## 主要接口
1. `/web/ai-analysis/v2/start-common-ai-analysis` - 启动AI分析任务
2. `/web/ai-analysis/v2/get-common-ai-analysis-result` - 获取分析结果
3. `/web/ai-analysis/page-common-ai-analysis-result` - 分页查询历史报告
4. `/ai/order/api/getReportDetailExport?id={id}` - 导出完整报告
## 场景代码
- `OPEN_FALL_DETECTION_VIDEO_ANALYSIS` - 开放平台跌倒检测视频分析

View File

@ -1 +0,0 @@
# Pet Analysis scripts package

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.smyx_common.scripts.util import RequestUtil
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, *args, **argss):
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
# params.setdefault("scene", scene_code)
# 添加宠物类型参数
if ConstantEnum.DEFAULT__PET_TYPE:
params.setdefault("petType", ConstantEnum.DEFAULT__PET_TYPE)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
# 跌倒检测视频版工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ConstantEnum as ConstantEnumBase
from skills.face_analysis.scripts.config import ApiEnum as ApiEnumParent, ConstantEnum as ConstantEnumParent, \
ApiEnumCommonAiMixin
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumCommonAiMixin, ApiEnumParent):
pass
class ConstantEnum(ConstantEnumParent):
@classmethod
def init(cls, config=None):
super().init(config)
ConstantEnumParent.DEFAULT__SCENE_CODE = SceneCodeEnum.FALL_DETECTION_VIDEO_ANALYSIS.value

View File

@ -1,97 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
from skills.smyx_common.scripts.util import RequestUtil
def detect_fall(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
input_path = input_path or url
return skill.get_output_analysis(input_path)
def show_analyze_list(open_id, start_time=None, end_time=None):
output_content = skill.get_output_analysis_list()
return output_content
def main():
parser = argparse.ArgumentParser(description="跌倒检测视频分析工具")
parser.add_argument("--input", help="本地视频文件路径")
parser.add_argument("--url", help="网络视频URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示跌倒检测视频分析列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
# 设置 Python 进程内的环境变量
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在检测跌倒,请稍候...")
output_content = detect_fall(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 跌倒检测视频分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import json
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.face_analysis.scripts.skill import Skill as SkillParent
from skills.smyx_common.scripts.util import JsonUtil
class Skill(SkillParent):
def __init__(self):
super().__init__()
def get_output_analysis_content_head(self, result=None):
return f"📊 跌倒检测视频分析结构化结果"
def get_output_analysis_content_foot(self, result):
pass
skill = Skill()

View File

@ -1,86 +0,0 @@
# 中医面诊分析工具 (face-analysis)
## 技能介绍
这是一个基于AI的中医面诊分析技能可以通过面部视频自动分析健康状况返回结构化的诊断结果和养生建议。
## 快速开始
### 1. 配置API信息
编辑 `scripts/config.py`设置你的API地址和密钥
```python
DEFAULT_API_URL = "https://your-api-server.com/api/v1/face-analysis"
DEFAULT_API_KEY = "your-api-key-here"
```
### 2. 分析本地视频
```bash
python scripts/face_analysis.py --input /path/to/your/video.mp4
```
### 3. 分析网络视频
```bash
python scripts/face_analysis.py --url https://example.com/video.mp4
```
## 功能特性
- ✅ 支持本地MP4视频上传
- ✅ 支持网络视频URL分析
- ✅ 三种输出详细程度:精简/标准/完整
- ✅ 结构化JSON结果输出
- ✅ 自动保存结果到文件
- ✅ 内置视频格式和大小校验
## 目录结构
```
face-analysis/
├── SKILL.md # 技能说明文件(系统自动加载)
├── README.md # 本说明文件
├── scripts/
│ ├── face_analysis.py # 主程序
│ └── config.py # 配置文件
├── references/
│ ├── api_doc.md # API接口文档
│ ├── tcm_theory.md # 中医面诊理论参考
│ └── faq.md # 常见问题
└── assets/
└── template.json # 返回结果模板
```
## 使用示例
### 标准输出
```
📊 中医面诊分析报告
==================================================
⏰ 分析时间: 2026-03-10 15:30:00
🎯 人脸检测: success (置信度: 95分)
🔍 诊断结果:
整体体质: 平和质
脏腑状况:
liver: 正常
heart: 轻微火旺
spleen: 略虚
lung: 正常
kidney: 正常
面色分析: 微黄
对应提示: 脾胃功能略弱
⚠️ 健康警示:
⚠️ 注意休息,避免熬夜
💡 养生建议:
💡 饮食清淡,减少辛辣食物摄入
💡 保持规律作息每晚11点前入睡
💡 适当进行有氧运动,如散步、太极拳
==================================================
```
### 输出到JSON文件
```bash
python scripts/face_analysis.py --input video.mp4 --detail full --output result.json
```
## 注意事项
1. 视频要求清晰正面面部光线充足时长5-30秒为宜
2. 支持格式mp4、avi、mov最大100MB
3. API需要自行部署或接入第三方服务
4. 结果仅供参考,不能替代专业医生诊断

View File

@ -1,69 +0,0 @@
# API接口文档
## 接口地址
`POST https://your-api-server.com/api/v1/face-analysis`
## 请求头
| 字段 | 必选 | 说明 |
|------|------|------|
| X-API-Key | 是 | API访问密钥 |
| Content-Type | 是 | multipart/form-data文件上传或 application/jsonURL模式 |
## 请求参数
### 1. 文件上传模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video | file | 是 | MP4视频文件 |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
### 2. URL模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video_url | string | 是 | 可公开访问的视频URL |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
## 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"analysis_time": "2026-03-10 15:30:00",
"face_detection": {
"status": "success",
"face_count": 1,
"quality_score": 95
},
"diagnosis": {
"overall_constitution": "平和质",
"organ_condition": {
"liver": "正常",
"heart": "轻微火旺",
"spleen": "略虚",
"lung": "正常",
"kidney": "正常"
},
"color_analysis": {
"complexion": "微黄",
"correspondence": "脾胃功能略弱"
}
},
"health_warnings": [
"注意休息,避免熬夜"
],
"suggestions": [
"饮食清淡,减少辛辣食物摄入"
]
}
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 400 | 请求参数错误 |
| 401 | API密钥无效 |
| 403 | 权限不足 |
| 413 | 文件过大 |
| 415 | 不支持的文件格式 |
| 500 | 服务器内部错误 |

View File

@ -1,3 +0,0 @@
pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3

View File

@ -1,52 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
scene_code and params.setdefault("sceneCode", scene_code)
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
scene_code and params.setdefault("sceneCode", scene_code)
params.setdefault("appCategory", ConstantEnum.DEFAULT__APP_CATEGORY)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
ConstantEnum.DEFAULT__SCENE_CODE and data.setdefault("sceneCode", ConstantEnum.DEFAULT__SCENE_CODE)
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python3
# 中医面诊分析工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase, ConstantEnum as ConstantEnumBase
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumBase):
ANALYSIS_URL = "/web/health-analysis/v2/start-health-analysis"
ANALYSIS_RESULT_URL = "/web/health-analysis/get-health-analysis-result"
PAGE_URL = "/web/health-analysis/page-health-analysis-result"
DETAIL_EXPORT_URL = ApiEnumBase.BASE_URL_HEALTH + "/health/order/api/getReportDetailExport?id="
@classmethod
def init(cls, config=None):
super().init(config)
class ApiEnumCommonAiMixin:
@classmethod
def init(cls, config=None):
parent = super()
if hasattr(parent, "init"):
parent.init(config)
ApiEnum.ANALYSIS_URL = "/web/ai-analysis/v2/start-common-ai-analysis"
ApiEnum.ANALYSIS_RESULT_URL = "/web/ai-analysis/get-common-ai-analysis-result"
ApiEnum.PAGE_URL = "/web/ai-analysis/page-common-ai-analysis-result"
class ConstantEnum(ConstantEnumBase):
DEFAULT__APP_CATEGORY = "PEI_NI_AN"

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
# import_path_common()
from skills.smyx_common.scripts.util import RequestUtil
# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB
def validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > MAX_FILE_SIZE_MB:
raise ValueError(f"文件过大,最大支持 {MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
def analyze_video(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
"""调用API分析视频"""
if not input_path and not url:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
try:
input_path = input_path or url
return skill.get_output_analysis(input_path)
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def show_analyze_list(open_id, start_time=None, end_time=None):
# if not open_id:
# raise ValueError("必须提供本用户的OpenId/UserId")
try:
output_content = skill.get_output_analysis_list()
return output_content
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def get_analysis_export_url(request_id=None):
"""调用API分析视频"""
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
def format_result(result, output_level="standard"):
"""格式化输出结果"""
if output_level == "json":
result_id = None
# if result.get('success'):
if result is not None:
result_json = result
result_id = result_json.get('id', {})
result_json = json.dumps(result_json.get('faceAnalysisResponse', {}), ensure_ascii=False, indent=2)
else:
# result_json = json.dumps(result, ensure_ascii=False, indent=2)
return "⚠️ 暂无分析结果"
return f"""
📊 面诊分析结构化结果
{result_json}
""", result_id
elif output_level == "basic":
# 精简输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
return f"""
📊 面诊分析结果
{'=' * 40}
整体体质: {diagnosis.get('overall_constitution', '未知')}
主要状况: {', '.join([f'{k}: {v}' for k, v in diagnosis.get('organ_condition', {}).items() if v != '正常'])}
健康提示: {data.get('health_warnings', ['无特殊警示'])[0] if data.get('health_warnings') else '无特殊警示'}
"""
elif output_level == "standard":
# 标准输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
face_detection = data.get('face_detection', {})
organ_status = "\n".join([f" {k}: {v}" for k, v in diagnosis.get('organ_condition', {}).items()])
warnings = "\n".join([f" ⚠️ {item}" for item in data.get('health_warnings', [])])
suggestions = "\n".join([f" 💡 {item}" for item in data.get('suggestions', [])])
return f"""
📊 中医面诊分析报告
{'=' * 50}
分析时间: {data.get('analysis_time', '未知')}
🎯 人脸检测: {face_detection.get('status', '未知')} (置信度: {face_detection.get('quality_score', 0)})
🔍 诊断结果:
整体体质: {diagnosis.get('overall_constitution', '未知')}
脏腑状况:
{organ_status}
面色分析: {diagnosis.get('color_analysis', {}).get('complexion', '未知')}
对应提示: {diagnosis.get('color_analysis', {}).get('correspondence', '未知')}
健康警示:
{warnings}
💡 养生建议:
{suggestions}
{'=' * 50}
"""
else:
# 完整输出JSON格式
return json.dumps(result, ensure_ascii=False, indent=2)
def main():
parser = argparse.ArgumentParser(description="中医面诊分析工具")
parser.add_argument("--input", help="本地MP4视频文件路径")
parser.add_argument("--url", help="网络视频MP4的URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示面诊视频历史列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在分析面诊视频,请稍候...")
output_content = analyze_video(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 面诊分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,267 +0,0 @@
#!/usr/bin/env python3
import datetime
import os
import sys
from .config import ApiEnum, ConstantEnum
from .api_service import ApiService
from skills.smyx_common.scripts.util import CommonUtil, JsonUtil
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase
from skills.smyx_common.scripts.base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def get_output_analysis_content_body(self, result=None):
result_json = result
result_json_pure_text = result_json.get("pureText")
if result_json_pure_text:
result_json = JsonUtil.parse(result_json_pure_text, result_json_pure_text)
result_json_common_ai_response = result_json.get("commonAiResponse")
if result_json_common_ai_response:
result_json = result_json_common_ai_response
result_json_health_ai_response = result_json.get("healthAiResponse")
if result_json_health_ai_response:
result_json = result_json_health_ai_response
result_json = JsonUtil.stringify(result_json, result_json)
return result_json
def get_output_analysis_content_head(self, result=None):
return f"📊 面诊分析结构化结果"
def get_output_analysis_content_foot(self, result):
result_id = result.get('id', {})
output_content_export_url = ApiEnum.DETAIL_EXPORT_URL + result_id
return f"🔗 获取报告导出图片链接: {output_content_export_url}"
def get_output_analysis_content(self, result):
if result is not None:
output_content = self.get_output_analysis_content_body(result) or ""
output_content_head = self.get_output_analysis_content_head(result)
output_content_foot = self.get_output_analysis_content_foot(result)
# d
if output_content_head:
output_content = f"""
{output_content_head}
""" + output_content
if output_content_foot:
output_content += f"""
{output_content_foot}
"""
else:
output_content = "⚠️ 暂无分析结果"
return output_content
def get_output_analysis(self, input_path, params={}):
response = self.get_analysis(
input_path, params
)
def _analysis_result():
return self.analysis_result(
data=response
)
new_response = CommonUtil.polling(_analysis_result,
check_condition=lambda res: res.get('needPageRefresh') is False, interval=5,
max_attempts=24)
output_content = self.get_output_analysis_content(new_response)
return output_content
def get_analysis(self, input_path, params={}):
import mimetypes
def _validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in ConstantEnum.SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(ConstantEnum.SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > ConstantEnum.MAX_FILE_SIZE_MB:
raise ValueError(
f"文件过大,最大支持 {ConstantEnum.MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
files = None
if not input_path:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
if (input_path.startswith("http://") or input_path.startswith("https://")):
params.update({
"videoUrl": input_path
})
else:
_validate_file(input_path)
# 自动检测 MIME 类型
mime_type, _ = mimetypes.guess_type(input_path)
if mime_type is None:
mime_type = 'application/octet-stream'
# 读取文件内容
with open(input_path, 'rb') as f:
file_content = f.read()
# 构建 multipart/form-data 格式的请求
files = {
'file': (os.path.basename(input_path), file_content, mime_type)
}
response = self.analysis(
params=params,
files=files
)
return response
def get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
response = self.page(pageNum, pageSize, *args, **argss)
if response:
for item in response:
if item.get("commonAiResponse") or item.get("healthAiResponse"):
item["reportImageUrl"] = _get_analysis_export_url(item.get("id"))
response_text = JsonUtil.stringify(response)
if response_text:
return f"""📊 分析报告记录列表(结构化结果)"
{response_text}
"""
else:
return "⚠️ 暂无分析报告记录"
def __get_output_analysis_list(self, pageNum=None, pageSize=None, *args, **argss):
"""获取面诊报告清单
优化规则只要API服务接口返回面诊报告清单直接输出API返回的结果
无需汇总上下文中的面诊分析报告以接口返回为准
"""
def _get_analysis_export_url(request_id=None):
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
# open_id 仅用于本地识别不传给API - 参数已经在argss中page方法会正确处理
open_id = argss.pop('open_id', None)
# if not open_id:
# return "⚠️ 错误:缺少 open_id 参数"
# 获取总页数,然后循环获取所有页
output_all = ""
# 先获取第一页来获取总页数
# page 方法在基类中已经处理过,我们需要兼容两种返回结果:
# 1. 完整响应:{"success": true, "data": {"records": [...], "total": ...}}
# 2. 已经提取好的数据:直接返回 data 对象或 records 列表
response = self.page(pageNum or 1, pageSize or 30, *args, **argss)
if response is None:
return "⚠️ 获取报告列表失败response is None"
# 兼容处理:不同版本的基类返回不同格式
if isinstance(response, list):
# 基类直接返回了 records 列表,无法获取分页信息,直接使用
records = response
total = len(records)
pages = 1
elif isinstance(response, dict):
# 完整响应格式
if not response.get('success'):
error_msg = response.get('errorMsg', '未知错误')
return f"⚠️ 获取报告列表失败:{error_msg}"
data = response.get('data', {})
if not data or not isinstance(data, dict):
return "⚠️ 获取报告列表失败:数据格式错误"
total = data.get('total', 0)
pages = data.get('pages', 1)
records = data.get('records', [])
else:
return f"⚠️ 获取报告列表失败response type={type(response)}"
if not records:
return "⚠️ 暂无面诊分析报告记录"
output_all = f"📋 历史面诊分析报告清单(共 {total} 份)\n\n"
output_all += "| 报告名称 | 分析时间 | 体质判断 | 点击查看 |\n"
output_all += "|----------|----------|----------|----------|\n"
# 处理第一页
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
# 处理剩余页
for current_page in range(2, pages + 1):
response = self.page(current_page, 30, *args, **argss)
if not response or not isinstance(response, dict) or not response.get('success'):
continue
data = response.get('data', {})
if not data or not isinstance(data, dict):
continue
records = data.get('records', [])
for item in records:
if not isinstance(item, dict):
continue
report_id = item.get('id', '')
create_time = item.get('createTimeString', '未知时间')
# 提取体质判断 - 优先从 healthAiResponse 获取,如果没有再从 faceAnalysisResponse 获取
health_ai = item.get('healthAiResponse', {}) or {}
if health_ai:
health_assessment = health_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
else:
face_ai = item.get('faceAnalysisResponse', {}) or {}
health_assessment = face_ai.get('healthAssessment', {}) or {}
subject = health_assessment.get('subject', '未知')
report_name = f"面诊分析报告-{report_id}"
report_url = _get_analysis_export_url(report_id)
output_all += f"| {report_name} | {create_time} | {subject} | [🔗 查看报告]({report_url}) |\n"
output_all += "\n> 注:面诊分析结果仅供健康参考,不能替代专业医疗诊断。"
return output_all
skill = Skill()

View File

@ -1,127 +0,0 @@
altgraph==0.17.5
annotated-doc==0.0.4
annotated-types==0.7.0
anyio==4.12.1
APScheduler==3.11.2
astroid==3.1.0
Authlib==1.6.6
blinker==1.4
cachetools==6.2.6
certifi==2026.1.4
cffi==2.0.0
chardet==5.2.0
charset-normalizer==3.4.4
click==8.3.1
coverage==7.13.2
coze-workload-identity==0.1.7
cozeloop==0.1.19
cryptography==3.4.8
Cython==3.2.4
dbus-python==1.2.18
dill==0.4.1
distro==1.7.0
distro-info==1.1+ubuntu0.2
et_xmlfile==2.0.0
fastapi==0.121.2
gitdb==4.0.12
gitignore_parser==0.1.13
GitPython==3.1.45
greenlet==3.3.1
h11==0.16.0
httpcore==1.0.9
httplib2==0.20.2
httpx==0.28.1
httpx-ws==0.8.2
idna==3.11
importlib-metadata==4.6.4
inflect==7.5.0
iniconfig==2.3.0
isort==5.13.2
jeepney==0.7.1
Jinja2==3.1.6
jiter==0.12.0
jsonpatch==1.33
jsonpointer==3.0.0
keyring==23.5.0
langchain==1.0.3
langchain-core==1.0.2
langchain-openai==1.0.1
langgraph==1.0.2
langgraph-checkpoint==3.0.0
langgraph-prebuilt==1.0.2
langgraph-sdk==0.2.9
langsmith==0.4.39
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mccabe==0.7.0
mdurl==0.1.2
more-itertools==8.10.0
numpy==2.4.1
oauthlib==3.2.0
openai==2.16.0
openpyxl==3.1.5
orjson==3.11.5
ormsgpack==1.12.2
packaging==25.0
pandas==2.3.3
platformdirs==4.5.1
pluggy==1.6.0
psutil==7.1.3
psycopg2-binary==2.9.11
pycparser==3.0
pydantic==2.12.4
pydantic_core==2.41.5
pydash==8.0.6
Pygments==2.19.2
PyGObject==3.42.1
pyinstaller==6.18.0
pyinstaller-hooks-contrib==2026.0
PyJWT==2.10.1
pylint==3.1.0
PyMySQL==1.1.2
pyparsing==2.4.7
pytest==9.0.1
pytest-asyncio==1.3.0
pytest-cov==7.0.0
pytest-mock==3.15.1
python-apt==2.4.0+ubuntu4.1
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.3
regex==2026.1.15
requests==2.32.5
requests-toolbelt==1.0.0
rich==14.2.0
SecretStorage==3.3.1
setuptools==80.9.0
six==1.16.0
smmap==5.0.2
sniffio==1.3.1
sqlacodegen==3.2.0
SQLAlchemy==2.0.46
starlette==0.49.3
supervisor==4.2.1
tenacity==9.1.2
tiktoken==0.12.0
tomlkit==0.14.0
tqdm==4.67.1
typeguard==4.4.4
typing-inspection==0.4.2
typing_extensions==4.15.0
tzdata==2025.3
tzlocal==5.3.1
unattended-upgrades==0.1
urllib3==2.6.3
uvicorn==0.38.0
wadllib==1.3.6
watchdog==6.0.0
websockets==15.0.1
wheel==0.45.1
wsproto==1.3.2
xlrd==2.0.2
xxhash==3.6.0
zipp==1.0.0
zstandard==0.25.0

View File

@ -1,8 +0,0 @@
from .util import RequestUtil, CommonUtil, DatetimeUtil
from .base import *
__all__ = [
'RequestUtil',
'CommonUtil',
'BaseUtil'
]

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
from .config import ApiEnum
from .base import BaseApiService
from .util import RequestUtil, CommonUtil
class ApiService(BaseApiService):
def __init__(self):
super().__init__()
def get_download_url(self, tosKey, expireSeconds=3600):
return RequestUtil.http_post(
ApiEnum.GET_DOWNLOAD_URL__URL,
params={
"tosKey": tosKey,
"expireSeconds": expireSeconds * 24
}
)
def page(self, url, pageNum=None, pageSize=None, *args, **argss):
data = args[0] if len(args) > 0 else argss.get('data') if argss.get('data') is not None else {}
if pageNum is None:
pageNum = 1
if pageSize is None:
pageSize = ApiEnum.DEFAULT__PAGE_SIZE
paramsPage = {
'pageNum': int(pageNum),
'pageSize': int(pageSize)
}
data.update({
"page": paramsPage
})
if not CommonUtil.is_empty(data):
if (len(args) == 0):
argss.setdefault("data", data)
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def list(self, url=None, *args, **argss):
if url is not None:
argss["url"] = url
return self.page(1, ApiEnum.DEFAULT__PAGE_SIZE_MAX, *args, **argss)
def add(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def edit(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def delete(self, url=None, *args, **argss):
response = RequestUtil.http_post(
url,
*args, **argss
)
return response
def http_post(self, url=None, *args, **argss):
return RequestUtil.http_post(
url,
*args, **argss
)
def http_put(self, url=None, *args, **argss):
return RequestUtil.http_put(
url,
*args, **argss
)
def http_get(self, url=None, *args, **argss):
return RequestUtil.http_get(
url,
*args, **argss
)
return response
def http_delete(self, url=None, *args, **argss):
return RequestUtil.http_delete(
url,
*args, **argss
)
return response

View File

@ -1,33 +0,0 @@
class BaseUtil:
pass
class BaseMixin:
pass
class BaseDao:
pass
class BaseService:
def __init__(self):
super().__init__()
class BaseApiService(BaseService):
INSTANCE = None
def __init__(self):
super().__init__()
@classmethod
def get_instance(cls):
if cls.INSTANCE is None:
cls.INSTANCE = cls()
return cls.INSTANCE
class BaseSkill:
def __init__(self):
super().__init__()

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "http://192.168.1.234:9601/smyx-open-api"
base-url-open-h5: "http://192.168.1.234:4100"
base-url-health: "http://192.168.1.234:8080/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,7 +0,0 @@
ApiEnum:
base-url-open-api: "https://livemonitortest.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitortest.lifeemergence.com"
base-url-health: "https://healthtest.lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: true

View File

@ -1,240 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from enum import Enum
from typing import Dict
import inspect
import yaml
import platform
class YamlUtil:
@staticmethod
def load(path, config: Dict = {}) -> Dict:
try:
if not os.path.exists(path):
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
return config
with open(path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f) or {}
for key, value in config.items():
if key not in config:
config[key] = value
return config
except:
pass
return config
@staticmethod
def save(path, config: Dict) -> Dict:
try:
with open(path, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False, allow_unicode=True)
except:
pass
return config
class BaseEnum:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
clsModule = cls.__module__
cls_path = inspect.getfile(cls)
clsFullName = f"{cls.__module__}.{cls.__name__}"
cls_dirpath = os.path.dirname(cls_path) # .../src
clsModulePath = clsModule.replace(".", "\\")
current_dir = os.path.dirname(os.path.abspath(__file__)) # .../src
config_path = os.path.join(cls_dirpath, "config.yaml")
config = YamlUtil.load(config_path)
cls.init(config)
env = config.get("env")
if env:
env_config_path = os.path.join(cls_dirpath, f"config-{env}.yaml")
env_config = YamlUtil.load(env_config_path)
cls.init(env_config)
@classmethod
def init(cls, config=None):
clsName = cls.__name__
clsConfig = config and config.get(clsName)
if clsConfig:
for config_key, config_value in clsConfig.items():
new_config_key = config_key = config_key.upper().replace("-", "_")
if hasattr(cls, new_config_key):
setattr(cls, new_config_key, config_value)
class ApiEnum(BaseEnum):
API_KEY = None
API_SECRET_KEY = None
DATABASE_URL = ""
BASE_URL_OPEN_API = ""
BASE_URL_OPEN_H5 = ""
BASE_URL_HEALTH = ""
OPEN_TOKEN = ""
TOKEN = ""
DEFAULT__REQUEST_TIMEOUT = 120
DEFAULT__PAGE_SIZE = 5
DEFAULT__PAGE_SIZE_MAX = 65536
GET_DOWNLOAD_URL__URL = BASE_URL_OPEN_API + "/api/tos/get-download-url"
class ConstantEnum(BaseEnum):
class SourceEnum(Enum):
ARK_CLAW = "ARK_CLAW"
JVS_CLAW = "JVS_CLAW"
LIGHT_CLAW = "LIGHT_CLAW"
WUHONG = "WUHONG"
COZE = "COZE"
SKILL_HUB = "SKILL_HUB"
CLAW_HUB = "CLAW_HUB"
FEISHU = "FEISHU"
DINGTALK = "DINGTALK"
WEIXIN = "WEIXIN"
YUANBAO = "YUANBAO"
WECOM = "WECOM"
QQBOT = "QQBOT"
APP__ID = ""
APP__SOURCE = SourceEnum.CLAW_HUB.value
IS_DEBUG = False
CURRENT__OPEN_ID = ""
CURRENT__USER_NAME = ""
CURRENT__TENTANT_CODE = ""
FEISHU_APP__ID = ""
FEISHU_APP__SECRET = ""
FEISHU_APP__RECEIVE_ID = ""
DEFAULT__SCENE_CODE = ""
DEFAULT__SKILL_HUB_NAME = APP__SOURCE
DEFAULT__SKILL_PLATFORM_NAME = ""
DEFAULT__OUTPUT_LEVEL = "json"
SUPPORTED_FORMATS = ["mp4", "avi", "mov"]
MAX_FILE_SIZE_MB = 10
@staticmethod
def is_debug():
return platform.system() != 'Linux' and ConstantEnum.IS_DEBUG
@classmethod
def init(cls, config=None):
super().init(config)
openclaw_sender_open_id = os.environ.get("OPENCLAW_SENDER_OPEN_ID")
openclaw_sender_username = os.environ.get("OPENCLAW_SENDER_USERNAME")
feishu_open_id = os.environ.get("FEISHU_OPEN_ID")
if openclaw_sender_open_id:
cls.CURRENT__OPEN_ID = openclaw_sender_open_id
if openclaw_sender_username:
cls.CURRENT__USER_NAME = openclaw_sender_username
if feishu_open_id:
cls.FEISHU_APP__RECEIVE_ID = feishu_open_id
class SceneCodeEnum(Enum):
# 开放 #
OPEN_HEALTH_AI_ANALYSIS = "OPEN_HEALTH_AI_ANALYSIS"
OPEN_PERSON_RISK_ANALYSIS = "OPEN_PERSON_RISK_ANALYSIS"
# 智眸 #
PUBLIC_AREA_AI_ANALYSIS = "PUBLIC_AREA_AI_ANALYSIS"
PERSONNEL_LEAVE_POST_MONITORING = "PERSONNEL_LEAVE_POST_MONITORING"
CRAWL_MONITOR = "CRAWL_MONITOR"
# 陪你安 #
PEI_NI_AN_DEFAULT = "PEI_NI_AN_DEFAULT"
PET_ANALYSIS = "PET_ANALYSIS"
CRAWL_ANALYSIS = "CRAWL_ANALYSIS"
AQUARIUM_ANALYSIS = "AQUARIUM_ANALYSIS"
PSYCHOLOGY_ANALYSIS = "PSYCHOLOGY_ANALYSIS"
AUTISM_ANALYSIS = "AUTISM_ANALYSIS"
DIET_ANALYSIS = "DIET_ANALYSIS"
DRIVE_ANALYSIS = "DRIVE_ANALYSIS"
SPORT_ANALYSIS = "SPORT_ANALYSIS"
EMOTION_ANALYSIS = "EMOTION_ANALYSIS"
STUDY_ANALYSIS = "STUDY_ANALYSIS"
INFANT_SAFETY_MONITORING_ANALYSIS = "INFANT_SAFETY_MONITORING"
PHONE_USAGE_MONITORING_ANALYSIS = "PHONE_USAGE_MONITORING"
INCONTINENCE_ALERT_ANALYSIS = "INCONTINENCE_ALERT"
RESPIRATORY_SYMPTOM_RECOGNITION_ANALYSIS = "RESPIRATORY_SYMPTOM_RECOGNITION"
ELECTRIC_VEHICLE_DETECTION_ANALYSIS = "ELECTRIC_VEHICLE_DETECTION"
SMOKING_DETECTION_ANALYSIS = "SMOKING_DETECTION"
PET_DETECTION_FEEDER_ANALYSIS = "PET_DETECTION_FEEDER"
PET_HEALTH_MONITORING_ANALYSIS = "PET_HEALTH_MONITORING"
STROKE_RISK_SCREENING_ANALYSIS = "STROKE_RISK_SCREENING"
HUMAN_DETECTION_ANALYSIS = "HUMAN_DETECTION"
STRANGER_RECOGNITION_ANALYSIS = "STRANGER_RECOGNITION"
FOCUS_ANALYSIS_ANALYSIS = "FOCUS_ANALYSIS"
HUMAN_POSTURE_RECOGNITION_ANALYSIS = "HUMAN_POSTURE_RECOGNITION"
HUMAN_EMOTION_RECOGNITION_ANALYSIS = "HUMAN_EMOTION_RECOGNITION"
FIRE_SMOKE_DETECTION_ANALYSIS = "FIRE_SMOKE_DETECTION"
BASIC_OBJECT_DETECTION_ANALYSIS = "BASIC_OBJECT_DETECTION"
CHILD_DANGEROUS_BEHAVIOR_RECOGNITION_ANALYSIS = "CHILD_DANGEROUS_BEHAVIOR_RECOGNITION"
PET_RESTRICTED_AREA_WARNING_ANALYSIS = "PET_RESTRICTED_AREA_WARNING"
SLEEP_QUALITY_ANALYSIS_ANALYSIS = "SLEEP_QUALITY_ANALYSIS"
PET_DETECTION_ANALYSIS = "PET_DETECTION"
PSYCHOLOGICAL_STRESS_ASSESSMENT_ANALYSIS = "PSYCHOLOGICAL_STRESS_ASSESSMENT"
VISUAL_QA_ANALYSIS = "VISUAL_QA"
PET_BODY_HEALTH_ANALYSIS = "PET_BODY_HEALTH_ANALYSIS"
PET_BEHAVIOR_DETECTION_ANALYSIS = "PET_BEHAVIOR_DETECTION"
INFANT_SUFFOCATION_WARNING_ANALYSIS = "INFANT_SUFFOCATION_WARNING"
STRANGER_APPROACH_WARNING_ANALYSIS = "STRANGER_APPROACH_WARNING"
IMAGE_QUALITY_DETECTION_ANALYSIS = "IMAGE_QUALITY_DETECTION"
CHILD_EMOTION_RECOGNITION_ANALYSIS = "CHILD_EMOTION_RECOGNITION"
OUTDOOR_MONITORING_ANALYSIS = "OUTDOOR_MONITORING"
FALL_DETECTION_IMAGE_ANALYSIS = "FALL_DETECTION_IMAGE"
CUSTOM_TIMELAPSE_ANALYSIS = "CUSTOM_TIMELAPSE"
CONTACTLESS_VITAL_SIGNS_MONITORING_ANALYSIS = "CONTACTLESS_VITAL_SIGNS_MONITORING"
VIDEO_SEARCH_ANALYSIS = "VIDEO_SEARCH"
FAMILIAR_PERSON_RECOGNITION_ANALYSIS = "FAMILIAR_PERSON_RECOGNITION"
TCM_CONSTITUTION_RECOGNITION_ANALYSIS = "TCM_CONSTITUTION_RECOGNITION"
CONTACTLESS_HEALTH_RISK_DETECTION_ANALYSIS = "CONTACTLESS_HEALTH_RISK_DETECTION"
UNACCOMPANIED_MONITORING_ANALYSIS = "UNACCOMPANIED_MONITORING"
ELDERLY_FALL_DETECTION_ANALYSIS = "ELDERLY_FALL_DETECTION"
PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION_ANALYSIS = "PARKINSON_EPILEPSY_BEHAVIOR_RECOGNITION"
PET_BREED_INDIVIDUAL_RECOGNITION_ANALYSIS = "PET_BREED_INDIVIDUAL_RECOGNITION"
ELDERLY_BED_EXIT_WANDERING_MONITORING_ANALYSIS = "ELDERLY_BED_EXIT_WANDERING_MONITORING"
ARRHYTHMIA_EARLY_WARNING_ANALYSIS = "ARRHYTHMIA_EARLY_WARNING"
FIRE_DETECTION_ANALYSIS = "FIRE_DETECTION"
VISUAL_SUMMARY_ANALYSIS = "VISUAL_SUMMARY"
PACKAGE_DETECTION_ANALYSIS = "PACKAGE_DETECTION"
INFANT_BLANKET_KICK_MONITORING_ANALYSIS = "INFANT_BLANKET_KICK_MONITORING"
PET_CALMING_TRIGGER_ANALYSIS = "PET_CALMING_TRIGGER"
CAT_FACE_RECOGNITION_ANALYSIS = "CAT_FACE_RECOGNITION"
INFANT_SLEEP_MONITORING_ANALYSIS = "INFANT_SLEEP_MONITORING"
VIRTUAL_FENCE_INTRUSION_WARNING_ANALYSIS = "VIRTUAL_FENCE_INTRUSION_WARNING"
FALL_DETECTION_VIDEO_ANALYSIS = "FALL_DETECTION_VIDEO"
INFANT_CRY_ANALYSIS = "INFANT_CRY_ANALYSIS"
PET_VOCAL_EMOTION_ANALYSIS = "PET_VOCAL_EMOTION_ANALYSIS"
BIRD_RECOGNITION_ANALYSIS = "BIRD_RECOGNITION"
FRAUD_CALL_IDENTIFICATION = "FRAUD_CALL_IDENTIFICATION"
PLANT_SPECIES_RECOGNITION = "PLANT_SPECIES_RECOGNITION"
PLANT_GROWTH_STAGE_RECOGNITION = "PLANT_GROWTH_STAGE_RECOGNITION"
PLANT_DISEASE_RECOGNITION = "PLANT_DISEASE_RECOGNITION"
PLANT_NUTRITION_DIAGNOSIS = "PLANT_NUTRITION_DIAGNOSIS"
PLANT_WILTING_MONITORING = "PLANT_WILTING_MONITORING"

View File

@ -1,18 +0,0 @@
env: prod
ApiEnum:
api-key:
api-secret-key:
database-url:
base-url-open-api: "https://open.lifeemergence.com/smyx-open-api"
base-url-open-h5: "http://livemonitor.lifeemergence.com"
base-url-health: "https://lifeemergence.com/jeecg-boot"
ConstantEnum:
is-debug: false
app--id: x1a3s4nwy1s2r4se
current--tentant-code: "PEI_NI_AN"
feishu-app--id: cli_a93d769369badcb1
feishu-app--secret:
default--skill-platform-name: ARK_CLAW
# default--scene-code: PEI_NI_AN_DEFAULT

View File

@ -1,348 +0,0 @@
#!/usr/bin/env python3
"""
本地化轻量级数据库封装
使用SQLite + SQLAlchemy ORM
支持基础CRUD操作通过继承BaseDao快速实现各表的Dao层
"""
import datetime
import sys
from enum import Enum
from typing import Any, Dict, List, Optional, Type, TypeVar
from sqlalchemy import create_engine, Column, Integer, String, DateTime, func, Select, Table, MetaData, select, or_
from sqlalchemy.orm import declarative_base
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy.sql.expression import text
from skills.smyx_common.scripts.config import ConstantEnum, ApiEnum
from skills.smyx_common.scripts.util import StringUtil, DatetimeUtil, FileUtil
from skills.smyx_common.scripts.base import BaseMixin, BaseDao
# 基础模型类
Base = declarative_base()
# 泛型类型,用于返回对应模型实例
T = TypeVar('T', bound=Base)
meta = MetaData()
DATABASE_URL = ApiEnum.DATABASE_URL
class BaseModelMixin(BaseMixin):
@classmethod
def load(cls, source: dict):
"""
获取源枚举
:param source:
:return: User
"""
column_names = cls.__table__.columns.keys()
user_dict = {k: source.get(StringUtil.snake_to_camel(k)) for k in column_names}
user_dict["create_time"] = DatetimeUtil.parse(user_dict["create_time"])
user_dict["update_time"] = DatetimeUtil.parse(user_dict["update_time"])
model = cls(**user_dict)
return model
class Dao(BaseDao):
"""
基础Dao类提供通用的CRUD操作
子类只需配置__model__和__tablename__即可使用
"""
__model__: Type[T] = None # 对应的模型类,子类必须配置
__tablename__: str = None # 表名,子类必须配置
def get_db_path(self, db_path):
import os
cwd = os.getcwd()
workspace = os.path.dirname(cwd)
workspace = os.path.dirname(workspace)
workspace = os.environ.get('OPENCLAW_WORKSPACE', workspace)
parent_dir = os.path.join(workspace, "data")
FileUtil.mkdir(parent_dir)
db_path = os.path.join(parent_dir, db_path)
return db_path
def __init__(self, db_path: str = None):
"""
初始化Dao
:param db_path: SQLite数据库文件路径
"""
if not db_path:
db_path = "smyx-common-claw.db"
db_path = self.get_db_path(db_path)
self.engine = create_engine(f"sqlite:///{db_path}", echo=False)
# 创建会话工厂
self.SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=self.engine)
# 初始化表结构
self._create_tables()
self._alter_tables()
def _create_tables(self) -> None:
"""创建所有表结构"""
Base.metadata.create_all(bind=self.engine)
def _alter_tables(self) -> None:
"""创建所有表结构"""
sql_statement = "ALTER TABLE sys_user ADD COLUMN source_id INT;"
# 3. 执行语句
try:
with self.engine.connect() as connection:
connection.execute(text(sql_statement))
connection.commit() # 对于数据定义语言(DDL),需要显式提交
except Exception as e:
connection.rollback()
if len(e.args) and "duplicate column name" in e.args[0]:
pass
else:
raise
def get_session(self) -> Session:
"""获取数据库会话"""
return self.SessionLocal()
def save(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
try:
return self.add(
model
)
except Exception as e:
return self.update(
model
)
def add(self, model) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
session = self.get_session()
try:
session.add(model)
session.commit()
session.refresh(model)
return model
finally:
session.close()
def create(self, **kwargs) -> T:
"""
创建新记录
:param kwargs: 字段键值对
:return: 创建的模型实例
"""
instance = self.__model__(**kwargs)
return self.add(instance)
def get_by_id(self, record_id: int) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
return session.query(self.__model__).filter(self.__model__.id == record_id).first()
finally:
session.close()
def get_by_username(self, username: str) -> Optional[T]:
"""
根据ID查询记录
:param record_id: 记录ID
:return: 模型实例或None
"""
session = self.get_session()
try:
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None) # 关键:使用 .is_(None) 来判断 SQL 的 NULL
)
return session.query(self.__model__).filter(self.__model__.username == username,
or_(
self.__model__.del_flag == 0,
self.__model__.del_flag.is_(None)
# 关键:使用 .is_(None) 来判断 SQL 的 NULL
)).first()
finally:
session.close()
def list(self, filters: Optional[Dict[str, Any]] = None, limit: Optional[int] = None,
offset: Optional[int] = None) -> List[T]:
"""
查询记录列表
:param filters: 过滤条件字典{"name": "张三", "age": 18}
:param limit: 最大返回数量
:param offset: 偏移量
:return: 模型实例列表
"""
session = self.get_session()
try:
query = session.query(self.__model__)
# .where(self.__model__.id != 2, self.__model__.id == 1))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
if offset:
query = query.offset(offset)
if limit:
query = query.limit(limit)
return query.all()
finally:
session.close()
def update(self, model) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == model.id).first()
if not instance:
return None
column_names = self.__model__.__table__.columns.keys()
for key in column_names:
value = getattr(model, key)
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def modify(self, record_id: int, **kwargs) -> Optional[T]:
"""
更新记录
:param record_id: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def update_by_username(self, username: str, **kwargs) -> Optional[T]:
"""
更新记录
:param username: 记录ID
:param kwargs: 要更新的字段键值对
:return: 更新后的模型实例或None
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.username == username).first()
if not instance:
return None
for key, value in kwargs.items():
setattr(instance, key, value)
session.commit()
session.refresh(instance)
return instance
finally:
session.close()
def delete(self, record_id: int) -> bool:
"""
删除记录
:param record_id: 记录ID
:return: 删除成功返回True失败返回False
"""
session = self.get_session()
try:
instance = session.query(self.__model__).filter(self.__model__.id == record_id).first()
if not instance:
return False
session.delete(instance)
session.commit()
return True
finally:
session.close()
def count(self, filters: Optional[Dict[str, Any]] = None) -> int:
"""
统计记录数量
:param filters: 过滤条件字典
:return: 记录数量
"""
session = self.get_session()
try:
query = session.query(func.count(self.__model__.id))
if filters:
for key, value in filters.items():
query = query.filter(getattr(self.__model__, key) == value)
return query.scalar()
finally:
session.close()
class User(Base, BaseModelMixin):
"""用户模型"""
__tablename__ = "sys_user"
id = Column(String(32), primary_key=True, index=True)
source_id = Column(String(32), comment="源头id")
username = Column(String(100), unique=True, index=True, nullable=False, comment="用户名")
email = Column(String(45), unique=True, index=True, comment="邮箱")
birthday = Column(DateTime, unique=True, index=True, comment="邮箱")
sex = Column(Integer, comment="性别")
age = Column(Integer, comment="年龄")
token = Column(String(500), comment="token")
open_token = Column(String(1000), comment="开放token")
source = Column(String(50), comment="token")
del_flag = Column(Integer, comment="是否删除", default=0)
create_time = Column(DateTime, default=func.now(), comment="创建时间")
update_time = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")
SourceEnum = ConstantEnum.SourceEnum
class UserDao(Dao):
"""用户Dao继承BaseDao即可拥有所有基础CRUD功能"""
__model__ = User
__tablename__ = "users"
if __name__ == "__main__":
pass

View File

@ -1,82 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import subprocess
from .config import ApiEnum as ApiEnumBase, ConstantEnum
from .base import BaseSkill
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from .util import FileUtil
from .api_service import ApiService
class Skill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
class AgentSkill(BaseSkill, ApiService):
def __init__(self):
super().__init__()
def ai_chat(self, prompt: str, session_id: str = None, timeout: int = 120):
"""
通过 subprocess 调用 openclaw agent 命令
Args:
prompt: 分析提示
session_id: 会话 ID可选
timeout: 超时时间
Returns:
分析结果或会话 ID
"""
import uuid
# 生成唯一会话 ID
if not session_id:
entry_script = sys.argv[0]
abs_entry_script = os.path.abspath(entry_script)
main_name = FileUtil.get_name(abs_entry_script)
session_id = f"{main_name}--{uuid.uuid4()}"
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent (会话:{session_id})..., prompt:{prompt}")
# 构建命令
cmd = [
"openclaw",
"agent",
"-m", str(prompt),
"--session-id", session_id,
"--thinking", "minimal",
"--timeout", str(timeout)
]
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行命令{' '.join(cmd)}")
try:
# 执行命令
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=timeout + 10
)
if result.stderr:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{result.stderr}")
return
output = result.stdout
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行成功:{output}")
return output
except subprocess.TimeoutExpired as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 超时({timeout}秒),任务可能仍在后台运行:{e}")
except Exception as e:
ConstantEnum.is_debug() and print(f"🤖 正在调用 openclaw agent 执行错误:{e}")
skill = Skill()

View File

@ -1,413 +0,0 @@
#!/usr/bin/env python3
import json
import os
import traceback
import requests
from .config import ApiEnum, ConstantEnum, sys, YamlUtil
from .base import BaseUtil
import time
import logging
from typing import Any, Callable, Optional, TypeVar, Dict
import pydash as _
if ConstantEnum.is_debug():
import http.client
# 【关键代码】开启调试模式
http.client.HTTPConnection.debuglevel = 1
# 可选:如果你希望日志更整洁,可以配合 logging 模块(否则打印会比较乱)
import logging
logging.basicConfig()
logging.getLogger().setLevel(logging.DEBUG)
requests_log = logging.getLogger("urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
class StringUtil(BaseUtil):
@staticmethod
def camel_to_snake(name):
import re
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
@staticmethod
def snake_to_pascal(name):
import re
name = re.sub(r'^([a-z])', lambda m: m.group(1).upper(), name)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
@staticmethod
def snake_to_camel(name):
import re
# 逻辑:匹配 '_[a-z]' (下划线+小写字母),将其替换为对应的大写字母(去掉下划线)
return re.sub(r'_([a-z])', lambda m: m.group(1).upper(), name)
class FileUtil(BaseUtil):
@staticmethod
def get_fullname(path):
try:
return os.path.basename(path)
except Exception as e:
CommonUtil.trace_exception_stack(e)
return ""
@staticmethod
def get_name(path):
try:
return os.path.splitext(os.path.basename(path))[0]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def get_ext(path):
try:
return os.path.splitext(os.path.basename(path))[1]
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def open(path):
try:
return open(path, 'w', encoding='utf-8')
except Exception as e:
CommonUtil.trace_exception_stack(e)
@staticmethod
def mkdir(path):
try:
os.makedirs(path, exist_ok=True)
except Exception as e:
CommonUtil.trace_exception_stack(e)
class JsonUtil(BaseUtil):
@staticmethod
def stringify(json_obj, default_str=""):
try:
return json.dumps(json_obj, ensure_ascii=False, indent=2)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_str
@staticmethod
def parse(json_str, default_json={}):
try:
return json.loads(json_str)
except Exception as e:
CommonUtil.trace_exception_stack(e)
pass
return default_json
class CommonUtil(BaseUtil):
@staticmethod
def trace_exception_stack(e):
if ConstantEnum.is_debug():
print(f"❌ 错误描述: {str(e)}, 堆栈跟踪:")
traceback.print_stack()
@staticmethod
def polling(
action: Callable[[], Any],
check_condition: Callable[[Any], bool],
on_success: Optional[Callable[[Any], None]] = None,
on_retry: Optional[Callable[[Any, int], None]] = None,
on_error: Optional[Callable[[Exception], None]] = None,
interval: float = 1.0,
max_attempts: int = 5,
description: str = "轮询任务"
) -> Optional[Any]:
"""
通用的轮询处理函数
:param action:
[必填] 执行动作的回调函数
例如发送 HTTP 请求查询数据库状态等
必须返回一个结果对象供 check_condition 使用
:param check_condition:
[必填] 检查是否结束的回调函数
接收 action 的返回值返回 True 表示满足结束条件False 表示继续轮询
例如lambda res: res.get('need_refresh') is False
:param on_success:
[可选] check_condition 返回 True 时执行的回调通常用于记录日志或处理最终数据
:param on_retry:
[可选] 当需要继续轮询时执行的回调
参数(当前结果, 当前尝试次数)可用于打印进度
:param on_error:
[可选] action 抛出异常时执行的回调
参数(异常对象)
:param interval:
每次轮询之间的等待时间
:param max_attempts:
最大尝试次数防止死循环
:param description:
任务描述用于日志输出
:return:
如果成功返回 action 的最后一次返回值如果超时或失败返回 None
"""
attempts = 0
print(f"🚀 开始执行 [{description}]...")
while attempts < max_attempts:
attempts += 1
try:
# 1. 执行动作
result = action()
last_result = result
# 2. 检查条件
if check_condition(result):
print(f"✅ [{description}] 成功!条件已满足 (尝试次数: {attempts}, 耗时{interval * attempts}秒)")
if on_success:
on_success(result)
return result
# 3. 条件未满足,准备重试
if on_retry:
on_retry(result, attempts)
else:
# 默认日志行为
print(
f"⏳ [{description}] 条件未满足,{interval}秒后重试... ({attempts}/{max_attempts}, 耗时{interval * attempts}秒)")
time.sleep(interval)
except Exception as e:
# 4. 异常处理
if on_error:
on_error(e)
else:
# 默认错误行为:打印错误并继续
logging.error(f"❌ [{description}] 发生异常: {e}")
print(f"⚠️ [{description}] 遇到错误,{interval}秒后重试...")
time.sleep(interval)
# 5. 超时处理
print(f"⚠️ [{description}] 失败:达到最大尝试次数 ({max_attempts}),强制停止。")
return None
@staticmethod
def is_empty(data):
# 1. 如果是 None (对应 JSON 的 null)
if data is None:
return True
# 2. 如果是字典或列表,且长度为 0 (对应 {} 或 [])
if isinstance(data, (dict, list)) and len(data) == 0:
return True
from datetime import date, datetime
class DatetimeUtil(BaseUtil):
FORMAT__DATETIME = "%Y-%m-%d %H:%M:%S"
@staticmethod
def now_str():
return DatetimeUtil.format(DatetimeUtil.now())
@staticmethod
def today_str():
return DatetimeUtil.format_date(DatetimeUtil.today())
@staticmethod
def now():
return datetime.now()
@staticmethod
def today():
return DatetimeUtil.now().replace(hour=0, minute=0, second=0, microsecond=0)
@staticmethod
def format(date):
return date.strftime('%Y-%m-%d %H:%M:%S') if type(date) == datetime else date
@staticmethod
def format_date(date):
return date.strftime('%Y-%m-%d') if type(date) == datetime else date
@staticmethod
def parse(date_str):
if type(date_str) == int:
return datetime.fromtimestamp(date_str)
return datetime.strptime(date_str, DatetimeUtil.FORMAT__DATETIME) if type(date_str) == str else date_str
@staticmethod
def timestamp(date=now()):
return int(date.timestamp() * 1000)
class RequestUtil(BaseUtil):
BASE_URL = ApiEnum.BASE_URL_OPEN_API
AUTHORIZATION_RETRY_COUNT_MAX = 3
authorization_retry_count = 0
@classmethod
def http_post(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("post", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_put(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("put", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_delete(cls, url, data=None, params=None, headers=None, *args, **argss):
return cls.http_request("delete", url, data=data, params=params, headers=headers, *args, **argss)
@classmethod
def http_get(cls, url, params=None, headers=None, *args, **argss):
return cls.http_request("get", url, params=params, headers=headers, *args, **argss)
@classmethod
def http_request(cls, method, url, data=None, params=None, headers=None, options=None, *args,
timeout=ApiEnum.DEFAULT__REQUEST_TIMEOUT, **argss):
def _get_or_create_user(username):
_url = ApiEnum.BASE_URL_HEALTH + "/sys/phoneLogin"
open_id = username
_data = {
"silent": 1,
"register": 1,
"openId": open_id,
"mobile": username
}
try:
_response = requests.post(_url, json=_data)
if _response.status_code == 200:
_response_json = _response.json()
if _response_json and _response_json.get("success"):
return _response_json and _response_json.get("result")
except Exception as _e:
CommonUtil.trace_exception_stack(_e)
return {}
try:
headers = headers or {}
if not url.startswith("https://") and not url.startswith("http://"):
url = cls.BASE_URL + url
headers['App-Id'] = ConstantEnum.APP__ID
# ConstantEnum.CURRENT__USER_NAME = ConstantEnum.CURRENT__OPEN_ID = "ou_86fdd8e0d5f116c18a9dd550abefe6d2"
current__user_name = ApiEnum.API_SECRET_KEY or ConstantEnum.CURRENT__USER_NAME or ConstantEnum.CURRENT__OPEN_ID
if (not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN) and current__user_name:
try:
from .dao import UserDao, User
user_dao = UserDao()
found_user = user_dao.get_by_username(current__user_name)
if found_user:
ApiEnum.TOKEN = found_user.token
ApiEnum.OPEN_TOKEN = found_user.open_token
if not ApiEnum.TOKEN or not ApiEnum.OPEN_TOKEN:
new_current_user = _get_or_create_user(current__user_name)
if new_current_user:
ApiEnum.TOKEN = new_current_user.get("token")
ApiEnum.OPEN_TOKEN = new_current_user.get("openToken")
current_user_info = new_current_user.get("userInfo")
if current_user_info:
current_user_info["token"] = new_current_user.get("token")
current_user_info["openToken"] = new_current_user.get(
"openToken")
user_model = User.load(current_user_info)
user = user_dao.save(
user_model
)
except Exception as e:
CommonUtil.trace_exception_stack(e)
raise
headers.setdefault("X-Access-Token", ApiEnum.TOKEN)
headers.setdefault("X-Api-Key", ApiEnum.API_SECRET_KEY)
headers.setdefault("Authorization", ApiEnum.OPEN_TOKEN)
data = data or {}
params = params or {}
options = options or {}
ConstantEnum.CURRENT__TENTANT_CODE and data.setdefault('tenantCode', ConstantEnum.CURRENT__TENTANT_CODE)
ConstantEnum.DEFAULT__SKILL_HUB_NAME and data.setdefault('skillHubName',
ConstantEnum.DEFAULT__SKILL_HUB_NAME)
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME and data.setdefault('skillPlatform',
ConstantEnum.DEFAULT__SKILL_PLATFORM_NAME)
if current__user_name:
data.setdefault('pnaUserName', current__user_name)
if bool(options.get("dataAsParams")):
params.update(data)
print(f"🔄 请求拦截, URL:{url}", "method", method, "params", params, "data", data, "headers", headers,
"options", options,
"timeout",
timeout)
response = requests.request(method, url, *args, json=data, params=params, headers=headers,
timeout=int(timeout), **argss)
response_text = response.text if ConstantEnum.is_debug() else response
if response.status_code == 401 and cls.authorization_retry_count < cls.AUTHORIZATION_RETRY_COUNT_MAX:
print(f"❌ 请求拦截, 鉴权:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
ApiEnum.TOKEN = ApiEnum.OPEN_TOKEN = None
if found_user:
found_user.token = found_user.open_token = None
user_dao.update(found_user)
cls.authorization_retry_count += 1
return cls.http_request(method, url, data, params, headers, options, *args, timeout=timeout, **argss)
elif response.status_code != 200:
raise requests.exceptions.RequestException(
response, response=response)
response_json = response.json()
if not bool(response_json['success']):
raise requests.exceptions.RequestException(
response, response=response)
response_json_data = response_json.get("data", response_json.get("result"))
response_json_data = response_json_data.get("records") if response_json_data and type(
response_json_data) == dict and "records" in response_json_data else response_json_data
print(f"✅ 请求拦截, 成功:{response_text}, url:{url}", "method", method, "params", params,
"data",
data,
"headers",
headers,
"timeout",
timeout)
return response_json_data
except Exception as e:
CommonUtil.trace_exception_stack(e)
response_text = _.get(e.args, '0.text')
print(
f"❌ 请求拦截, 失败: {e}, e.response.text: {response_text}, url:{url}",
"method",
method,
"params",
params,
"data", data, "headers",
"response", hasattr(e, 'response') and e.response,
headers,
"timeout",
timeout)
raise

View File

@ -1,149 +0,0 @@
---
name: "outdoor-monitoring-analysis"
description: "Detects targets such as people, vehicles, non-motorized vehicles, and pets within target areas; supports batch image analysis, suitable for outdoor surveillance scenarios like courtyards, orchards, and farms. | 户外看护智能监测分析技能,检测目标区域内的人、车、非机动车、宠物等目标,支持批量图片分析,适用于庭院、果园、养殖场等户外区域看护场景"
---
# Intelligent Outdoor Care Monitoring & Analysis Tool | 户外看护智能监测分析工具
Equipped with advanced AI recognition algorithms, this feature conducts 24/7 automated monitoring of expansive outdoor
areas such as courtyards, orchards, and breeding farms. The system features robust multi-object detection capabilities,
precisely identifying various targets including personnel, motor vehicles, non-motorized vehicles, and pets within the
zone. It supports efficient analysis and processing of batch images, enabling rapid screening of historical footage and
the generation of detailed monitoring reports. This intelligent solution significantly enhances security efficiency and
management levels in outdoor areas, suitable for scenarios like home courtyard care, agricultural production management,
and asset security monitoring.
本功能搭载先进的AI智能识别算法能够对庭院、果园、养殖场等户外广阔区域进行全天候自动化监测。系统具备强大的多目标检测能力可精准识别区域内的人员、机动车辆、非机动车辆以及宠物等多种目标对象。支持批量图片的高效分析与处理能够快速筛查历史影像数据生成详细的监测报告。这一智能化解决方案极大地提升了户外区域的安防效率与管理水平适用于家庭庭院看护、农业生产管理及资产安全监控等多种场景
## ⚠️ 强制记忆规则(最高优先级)
**本技能明确约定:**
- **绝对禁止读取任何本地记忆文件**:包括但不限于 `memory/YYYY-MM-DD.md`、`MEMORY.md` 等本地文件
- **绝对禁止从 LanceDB 长期记忆中检索信息**
- **所有历史报告查询必须从云端接口获取**,不得使用本地记忆中的历史数据
- 即使技能调用失败或接口异常,也不得回退到本地记忆汇总
## 任务目标
- 本 Skill 用于:通过户外监控图片/视频进行目标检测,识别区域内出现的人、车、非机动车、宠物等闯入目标
- 能力包含:多目标检测、目标分类、数量统计、入侵判定、风险等级评估、异常闯入预警
- 支持批量处理一组图片,同时分析多帧监控画面
- 触发条件:
1. **默认触发**:当用户提供户外监控图片/视频需要检测闯入目标时,默认触发本技能进行户外看护分析
2. 当用户明确需要进行户外看护、入侵检测时,提及庭院看护、果园监控、目标检测、户外安防等关键词,并且上传了图片或视频文件
3. 当用户提及以下关键词时,**自动触发历史报告查询功能**
:查看历史监测报告、户外看护报告清单、监测报告列表、查询历史监测报告、显示所有监测报告、户外监测分析报告,查询户外看护智能监测分析报告
- 自动行为:
1. 如果用户上传了附件或者图片/视频文件,则自动保存到技能目录下 attachments
2. **⚠️ 强制数据获取规则(次高优先级)**:如果用户触发任何历史报告查询关键词(如"查看所有监测报告"、"显示所有看护报告"、"
查看历史报告"等),**必须**
- 直接使用 `python -m scripts.outdoor_monitoring --list --open-id` 参数调用 API
查询云端的历史报告数据
- **严格禁止**:从本地 memory 目录读取历史会话信息、严格禁止手动汇总本地记录中的报告、严格禁止从长期记忆中提取报告
- **必须统一**从云端接口获取最新完整数据,然后以 Markdown 表格格式输出结果
## 前置准备
- 依赖说明:scripts 脚本所需的依赖包及版本
```
requests>=2.28.0
```
## 操作步骤
### 🔒 open-id 获取流程控制(强制执行,防止遗漏)
**在执行户外看护分析前,必须按以下优先级顺序获取 open-id**
```
第 1 步:【最高优先级】检查技能所在目录的配置文件(优先)
路径skills/smyx_common/scripts/config.yaml相对于技能根目录
完整路径示例:${OPENCLAW_WORKSPACE}/skills/{当前技能目录}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置/api-key 为空)
第 2 步:检查 workspace 公共目录的配置文件
路径:${OPENCLAW_WORKSPACE}/skills/smyx_common/scripts/config.yaml
→ 如果文件存在且配置了 api-key 字段,则读取 api-key 作为 open-id
↓ (未找到/未配置)
第 3 步:检查用户是否在消息中明确提供了 open-id
↓ (未提供)
第 4 步:❗ 必须暂停执行,明确提示用户提供用户名或手机号作为 open-id
```
**⚠️ 关键约束:**
- **禁止**自行假设,自行推导,自行生成 open-id 值(如 openclaw-control-ui、default、outdoor123、monitor456 等)
- **禁止**跳过 open-id 验证直接调用 API
- **必须**在获取到有效 open-id 后才能继续执行分析
- 如果用户拒绝提供 open-id说明用途用于保存和查询监测报告记录并询问是否继续
---
- 标准流程:
1. **准备图片/视频输入**
- 提供本地图片/视频文件路径或网络 URL
- 支持批量上传一组图片同时分析
- 确保监控画面覆盖完整目标监测区域
2. **获取 open-id强制执行**
- 按上述流程控制获取 open-id
- 如无法获取,必须提示用户提供用户名或手机号
3. **执行户外看护智能监测分析**
- 调用 `-m scripts.outdoor_monitoring` 处理输入(**必须在技能根目录下运行脚本**
- 参数说明:
- `--input`: 本地图片/视频文件路径(使用 multipart/form-data 方式上传)
- `--url`: 网络图片/视频 URL 地址API 服务自动下载)
- `--open-id`: 当前用户的 open-id必填按上述流程获取
- `--list`: 显示历史户外看护监测分析报告列表清单(可以输入起始日期参数过滤数据范围)
- `--api-key`: API 访问密钥(可选)
- `--api-url`: API 服务地址(可选,使用默认值)
- `--detail`: 输出详细程度basic/standard/json默认 json
- `--output`: 结果输出文件路径(可选)
4. **查看分析结果**
- 接收结构化的户外看护智能监测分析报告
- 包含:监控基本信息、检测到的目标类型、目标数量、位置分布、是否异常闯入、风险等级、处置建议
## 资源索引
- 必要脚本:见 [scripts/outdoor_monitoring.py](scripts/outdoor_monitoring.py)(用途:调用 API 进行户外看护智能监测分析,本地文件使用
multipart/form-data 方式上传,网络 URL 由 API 服务自动下载)
- 配置文件:见 [scripts/config.py](scripts/config.py)(用途:配置 API 地址、默认参数和格式限制)
- 领域参考:见 [references/api_doc.md](references/api_doc.md)(何时读取:需要了解 API 接口详细规范和错误码时)
## 注意事项
- 仅在需要时读取参考文档,保持上下文简洁
- 支持格式jpg/jpeg/png/mp4/avi/mov最大 100MB支持批量图片分析
- API 密钥可选,如果通过参数传入则必须确保调用鉴权成功,否则忽略鉴权
- 分析结果仅供安防参考,不能替代专业安保措施,发现可疑闯入请及时报警
- 禁止临时生成脚本,只能用技能本身的脚本
- 传入的网路地址参数不需要下载本地默认地址都是公网地址api 服务会自动下载
- 当显示历史分析报告清单的时候,从数据 json 中提取字段 reportImageUrl 作为超链接地址,使用 Markdown 表格格式输出,包含"
报告名称"、"输入类型"、"分析时间"、"检测目标数"、"风险等级"、"点击查看"六列,其中"报告名称"列使用`户外看护监测分析报告-{记录id}`
形式拼接, "点击查看"列使用
`[🔗 查看报告](reportImageUrl)`
格式的超链接,用户点击即可直接跳转到对应的完整报告页面。
- 表格输出示例:
| 报告名称 | 输入类型 | 分析时间 | 检测目标数 | 风险等级 | 点击查看 |
|----------|----------|----------|------------|----------|----------|
| 户外看护监测分析报告 -20260328221000001 | 多图 | 2026-03-28 22:10:00 | 2人+1车 |
中风险 | [🔗 查看报告](https://example.com/report?id=xxx) |
## 使用示例
```bash
# 分析单张监控图片以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.outdoor_monitoring --input /path/to/yard.jpg --open-id openclaw-control-ui
# 分析网络监控视频以下只是示例禁止直接使用openclaw-control-ui 作为 open-id
python -m scripts.outdoor_monitoring --url https://example.com/garden.mp4 --open-id openclaw-control-ui
# 显示历史分析报告/显示分析报告清单列表/显示历史监测报告(自动触发关键词:查看历史监测报告、历史报告、监测报告清单等)
python -m scripts.outdoor_monitoring --list --open-id openclaw-control-ui
# 输出精简报告
python -m scripts.outdoor_monitoring --input capture.jpg --open-id your-open-id --detail basic
# 保存结果到文件
python -m scripts.outdoor_monitoring --input capture.jpg --open-id your-open-id --output result.json
```

View File

@ -1,11 +0,0 @@
{
"owner": "18072937735",
"slug": "smyx-outdoor-monitoring-analysis",
"displayName": "Intelligent Outdoor Care Monitoring & Analysis Tool | 户外看护智能监测分析工具",
"latest": {
"version": "1.0.0",
"publishedAt": 1776314371084,
"commit": "https://github.com/openclaw/skills/commit/539926e12970ba32a8d97826203327b8ffaee85d"
},
"history": []
}

View File

@ -1,21 +0,0 @@
# API 接口文档
此处用于存放户外看护智能监测分析 API 的接口文档,待后续补充。
## 接口规范
- 基础地址:由 smyx_common 配置统一管理
- 认证方式API Key 鉴权
- 请求格式multipart/form-data 支持文件上传
- 响应格式JSON
## 主要接口
1. `/web/ai-analysis/v2/start-common-ai-analysis` - 启动AI分析任务
2. `/web/ai-analysis/v2/get-common-ai-analysis-result` - 获取分析结果
3. `/web/ai-analysis/page-common-ai-analysis-result` - 分页查询历史报告
4. `/ai/order/api/getReportDetailExport?id={id}` - 导出完整报告
## 场景代码
- `OPEN_OUTDOOR_MONITORING_ANALYSIS` - 开放平台户外看护监测分析

View File

@ -1 +0,0 @@
# Pet Analysis scripts package

View File

@ -1,53 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.smyx_common.scripts.util import RequestUtil
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, *args, **argss):
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
# params.setdefault("scene", scene_code)
# 添加宠物类型参数
if ConstantEnum.DEFAULT__PET_TYPE:
params.setdefault("petType", ConstantEnum.DEFAULT__PET_TYPE)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
# 户外看护智能监测分析工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ConstantEnum as ConstantEnumBase
from skills.face_analysis.scripts.config import ApiEnum as ApiEnumParent, ConstantEnum as ConstantEnumParent, \
ApiEnumCommonAiMixin
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumCommonAiMixin, ApiEnumParent):
pass
class ConstantEnum(ConstantEnumParent):
@classmethod
def init(cls, config=None):
super().init(config)
ConstantEnumParent.DEFAULT__SCENE_CODE = SceneCodeEnum.OUTDOOR_MONITORING_ANALYSIS.value

View File

@ -1,97 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
from skills.smyx_common.scripts.util import RequestUtil
def analyze_monitor(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
input_path = input_path or url
return skill.get_output_analysis(input_path)
def show_analyze_list(open_id, start_time=None, end_time=None):
output_content = skill.get_output_analysis_list()
return output_content
def main():
parser = argparse.ArgumentParser(description="户外看护智能监测分析工具")
parser.add_argument("--input", help="本地图片/视频文件路径")
parser.add_argument("--url", help="网络图片/视频URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示户外看护监测分析列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
# 设置 Python 进程内的环境变量
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在监测户外目标,请稍候...")
output_content = analyze_monitor(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 户外看护监测分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,23 +0,0 @@
#!/usr/bin/env python3
import json
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
from skills.face_analysis.scripts.skill import Skill as SkillParent
from skills.smyx_common.scripts.util import JsonUtil
class Skill(SkillParent):
def __init__(self):
super().__init__()
def get_output_analysis_content_head(self, result=None):
return f"📊 户外看护智能监测分析结构化结果"
def get_output_analysis_content_foot(self, result):
pass
skill = Skill()

View File

@ -1,86 +0,0 @@
# 中医面诊分析工具 (face-analysis)
## 技能介绍
这是一个基于AI的中医面诊分析技能可以通过面部视频自动分析健康状况返回结构化的诊断结果和养生建议。
## 快速开始
### 1. 配置API信息
编辑 `scripts/config.py`设置你的API地址和密钥
```python
DEFAULT_API_URL = "https://your-api-server.com/api/v1/face-analysis"
DEFAULT_API_KEY = "your-api-key-here"
```
### 2. 分析本地视频
```bash
python scripts/face_analysis.py --input /path/to/your/video.mp4
```
### 3. 分析网络视频
```bash
python scripts/face_analysis.py --url https://example.com/video.mp4
```
## 功能特性
- ✅ 支持本地MP4视频上传
- ✅ 支持网络视频URL分析
- ✅ 三种输出详细程度:精简/标准/完整
- ✅ 结构化JSON结果输出
- ✅ 自动保存结果到文件
- ✅ 内置视频格式和大小校验
## 目录结构
```
face-analysis/
├── SKILL.md # 技能说明文件(系统自动加载)
├── README.md # 本说明文件
├── scripts/
│ ├── face_analysis.py # 主程序
│ └── config.py # 配置文件
├── references/
│ ├── api_doc.md # API接口文档
│ ├── tcm_theory.md # 中医面诊理论参考
│ └── faq.md # 常见问题
└── assets/
└── template.json # 返回结果模板
```
## 使用示例
### 标准输出
```
📊 中医面诊分析报告
==================================================
⏰ 分析时间: 2026-03-10 15:30:00
🎯 人脸检测: success (置信度: 95分)
🔍 诊断结果:
整体体质: 平和质
脏腑状况:
liver: 正常
heart: 轻微火旺
spleen: 略虚
lung: 正常
kidney: 正常
面色分析: 微黄
对应提示: 脾胃功能略弱
⚠️ 健康警示:
⚠️ 注意休息,避免熬夜
💡 养生建议:
💡 饮食清淡,减少辛辣食物摄入
💡 保持规律作息每晚11点前入睡
💡 适当进行有氧运动,如散步、太极拳
==================================================
```
### 输出到JSON文件
```bash
python scripts/face_analysis.py --input video.mp4 --detail full --output result.json
```
## 注意事项
1. 视频要求清晰正面面部光线充足时长5-30秒为宜
2. 支持格式mp4、avi、mov最大100MB
3. API需要自行部署或接入第三方服务
4. 结果仅供参考,不能替代专业医生诊断

View File

@ -1,69 +0,0 @@
# API接口文档
## 接口地址
`POST https://your-api-server.com/api/v1/face-analysis`
## 请求头
| 字段 | 必选 | 说明 |
|------|------|------|
| X-API-Key | 是 | API访问密钥 |
| Content-Type | 是 | multipart/form-data文件上传或 application/jsonURL模式 |
## 请求参数
### 1. 文件上传模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video | file | 是 | MP4视频文件 |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
### 2. URL模式
| 字段 | 类型 | 必选 | 说明 |
|------|------|------|------|
| video_url | string | 是 | 可公开访问的视频URL |
| detail_level | string | 否 | 输出详细程度basic/standard/full默认standard |
## 响应格式
```json
{
"code": 200,
"message": "success",
"data": {
"analysis_time": "2026-03-10 15:30:00",
"face_detection": {
"status": "success",
"face_count": 1,
"quality_score": 95
},
"diagnosis": {
"overall_constitution": "平和质",
"organ_condition": {
"liver": "正常",
"heart": "轻微火旺",
"spleen": "略虚",
"lung": "正常",
"kidney": "正常"
},
"color_analysis": {
"complexion": "微黄",
"correspondence": "脾胃功能略弱"
}
},
"health_warnings": [
"注意休息,避免熬夜"
],
"suggestions": [
"饮食清淡,减少辛辣食物摄入"
]
}
}
```
## 错误码说明
| 错误码 | 说明 |
|--------|------|
| 400 | 请求参数错误 |
| 401 | API密钥无效 |
| 403 | 权限不足 |
| 413 | 文件过大 |
| 415 | 不支持的文件格式 |
| 500 | 服务器内部错误 |

View File

@ -1,3 +0,0 @@
pydash==8.0.6
SQLAlchemy==2.0.46
yaml==6.0.3

View File

@ -1,52 +0,0 @@
#!/usr/bin/env python3
import os
import sys
from .config import ApiEnum, ConstantEnum
from skills.smyx_common.scripts.api_service import ApiService as ApiServiceBase
class ApiService(ApiServiceBase):
def __init__(self):
super().__init__()
self.analysis_url = ApiEnum.ANALYSIS_URL
def analysis_result(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
scene_code and params.setdefault("sceneCode", scene_code)
return self.http_post(ApiEnum.ANALYSIS_RESULT_URL, *args, **argss)
def analysis(self, scene_code=ConstantEnum.DEFAULT__SCENE_CODE, *args, **argss):
params = argss.setdefault("params", {})
options = {
"dataAsParams": True
}
scene_code and params.setdefault("sceneCode", scene_code)
params.setdefault("appCategory", ConstantEnum.DEFAULT__APP_CATEGORY)
return self.http_post(self.analysis_url, options=options, *args, **argss)
def page(self, pageNum=None, pageSize=None, *args, **argss):
data = argss.setdefault("data", {})
ConstantEnum.DEFAULT__SCENE_CODE and data.setdefault("sceneCode", ConstantEnum.DEFAULT__SCENE_CODE)
data.setdefault("orderBy", {
"fieldName": "createTime",
"isAsc": False
})
return super().page(ApiEnum.PAGE_URL, pageNum, pageSize, *args, **argss)
def list(self, *args, **argss):
return super().list(None, *args, **argss)
def add(self, item: dict):
return super().add(ApiEnum.ADD_URL, item)
def edit(self, item: dict):
return super().edit(ApiEnum.EDIT_URL, item)
def delete(self, cameraSn):
data = {
"cameraSn": cameraSn
}
return super().delete(ApiEnum.DELETE_URL, data, options={"dataAsParams": True})

View File

@ -1,40 +0,0 @@
#!/usr/bin/env python3
# 中医面诊分析工具配置文件
import os
import sys
from enum import Enum
from skills.smyx_common.scripts.config import ApiEnum as ApiEnumBase, ConstantEnum as ConstantEnumBase
SceneCodeEnum = ConstantEnumBase.SceneCodeEnum
class ApiEnum(ApiEnumBase):
ANALYSIS_URL = "/web/health-analysis/v2/start-health-analysis"
ANALYSIS_RESULT_URL = "/web/health-analysis/get-health-analysis-result"
PAGE_URL = "/web/health-analysis/page-health-analysis-result"
DETAIL_EXPORT_URL = ApiEnumBase.BASE_URL_HEALTH + "/health/order/api/getReportDetailExport?id="
@classmethod
def init(cls, config=None):
super().init(config)
class ApiEnumCommonAiMixin:
@classmethod
def init(cls, config=None):
parent = super()
if hasattr(parent, "init"):
parent.init(config)
ApiEnum.ANALYSIS_URL = "/web/ai-analysis/v2/start-common-ai-analysis"
ApiEnum.ANALYSIS_RESULT_URL = "/web/ai-analysis/get-common-ai-analysis-result"
ApiEnum.PAGE_URL = "/web/ai-analysis/page-common-ai-analysis-result"
class ConstantEnum(ConstantEnumBase):
DEFAULT__APP_CATEGORY = "PEI_NI_AN"

View File

@ -1,205 +0,0 @@
#!/usr/bin/env python3
import sys
import os
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))
sys.path.insert(0, parent_dir)
import argparse
import json
import mimetypes
import traceback
from datetime import datetime
import requests
import sys
import os
from .config import *
from .skill import skill
# import_path_common()
from skills.smyx_common.scripts.util import RequestUtil
# 从config导入常量
SUPPORTED_FORMATS = ConstantEnum.SUPPORTED_FORMATS
MAX_FILE_SIZE_MB = ConstantEnum.MAX_FILE_SIZE_MB
def validate_file(file_path):
"""验证输入文件是否合法"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
if not os.access(file_path, os.R_OK):
raise PermissionError(f"文件没有读权限: {file_path}")
ext = os.path.splitext(file_path)[1].lower()[1:]
if ext not in SUPPORTED_FORMATS:
raise ValueError(f"不支持的文件格式,支持的格式: {', '.join(SUPPORTED_FORMATS)}")
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
if file_size_mb > MAX_FILE_SIZE_MB:
raise ValueError(f"文件过大,最大支持 {MAX_FILE_SIZE_MB}MB当前文件大小: {file_size_mb:.1f}MB")
return True
def analyze_video(input_path=None, url=None, api_url=None, api_key=None, output_level=None):
"""调用API分析视频"""
if not input_path and not url:
raise ValueError("必须提供本地视频路径(--input)或网络视频URL(--url)")
try:
input_path = input_path or url
return skill.get_output_analysis(input_path)
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def show_analyze_list(open_id, start_time=None, end_time=None):
# if not open_id:
# raise ValueError("必须提供本用户的OpenId/UserId")
try:
output_content = skill.get_output_analysis_list()
return output_content
except requests.exceptions.RequestException as e:
traceback.print_stack()
raise Exception(f"API请求失败: {str(e)}")
def get_analysis_export_url(request_id=None):
"""调用API分析视频"""
if not request_id:
return ""
return ApiEnum.DETAIL_EXPORT_URL + request_id
def format_result(result, output_level="standard"):
"""格式化输出结果"""
if output_level == "json":
result_id = None
# if result.get('success'):
if result is not None:
result_json = result
result_id = result_json.get('id', {})
result_json = json.dumps(result_json.get('faceAnalysisResponse', {}), ensure_ascii=False, indent=2)
else:
# result_json = json.dumps(result, ensure_ascii=False, indent=2)
return "⚠️ 暂无分析结果"
return f"""
📊 面诊分析结构化结果
{result_json}
""", result_id
elif output_level == "basic":
# 精简输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
return f"""
📊 面诊分析结果
{'=' * 40}
整体体质: {diagnosis.get('overall_constitution', '未知')}
主要状况: {', '.join([f'{k}: {v}' for k, v in diagnosis.get('organ_condition', {}).items() if v != '正常'])}
健康提示: {data.get('health_warnings', ['无特殊警示'])[0] if data.get('health_warnings') else '无特殊警示'}
"""
elif output_level == "standard":
# 标准输出
data = result.get('data', {})
diagnosis = data.get('diagnosis', {})
face_detection = data.get('face_detection', {})
organ_status = "\n".join([f" {k}: {v}" for k, v in diagnosis.get('organ_condition', {}).items()])
warnings = "\n".join([f" ⚠️ {item}" for item in data.get('health_warnings', [])])
suggestions = "\n".join([f" 💡 {item}" for item in data.get('suggestions', [])])
return f"""
📊 中医面诊分析报告
{'=' * 50}
分析时间: {data.get('analysis_time', '未知')}
🎯 人脸检测: {face_detection.get('status', '未知')} (置信度: {face_detection.get('quality_score', 0)})
🔍 诊断结果:
整体体质: {diagnosis.get('overall_constitution', '未知')}
脏腑状况:
{organ_status}
面色分析: {diagnosis.get('color_analysis', {}).get('complexion', '未知')}
对应提示: {diagnosis.get('color_analysis', {}).get('correspondence', '未知')}
健康警示:
{warnings}
💡 养生建议:
{suggestions}
{'=' * 50}
"""
else:
# 完整输出JSON格式
return json.dumps(result, ensure_ascii=False, indent=2)
def main():
parser = argparse.ArgumentParser(description="中医面诊分析工具")
parser.add_argument("--input", help="本地MP4视频文件路径")
parser.add_argument("--url", help="网络视频MP4的URL地址")
parser.add_argument("--open-id", required=True, help="当前用户的OpenID/UserId/用户名/手机号")
parser.add_argument("--list", action='store_true', help="显示面诊视频历史列表清单")
parser.add_argument("--api-url", help="服务端API地址")
parser.add_argument("--api-key", help="API访问密钥必需")
parser.add_argument("--output", help="结果输出文件路径")
parser.add_argument("--detail", choices=["basic", "standard", "json"],
default=ConstantEnum.DEFAULT__OUTPUT_LEVEL,
help="输出详细程度")
parser.add_argument("--export-env-only", action='store_true',
help="仅输出 export 命令设置环境变量,不执行分析")
args = parser.parse_args()
try:
if args.open_id:
ConstantEnumBase.CURRENT__OPEN_ID = args.open_id
# 检查必需参数
if args.list:
open_id = ConstantEnum.CURRENT__OPEN_ID
result = show_analyze_list(open_id)
print(result)
exit(0)
# 检查必需参数
if not args.input and not args.url:
print("❌ 错误: 必须提供 --input 或 --url 参数")
exit(1)
print("🔍 正在分析面诊视频,请稍候...")
output_content = analyze_video(
input_path=args.input,
url=args.url,
api_url=args.api_url,
api_key=args.api_key,
output_level=args.detail
)
print(output_content)
# 保存到文件
if args.output:
with open(args.output, "w", encoding="utf-8") as f:
if args.detail == "full":
json.dump(result, f, ensure_ascii=False, indent=2)
else:
f.write(output_content)
print(f"✅ 结果已保存到: {args.output}")
except Exception as e:
traceback.print_stack()
print(f"❌ 面诊分析失败: {str(e)}")
exit(1)
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show More