@symerian/symi 3.0.17 → 3.0.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{audio-preflight-CBDFctZN.js → audio-preflight-BfmZbg4Y.js} +4 -4
- package/dist/{audio-preflight-gsZSpG-6.js → audio-preflight-DcuC-liM.js} +4 -4
- package/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +8 -8
- package/dist/bundled/session-memory/handler.js +7 -7
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-nPMY1XTJ.js → chrome-Bo7cbvFK.js} +5 -5
- package/dist/{chrome-BjVab8gM.js → chrome-DYp18Q0t.js} +5 -5
- package/dist/{deliver-D-QFqm31.js → deliver-ChSIbiMM.js} +1 -1
- package/dist/{deliver-B4-bcot9.js → deliver-DEgRQM4J.js} +1 -1
- package/dist/extensionAPI.js +7 -7
- package/dist/{image-CDwtQjmt.js → image-Bx-hvoNJ.js} +1 -1
- package/dist/{image-CcS-vzTA.js → image-CQl_mjWk.js} +1 -1
- package/dist/llm-slug-generator.js +7 -7
- package/dist/{manager-BnEdHzmO.js → manager-D_pn0urG.js} +1 -1
- package/dist/{manager-09r0qPze.js → manager-YQxK2t0C.js} +1 -1
- package/dist/{pi-embedded-CWsY69-4.js → pi-embedded-CLw_ZzEZ.js} +16 -16
- package/dist/{pi-embedded-helpers-BBMy-lqr.js → pi-embedded-helpers-B5I53aw6.js} +4 -4
- package/dist/{pi-embedded-helpers-ChEYbgVj.js → pi-embedded-helpers-sUAEIC9X.js} +4 -4
- package/dist/plugin-sdk/{accounts-BfyWsC_i.js → accounts-CWFytwbR.js} +3 -3
- package/dist/plugin-sdk/{active-listener-DcJW7xAT.js → active-listener-BkZ4jHrL.js} +2 -2
- package/dist/plugin-sdk/{agent-scope-ChbGV6of.js → agent-scope-C9gfY_Gk.js} +2 -2
- package/dist/plugin-sdk/{audio-preflight-D3GtNLqW.js → audio-preflight-HKbdzXLZ.js} +21 -21
- package/dist/plugin-sdk/{bindings-CN2Qmefj.js → bindings-BaKIqPPy.js} +2 -2
- package/dist/plugin-sdk/{channel-web-DTyqujjA.js → channel-web-D5nWiTH1.js} +18 -18
- package/dist/plugin-sdk/{chrome-BKzAKr3K.js → chrome-klTSnz-9.js} +3 -3
- package/dist/plugin-sdk/{chunk-DhDkBujV.js → chunk-BbrYSny_.js} +1 -1
- package/dist/plugin-sdk/{command-format-CVrYFyZS.js → command-format-BN6tyZt6.js} +1 -1
- package/dist/plugin-sdk/{commands-registry-17yfZkHZ.js → commands-registry-CTzKKtY6.js} +4 -4
- package/dist/plugin-sdk/{config-7wk65zKC.js → config-Crv2qEdJ.js} +9 -9
- package/dist/plugin-sdk/{consolidate-exbAW0ml.js → consolidate-DT1QH65Q.js} +2 -2
- package/dist/plugin-sdk/{deliver-TxAcw7J5.js → deliver-7rOvAlrc.js} +12 -12
- package/dist/plugin-sdk/{diagnostic-Debx4frd.js → diagnostic-0nsxhWp7.js} +1 -1
- package/dist/plugin-sdk/{fs-safe-wBYbAkJF.js → fs-safe-DfWYBeWF.js} +1 -1
- package/dist/plugin-sdk/{gemini-auth-7U2pm2Ky.js → gemini-auth-C0N0_u49.js} +1 -1
- package/dist/plugin-sdk/{image-BtDVmYA5.js → image-WOSl2apK.js} +4 -4
- package/dist/plugin-sdk/index.js +43 -43
- package/dist/plugin-sdk/{ir-CKMvRrGW.js → ir-9J84MTls.js} +4 -4
- package/dist/plugin-sdk/{local-roots-c_gaPs01.js → local-roots-OLRDbvyY.js} +3 -3
- package/dist/plugin-sdk/{login-DUym1Jy0.js → login-C7x4q0i2.js} +7 -7
- package/dist/plugin-sdk/{login-qr-B-WBdvrX.js → login-qr-Dv5_MoAW.js} +9 -9
- package/dist/plugin-sdk/{manager-B71SCzos.js → manager-C83tK17x.js} +8 -8
- package/dist/plugin-sdk/{manifest-registry-Dnic6Chh.js → manifest-registry-CJMV-PI7.js} +1 -1
- package/dist/plugin-sdk/{markdown-tables-Dur7OTlM.js → markdown-tables-DXNKz5y_.js} +1 -1
- package/dist/plugin-sdk/{message-channel-BrAhJJV_.js → message-channel-aGy1HbQQ.js} +1 -1
- package/dist/plugin-sdk/{model-selection-B9qaVQSJ.js → model-selection-C-3-tpe7.js} +4 -4
- package/dist/plugin-sdk/{outbound-DB1wDM8b.js → outbound-DquCeSy5.js} +6 -6
- package/dist/plugin-sdk/{pi-auth-json-ZO118hoy.js → pi-auth-json-D9PDCXGn.js} +1 -1
- package/dist/plugin-sdk/{pi-embedded-helpers-s_U0Un7j.js → pi-embedded-helpers-D3ygfH7l.js} +16 -16
- package/dist/plugin-sdk/{plugins-DF81oSaI.js → plugins-DOwnSg9D.js} +4 -4
- package/dist/plugin-sdk/{pw-ai-CTwP02uv.js → pw-ai-rlengLjb.js} +8 -8
- package/dist/plugin-sdk/{qmd-manager-CBaSGant.js → qmd-manager-BzxFjRFa.js} +4 -4
- package/dist/plugin-sdk/{registry-CZVURNhF.js → registry-5iFfixlB.js} +2 -2
- package/dist/plugin-sdk/{replies-hwRbkU3z.js → replies-BXOzO_H5.js} +7 -7
- package/dist/plugin-sdk/{reply-prefix-CaXmzZlx.js → reply-prefix-INAKTqCU.js} +1 -1
- package/dist/plugin-sdk/{resolve-outbound-target-fxVSOBmk.js → resolve-outbound-target-DvbxHtqp.js} +2 -2
- package/dist/plugin-sdk/{resolve-route-ClCyiOeu.js → resolve-route-URXlY3AK.js} +3 -3
- package/dist/plugin-sdk/{runner-Cq5jvwQ7.js → runner-Bv0_DWoH.js} +9 -9
- package/dist/plugin-sdk/{session-B_TkB65Y.js → session-C3r8l7ou.js} +4 -4
- package/dist/plugin-sdk/{skill-commands-0LF9HTGr.js → skill-commands-KjLUGIdZ.js} +5 -5
- package/dist/plugin-sdk/{skills-BIT_O7J0.js → skills-BrsD4L5c.js} +7 -7
- package/dist/plugin-sdk/{sqlite-Bx5Y5U5X.js → sqlite-CjW7ME1H.js} +1 -1
- package/dist/plugin-sdk/{subsystem-CXqYeDy-.js → subsystem-DcOg1xJr.js} +1 -1
- package/dist/plugin-sdk/{synthesis-DtsYAj1E.js → synthesis-CY7YAasV.js} +38 -38
- package/dist/plugin-sdk/{target-errors-B8mokOeH.js → target-errors-BVWJGWFq.js} +2 -2
- package/dist/plugin-sdk/{thinking-Ca0DhqzO.js → thinking-CtsTDPOi.js} +3 -3
- package/dist/plugin-sdk/{tokens-CvlONEqh.js → tokens-8lqOTZCB.js} +1 -1
- package/dist/plugin-sdk/{tool-images-DpBaWEHT.js → tool-images-Cl_rGIUZ.js} +2 -2
- package/dist/plugin-sdk/{tool-loop-detection-BOvUFa0f.js → tool-loop-detection-Da4WUT_P.js} +2 -2
- package/dist/plugin-sdk/{unified-runner-CnM7lyNd.js → unified-runner-nwMnsZyj.js} +60 -60
- package/dist/plugin-sdk/web-BlweOZDp.js +54 -0
- package/dist/plugin-sdk/{whatsapp-actions-CvnfsFJm.js → whatsapp-actions-DpfaGYs7.js} +21 -21
- package/dist/{pw-ai-BW8_KeDf.js → pw-ai-BqxJG-Wh.js} +1 -1
- package/dist/{pw-ai-j9IE1K0-.js → pw-ai-C-NSGye0.js} +1 -1
- package/dist/{runner-8ALr2UII.js → runner-COGFTeDw.js} +1 -1
- package/dist/{runner-C4-9kFdR.js → runner-DhCi2lT1.js} +1 -1
- package/dist/{synthesis-Cph3LhA1.js → synthesis-CXZu24Vx.js} +7 -7
- package/dist/{synthesis-Cus0A2dL.js → synthesis-DrPxcMlQ.js} +7 -7
- package/dist/{unified-runner-CX80YMTk.js → unified-runner-iByUazvW.js} +16 -16
- package/dist/{web-ChozvJ7I.js → web-EsMQBIYf.js} +7 -7
- package/dist/{web-DFlsbXmQ.js → web-PPg5y6xI.js} +7 -7
- package/package.json +1 -1
- package/dist/plugin-sdk/web-CIPJBHAU.js +0 -54
- package/extensions/copilot-proxy/README.md +0 -24
- package/extensions/copilot-proxy/index.ts +0 -154
- package/extensions/copilot-proxy/node_modules/.bin/symi +0 -21
- package/extensions/copilot-proxy/package.json +0 -15
- package/extensions/copilot-proxy/symi.plugin.json +0 -9
- package/extensions/device-pair/index.ts +0 -642
- package/extensions/device-pair/symi.plugin.json +0 -20
- package/extensions/diagnostics-otel/index.ts +0 -15
- package/extensions/diagnostics-otel/node_modules/.bin/acorn +0 -21
- package/extensions/diagnostics-otel/node_modules/.bin/symi +0 -21
- package/extensions/diagnostics-otel/package.json +0 -27
- package/extensions/diagnostics-otel/src/service.test.ts +0 -290
- package/extensions/diagnostics-otel/src/service.ts +0 -666
- package/extensions/diagnostics-otel/symi.plugin.json +0 -8
- package/extensions/google-antigravity-auth/README.md +0 -24
- package/extensions/google-antigravity-auth/index.ts +0 -424
- package/extensions/google-antigravity-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-antigravity-auth/package.json +0 -15
- package/extensions/google-antigravity-auth/symi.plugin.json +0 -9
- package/extensions/google-gemini-cli-auth/README.md +0 -35
- package/extensions/google-gemini-cli-auth/index.ts +0 -75
- package/extensions/google-gemini-cli-auth/node_modules/.bin/symi +0 -21
- package/extensions/google-gemini-cli-auth/oauth.test.ts +0 -162
- package/extensions/google-gemini-cli-auth/oauth.ts +0 -636
- package/extensions/google-gemini-cli-auth/package.json +0 -15
- package/extensions/google-gemini-cli-auth/symi.plugin.json +0 -9
- package/extensions/learning-loop/index.ts +0 -159
- package/extensions/learning-loop/node_modules/.bin/symi +0 -21
- package/extensions/learning-loop/package.json +0 -18
- package/extensions/learning-loop/src/analytics/gateway-methods.ts +0 -230
- package/extensions/learning-loop/src/analytics/metrics-aggregator.ts +0 -153
- package/extensions/learning-loop/src/capture/run-tracker.ts +0 -181
- package/extensions/learning-loop/src/capture/serializer.ts +0 -74
- package/extensions/learning-loop/src/db.ts +0 -583
- package/extensions/learning-loop/src/feedback/explicit-feedback.ts +0 -58
- package/extensions/learning-loop/src/feedback/implicit-signals.ts +0 -89
- package/extensions/learning-loop/src/graph/edge-inference.ts +0 -189
- package/extensions/learning-loop/src/graph/graph-retrieval.ts +0 -144
- package/extensions/learning-loop/src/graph/graph-store.ts +0 -183
- package/extensions/learning-loop/src/hooks.ts +0 -244
- package/extensions/learning-loop/src/injection/cache.ts +0 -73
- package/extensions/learning-loop/src/injection/context-injector.ts +0 -104
- package/extensions/learning-loop/src/injection/prompt-builder.ts +0 -43
- package/extensions/learning-loop/src/learning/embedding-bridge.ts +0 -54
- package/extensions/learning-loop/src/learning/learning-extractor.ts +0 -217
- package/extensions/learning-loop/src/learning/learning-store.ts +0 -158
- package/extensions/learning-loop/src/learning/retrieval.ts +0 -87
- package/extensions/learning-loop/src/math/confidence-intervals.ts +0 -62
- package/extensions/learning-loop/src/math/ewma.ts +0 -51
- package/extensions/learning-loop/src/math/weighted-scorer.ts +0 -42
- package/extensions/learning-loop/src/schema.ts +0 -176
- package/extensions/learning-loop/src/scoring/normalization.ts +0 -32
- package/extensions/learning-loop/src/scoring/quality-engine.ts +0 -78
- package/extensions/learning-loop/src/scoring/signal-extractors.ts +0 -155
- package/extensions/learning-loop/src/test/context-injector.test.ts +0 -142
- package/extensions/learning-loop/src/test/fixes.test.ts +0 -1286
- package/extensions/learning-loop/src/test/graph.test.ts +0 -711
- package/extensions/learning-loop/src/test/integration.test.ts +0 -312
- package/extensions/learning-loop/src/test/learning-store.test.ts +0 -191
- package/extensions/learning-loop/src/test/math.test.ts +0 -148
- package/extensions/learning-loop/src/test/quality-engine.test.ts +0 -231
- package/extensions/learning-loop/src/test/run-tracker.test.ts +0 -143
- package/extensions/learning-loop/src/types.ts +0 -281
- package/extensions/learning-loop/symi.plugin.json +0 -46
- package/extensions/llm-task/README.md +0 -97
- package/extensions/llm-task/index.ts +0 -6
- package/extensions/llm-task/package.json +0 -12
- package/extensions/llm-task/src/llm-task-tool.test.ts +0 -138
- package/extensions/llm-task/src/llm-task-tool.ts +0 -249
- package/extensions/llm-task/symi.plugin.json +0 -21
- package/extensions/memory-lancedb/config.ts +0 -161
- package/extensions/memory-lancedb/index.test.ts +0 -330
- package/extensions/memory-lancedb/index.ts +0 -670
- package/extensions/memory-lancedb/node_modules/.bin/arrow2csv +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/openai +0 -21
- package/extensions/memory-lancedb/node_modules/.bin/symi +0 -21
- package/extensions/memory-lancedb/package.json +0 -20
- package/extensions/memory-lancedb/symi.plugin.json +0 -71
- package/extensions/minimax-portal-auth/README.md +0 -33
- package/extensions/minimax-portal-auth/index.ts +0 -161
- package/extensions/minimax-portal-auth/node_modules/.bin/symi +0 -21
- package/extensions/minimax-portal-auth/oauth.ts +0 -247
- package/extensions/minimax-portal-auth/package.json +0 -15
- package/extensions/minimax-portal-auth/symi.plugin.json +0 -9
- package/extensions/model-equalizer/index.ts +0 -80
- package/extensions/model-equalizer/skills/model-equalizer/SKILL.md +0 -58
- package/extensions/model-equalizer/src/detection.ts +0 -62
- package/extensions/model-equalizer/src/enhancer.ts +0 -63
- package/extensions/model-equalizer/src/test/detection.test.ts +0 -218
- package/extensions/model-equalizer/src/test/enhancer.test.ts +0 -137
- package/extensions/model-equalizer/src/test/integration.test.ts +0 -185
- package/extensions/model-equalizer/src/types.ts +0 -24
- package/extensions/model-equalizer/symi.plugin.json +0 -12
- package/extensions/phone-control/index.ts +0 -421
- package/extensions/phone-control/symi.plugin.json +0 -10
- package/extensions/pipeline/README.md +0 -75
- package/extensions/pipeline/SKILL.md +0 -97
- package/extensions/pipeline/index.ts +0 -18
- package/extensions/pipeline/package.json +0 -11
- package/extensions/pipeline/src/pipeline-tool.test.ts +0 -345
- package/extensions/pipeline/src/pipeline-tool.ts +0 -266
- package/extensions/pipeline/src/windows-spawn.test.ts +0 -148
- package/extensions/pipeline/src/windows-spawn.ts +0 -193
- package/extensions/pipeline/symi.plugin.json +0 -10
- package/extensions/qwen-portal-auth/README.md +0 -24
- package/extensions/qwen-portal-auth/index.ts +0 -134
- package/extensions/qwen-portal-auth/oauth.ts +0 -190
- package/extensions/qwen-portal-auth/symi.plugin.json +0 -9
- package/extensions/talk-voice/index.ts +0 -150
- package/extensions/talk-voice/symi.plugin.json +0 -10
- package/extensions/thread-ownership/index.test.ts +0 -180
- package/extensions/thread-ownership/index.ts +0 -133
- package/extensions/thread-ownership/symi.plugin.json +0 -28
- package/skills/1password/SKILL.md +0 -71
- package/skills/1password/references/cli-examples.md +0 -29
- package/skills/1password/references/get-started.md +0 -17
- package/skills/apple-notes/SKILL.md +0 -78
- package/skills/apple-reminders/SKILL.md +0 -119
- package/skills/bear-notes/SKILL.md +0 -108
- package/skills/blogwatcher/SKILL.md +0 -70
- package/skills/blucli/SKILL.md +0 -48
- package/skills/bluebubbles/SKILL.md +0 -132
- package/skills/camsnap/SKILL.md +0 -46
- package/skills/canvas/SKILL.md +0 -204
- package/skills/connect-email/SKILL.md +0 -142
- package/skills/document-generation/SKILL.md +0 -83
- package/skills/eightctl/SKILL.md +0 -51
- package/skills/food-order/SKILL.md +0 -49
- package/skills/gemini/SKILL.md +0 -44
- package/skills/gh-issues/SKILL.md +0 -865
- package/skills/gifgrep/SKILL.md +0 -80
- package/skills/github/SKILL.md +0 -164
- package/skills/gog/SKILL.md +0 -117
- package/skills/goplaces/SKILL.md +0 -53
- package/skills/healthcheck/SKILL.md +0 -246
- package/skills/himalaya/SKILL.md +0 -258
- package/skills/himalaya/references/configuration.md +0 -184
- package/skills/himalaya/references/message-composition.md +0 -199
- package/skills/imsg/SKILL.md +0 -122
- package/skills/long-task/SKILL.md +0 -58
- package/skills/long-task/scripts/detach-task.sh +0 -187
- package/skills/nano-banana-pro/SKILL.md +0 -59
- package/skills/nano-banana-pro/scripts/generate_image.py +0 -184
- package/skills/nano-pdf/SKILL.md +0 -39
- package/skills/notion/SKILL.md +0 -173
- package/skills/obsidian/SKILL.md +0 -82
- package/skills/openai-image-gen/SKILL.md +0 -90
- package/skills/openai-image-gen/scripts/gen.py +0 -240
- package/skills/openai-whisper/SKILL.md +0 -39
- package/skills/openai-whisper-api/SKILL.md +0 -53
- package/skills/openai-whisper-api/scripts/transcribe.sh +0 -85
- package/skills/openhue/SKILL.md +0 -113
- package/skills/oracle/SKILL.md +0 -126
- package/skills/ordercli/SKILL.md +0 -79
- package/skills/peekaboo/SKILL.md +0 -191
- package/skills/reactions-extensive/SKILL.md +0 -30
- package/skills/reactions-minimal/SKILL.md +0 -31
- package/skills/safe-edit/SKILL.md +0 -51
- package/skills/sag/SKILL.md +0 -88
- package/skills/sherpa-onnx-tts/SKILL.md +0 -104
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +0 -178
- package/skills/songsee/SKILL.md +0 -50
- package/skills/sonoscli/SKILL.md +0 -66
- package/skills/spotify-player/SKILL.md +0 -65
- package/skills/symihub/SKILL.md +0 -78
- package/skills/things-mac/SKILL.md +0 -87
- package/skills/tmux/SKILL.md +0 -153
- package/skills/tmux/scripts/find-sessions.sh +0 -112
- package/skills/tmux/scripts/wait-for-text.sh +0 -83
- package/skills/trello/SKILL.md +0 -96
- package/skills/video-frames/SKILL.md +0 -47
- package/skills/video-frames/scripts/frame.sh +0 -81
- package/skills/voice-call/SKILL.md +0 -46
- package/skills/wacli/SKILL.md +0 -73
- package/skills/weather/SKILL.md +0 -113
- package/skills/xurl/SKILL.md +0 -462
package/skills/imsg/SKILL.md
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: imsg
|
|
3
|
-
description: iMessage/SMS CLI for listing chats, history, and sending messages via Messages.app.
|
|
4
|
-
homepage: https://imsg.to
|
|
5
|
-
metadata:
|
|
6
|
-
{
|
|
7
|
-
"symi":
|
|
8
|
-
{
|
|
9
|
-
"emoji": "📨",
|
|
10
|
-
"os": ["darwin"],
|
|
11
|
-
"requires": { "bins": ["imsg"] },
|
|
12
|
-
"install":
|
|
13
|
-
[
|
|
14
|
-
{
|
|
15
|
-
"id": "brew",
|
|
16
|
-
"kind": "brew",
|
|
17
|
-
"formula": "steipete/tap/imsg",
|
|
18
|
-
"bins": ["imsg"],
|
|
19
|
-
"label": "Install imsg (brew)",
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
triggers: [imsg]
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
# imsg
|
|
28
|
-
|
|
29
|
-
Use `imsg` to read and send iMessage/SMS via macOS Messages.app.
|
|
30
|
-
|
|
31
|
-
## When to Use
|
|
32
|
-
|
|
33
|
-
✅ **USE this skill when:**
|
|
34
|
-
|
|
35
|
-
- User explicitly asks to send iMessage or SMS
|
|
36
|
-
- Reading iMessage conversation history
|
|
37
|
-
- Checking recent Messages.app chats
|
|
38
|
-
- Sending to phone numbers or Apple IDs
|
|
39
|
-
|
|
40
|
-
## When NOT to Use
|
|
41
|
-
|
|
42
|
-
❌ **DON'T use this skill when:**
|
|
43
|
-
|
|
44
|
-
- Telegram messages → use `message` tool with `channel:telegram`
|
|
45
|
-
- Signal messages → use Signal channel if configured
|
|
46
|
-
- WhatsApp messages → use WhatsApp channel if configured
|
|
47
|
-
- Slack messages → use `slack` skill
|
|
48
|
-
- Group chat management (adding/removing members) → not supported
|
|
49
|
-
- Bulk/mass messaging → always confirm with user first
|
|
50
|
-
- Replying in current conversation → just reply normally (Symi routes automatically)
|
|
51
|
-
|
|
52
|
-
## Requirements
|
|
53
|
-
|
|
54
|
-
- macOS with Messages.app signed in
|
|
55
|
-
- Full Disk Access for terminal
|
|
56
|
-
- Automation permission for Messages.app (for sending)
|
|
57
|
-
|
|
58
|
-
## Common Commands
|
|
59
|
-
|
|
60
|
-
### List Chats
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
imsg chats --limit 10 --json
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### View History
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
# By chat ID
|
|
70
|
-
imsg history --chat-id 1 --limit 20 --json
|
|
71
|
-
|
|
72
|
-
# With attachments info
|
|
73
|
-
imsg history --chat-id 1 --limit 20 --attachments --json
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Watch for New Messages
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
imsg watch --chat-id 1 --attachments
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
### Send Messages
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
# Text only
|
|
86
|
-
imsg send --to "+14155551212" --text "Hello!"
|
|
87
|
-
|
|
88
|
-
# With attachment
|
|
89
|
-
imsg send --to "+14155551212" --text "Check this out" --file /path/to/image.jpg
|
|
90
|
-
|
|
91
|
-
# Specify service
|
|
92
|
-
imsg send --to "+14155551212" --text "Hi" --service imessage
|
|
93
|
-
imsg send --to "+14155551212" --text "Hi" --service sms
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Service Options
|
|
97
|
-
|
|
98
|
-
- `--service imessage` — Force iMessage (requires recipient has iMessage)
|
|
99
|
-
- `--service sms` — Force SMS (green bubble)
|
|
100
|
-
- `--service auto` — Let Messages.app decide (default)
|
|
101
|
-
|
|
102
|
-
## Safety Rules
|
|
103
|
-
|
|
104
|
-
1. **Always confirm recipient and message content** before sending
|
|
105
|
-
2. **Never send to unknown numbers** without explicit user approval
|
|
106
|
-
3. **Be careful with attachments** — confirm file path exists
|
|
107
|
-
4. **Rate limit yourself** — don't spam
|
|
108
|
-
|
|
109
|
-
## Example Workflow
|
|
110
|
-
|
|
111
|
-
User: "Text mom that I'll be late"
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
# 1. Find mom's chat
|
|
115
|
-
imsg chats --limit 20 --json | jq '.[] | select(.displayName | contains("Mom"))'
|
|
116
|
-
|
|
117
|
-
# 2. Confirm with user
|
|
118
|
-
# "Found Mom at +1555123456. Send 'I'll be late' via iMessage?"
|
|
119
|
-
|
|
120
|
-
# 3. Send after confirmation
|
|
121
|
-
imsg send --to "+1555123456" --text "I'll be late"
|
|
122
|
-
```
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: long-task
|
|
3
|
-
description: Run long operations (deep scans, brute-forces, big builds, exhaustive enumeration) without hitting the agent-turn timeout. Detaches the command with setsid+nohup and schedules a cron monitor that polls status every 2-3 minutes. Use when a single exec call may exceed 15 minutes (deep nmap `-sS -sV -O` on /24 or larger, full hydra/gobuster/ffuf/masscan sweeps, multi-hour builds).
|
|
4
|
-
triggers: [long-task, long, task]
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Long-Running Task — detach + cron monitor pattern
|
|
8
|
-
|
|
9
|
-
Deep scans (`nmap -sS -sV -O` on a /24, comprehensive `-sC` sweeps, full TCP/UDP, long brute-forces, big builds) routinely run 25–60 minutes. The agent-turn timeout (`agents.defaults.timeoutSeconds`, default 1800 = 30 min) is a hard ceiling, and a foreground-blocked exec tool call will SIGTERM the subprocess when the turn ends. **Do not foreground long tasks.** Use this two-part pattern instead.
|
|
10
|
-
|
|
11
|
-
## Part 1 — Detach the task
|
|
12
|
-
|
|
13
|
-
Launch with the helper script bundled with this skill:
|
|
14
|
-
|
|
15
|
-
<skill-dir>/scripts/detach-task.sh <task-id> <workdir> <command...>
|
|
16
|
-
|
|
17
|
-
Where:
|
|
18
|
-
|
|
19
|
-
- `<task-id>` is a short identifier you pick (e.g. `scan-192-168-10`)
|
|
20
|
-
- `<workdir>` is a directory the state files will be written into (agent workspace is fine)
|
|
21
|
-
- `<command...>` is the literal command you want to run
|
|
22
|
-
|
|
23
|
-
The script creates in `<workdir>`:
|
|
24
|
-
|
|
25
|
-
- `task-<id>.pid` — PID of the detached process
|
|
26
|
-
- `task-<id>.cmd` — the literal command line
|
|
27
|
-
- `task-<id>.started` — ISO-8601 start timestamp
|
|
28
|
-
- `task-<id>.log` — merged stdout/stderr
|
|
29
|
-
- `task-<id>.status` — `running` while alive, `complete rc=<N>` after exit
|
|
30
|
-
|
|
31
|
-
After running, **return control to the user immediately** with: task id, PID, log path, your ETA, and a note that a cron monitor will be scheduled.
|
|
32
|
-
|
|
33
|
-
## Part 2 — Schedule the cron monitor
|
|
34
|
-
|
|
35
|
-
Use the `cron` tool with `action: add`. Schedule every 2–3 minutes. The job prompt must be self-contained — include the task-id, PID, log path, and status path literally, since a future agent turn will receive only that prompt.
|
|
36
|
-
|
|
37
|
-
Example job prompt:
|
|
38
|
-
|
|
39
|
-
> Check if PID <pid> is alive (`kill -0 <pid> 2>/dev/null`).
|
|
40
|
-
> If alive: tail -n 20 <log-path>, post one-sentence progress update.
|
|
41
|
-
> If not alive: read <status-path> and tail <log-path>, post final summary, then call the cron tool with `action: remove, id: <this-cron-id>` to clean up.
|
|
42
|
-
|
|
43
|
-
## Part 3 — Cleanup
|
|
44
|
-
|
|
45
|
-
The monitor cron job is responsible for removing itself when it detects `status=complete`. Do not leave stale monitors running.
|
|
46
|
-
|
|
47
|
-
## Why this works
|
|
48
|
-
|
|
49
|
-
- `setsid` puts the subprocess in a new session — it survives SIGHUP.
|
|
50
|
-
- `nohup` ignores SIGHUP directly as belt-and-braces.
|
|
51
|
-
- The outer shell exits immediately, so the agent-turn abort (which kills the process tree of the shell the agent spawned) never reaches the detached task — it's no longer a descendant.
|
|
52
|
-
- Each agent turn stays short (initial detach <1 min, each cron check <30 sec, final summary <1 min), so the default 1800s ceiling never fires.
|
|
53
|
-
|
|
54
|
-
## When NOT to use this pattern
|
|
55
|
-
|
|
56
|
-
- Short scans (single host, small port range) that complete in under 10 minutes — foreground exec is simpler.
|
|
57
|
-
- Interactive commands that need a TTY — the script closes stdin and redirects stdout/stderr.
|
|
58
|
-
- Tasks where you need to stream output back to the user in real time — the cron-monitor pattern polls at 2–3 min intervals, not live.
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# detach-task.sh — launch a long-running command fully detached from the
|
|
3
|
-
# calling agent turn, so it survives the agent-turn timeout (default 30 min).
|
|
4
|
-
#
|
|
5
|
-
# Usage:
|
|
6
|
-
# detach-task.sh <task-id> <workdir> <command...>
|
|
7
|
-
#
|
|
8
|
-
# Creates in <workdir>:
|
|
9
|
-
# task-<id>.pid — PID of the detached process
|
|
10
|
-
# task-<id>.cmd — literal command line (for future agent turns to see)
|
|
11
|
-
# task-<id>.started — ISO-8601 start timestamp
|
|
12
|
-
# task-<id>.log — combined stdout + stderr (grows as task runs)
|
|
13
|
-
# task-<id>.status — "running pid=<N> started=<ISO>"
|
|
14
|
-
# → "complete rc=<N> ended=<ISO>" on clean exit
|
|
15
|
-
# → "aborted rc=<N> signal=<NAME>" on SIGTERM/INT/HUP
|
|
16
|
-
#
|
|
17
|
-
# Exit code: 0 on successful spawn, 1 on missing args, 2 on invalid args,
|
|
18
|
-
# 3 on workdir creation failure, 4 on task-id collision (a task
|
|
19
|
-
# with the same id is already running).
|
|
20
|
-
#
|
|
21
|
-
# Hardening notes (2.7.0):
|
|
22
|
-
# - task-id is validated against an allow-list ([a-zA-Z0-9._-], 1–60 chars,
|
|
23
|
-
# must not start with '.' or '-'). Prevents path traversal via task-id.
|
|
24
|
-
# - workdir must be an absolute path.
|
|
25
|
-
# - Status file is written via tmp + mv so concurrent readers never see a
|
|
26
|
-
# half-written file and the "stuck at running after SIGTERM" failure mode
|
|
27
|
-
# is eliminated.
|
|
28
|
-
# - Child shell has a TERM/INT/HUP trap that records an "aborted" status
|
|
29
|
-
# before exiting, so external kill signals leave a deterministic record.
|
|
30
|
-
#
|
|
31
|
-
# Hardening notes (post-2.8.12):
|
|
32
|
-
# - Pre-flight collision check: refuses to overwrite the state files of a
|
|
33
|
-
# task-id whose recorded PID is still alive (exit code 4).
|
|
34
|
-
# - Log file is bounded by LONG_TASK_LOG_MAX_BYTES (default 104857600 =
|
|
35
|
-
# 100 MiB; override via env). Once exceeded, a single marker line is
|
|
36
|
-
# written and further output is silently discarded — the producer keeps
|
|
37
|
-
# running, never gets SIGPIPE.
|
|
38
|
-
#
|
|
39
|
-
# Design notes:
|
|
40
|
-
# - setsid + nohup + disown: triple-armor against SIGHUP when the parent
|
|
41
|
-
# shell exits. The subprocess gets its own session.
|
|
42
|
-
# - stdin closed, stdout/stderr redirected to the log file. No terminal.
|
|
43
|
-
# - The wrapper shell exits immediately after spawning; does NOT wait.
|
|
44
|
-
|
|
45
|
-
set -euo pipefail
|
|
46
|
-
|
|
47
|
-
if [[ $# -lt 3 ]]; then
|
|
48
|
-
echo "usage: $0 <task-id> <workdir> <command...>" >&2
|
|
49
|
-
exit 1
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
raw_task_id="$1"
|
|
53
|
-
workdir="$2"
|
|
54
|
-
shift 2
|
|
55
|
-
|
|
56
|
-
# --- task-id validation -----------------------------------------------------
|
|
57
|
-
if [[ -z "$raw_task_id" ]]; then
|
|
58
|
-
echo "detach-task: task-id must not be empty" >&2
|
|
59
|
-
exit 2
|
|
60
|
-
fi
|
|
61
|
-
if [[ ${#raw_task_id} -gt 60 ]]; then
|
|
62
|
-
echo "detach-task: task-id exceeds 60 chars" >&2
|
|
63
|
-
exit 2
|
|
64
|
-
fi
|
|
65
|
-
if [[ "$raw_task_id" =~ [^a-zA-Z0-9._-] ]]; then
|
|
66
|
-
echo "detach-task: task-id contains illegal chars (allowed: alphanumeric, dot, dash, underscore): $raw_task_id" >&2
|
|
67
|
-
exit 2
|
|
68
|
-
fi
|
|
69
|
-
case "$raw_task_id" in
|
|
70
|
-
.* | -*)
|
|
71
|
-
echo "detach-task: task-id must not start with dot or dash: $raw_task_id" >&2
|
|
72
|
-
exit 2
|
|
73
|
-
;;
|
|
74
|
-
esac
|
|
75
|
-
task_id="$raw_task_id"
|
|
76
|
-
|
|
77
|
-
# --- workdir validation -----------------------------------------------------
|
|
78
|
-
if [[ "$workdir" != /* ]]; then
|
|
79
|
-
echo "detach-task: workdir must be an absolute path: $workdir" >&2
|
|
80
|
-
exit 2
|
|
81
|
-
fi
|
|
82
|
-
if ! mkdir -p "$workdir"; then
|
|
83
|
-
echo "detach-task: cannot create workdir: $workdir" >&2
|
|
84
|
-
exit 3
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
# --- log size cap -----------------------------------------------------------
|
|
88
|
-
log_max_bytes="${LONG_TASK_LOG_MAX_BYTES:-104857600}"
|
|
89
|
-
if ! [[ "$log_max_bytes" =~ ^[0-9]+$ ]] || (( log_max_bytes < 1 )); then
|
|
90
|
-
echo "detach-task: LONG_TASK_LOG_MAX_BYTES must be a positive integer (got: $log_max_bytes)" >&2
|
|
91
|
-
exit 2
|
|
92
|
-
fi
|
|
93
|
-
|
|
94
|
-
pid_file="$workdir/task-${task_id}.pid"
|
|
95
|
-
cmd_file="$workdir/task-${task_id}.cmd"
|
|
96
|
-
started_file="$workdir/task-${task_id}.started"
|
|
97
|
-
log_file="$workdir/task-${task_id}.log"
|
|
98
|
-
status_file="$workdir/task-${task_id}.status"
|
|
99
|
-
|
|
100
|
-
# --- collision check --------------------------------------------------------
|
|
101
|
-
# If a pid file exists and the recorded PID is still alive, a prior task with
|
|
102
|
-
# this id is already running. Refuse to clobber its log/status files.
|
|
103
|
-
# Stale pid files (process already dead) are silently overwritten below.
|
|
104
|
-
if [[ -f "$pid_file" ]]; then
|
|
105
|
-
prior_pid="$(tr -dc '0-9' < "$pid_file" 2>/dev/null || true)"
|
|
106
|
-
if [[ -n "$prior_pid" ]] && kill -0 "$prior_pid" 2>/dev/null; then
|
|
107
|
-
echo "detach-task: task-id '$task_id' is already running (pid=$prior_pid). Pick a different id or wait for it to finish." >&2
|
|
108
|
-
exit 4
|
|
109
|
-
fi
|
|
110
|
-
fi
|
|
111
|
-
|
|
112
|
-
# --- atomic status writer (parent) -----------------------------------------
|
|
113
|
-
write_status_atomic() {
|
|
114
|
-
local _payload="$1"
|
|
115
|
-
local _tmp="${status_file}.$$.${RANDOM}.tmp"
|
|
116
|
-
printf '%s\n' "$_payload" > "$_tmp"
|
|
117
|
-
mv -f "$_tmp" "$status_file"
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
printf '%s\n' "$*" > "$cmd_file"
|
|
121
|
-
started_at="$(date -u -Iseconds)"
|
|
122
|
-
printf '%s\n' "$started_at" > "$started_file"
|
|
123
|
-
write_status_atomic "running started=$started_at"
|
|
124
|
-
|
|
125
|
-
# --- detached child --------------------------------------------------------
|
|
126
|
-
# The inner `bash -c` inherits status/log paths as positional args so we
|
|
127
|
-
# never string-interpolate paths into the script body. The inner script
|
|
128
|
-
# re-implements the atomic writer (functions don't cross process boundaries
|
|
129
|
-
# via `bash -c`). A TERM/INT/HUP trap catches external kill signals and
|
|
130
|
-
# writes a terminal "aborted" status before exiting with the signal's code.
|
|
131
|
-
setsid nohup bash -c '
|
|
132
|
-
set -u
|
|
133
|
-
_status="$1"
|
|
134
|
-
_log="$2"
|
|
135
|
-
_max="$3"
|
|
136
|
-
shift 3
|
|
137
|
-
_write() {
|
|
138
|
-
local _tmp="${_status}.$$.${RANDOM}.tmp"
|
|
139
|
-
printf "%s\n" "$1" > "$_tmp" && mv -f "$_tmp" "$_status"
|
|
140
|
-
}
|
|
141
|
-
_on_signal() {
|
|
142
|
-
local _sig="$1"
|
|
143
|
-
local _rc=$?
|
|
144
|
-
_write "aborted rc=$_rc signal=$_sig ended=$(date -u -Iseconds)" 2>/dev/null || true
|
|
145
|
-
exit "$_rc"
|
|
146
|
-
}
|
|
147
|
-
# Reads stdin line-by-line, prints to its stdout (which the caller
|
|
148
|
-
# redirects to the log file) up to _max total bytes, then writes one
|
|
149
|
-
# cap marker and silently discards the rest. Reading continues until
|
|
150
|
-
# EOF so the producing pipe never receives SIGPIPE.
|
|
151
|
-
_log_capper() {
|
|
152
|
-
local _total=0
|
|
153
|
-
local _capped=0
|
|
154
|
-
local _line
|
|
155
|
-
local _ls
|
|
156
|
-
while IFS= read -r _line; do
|
|
157
|
-
if (( _capped )); then
|
|
158
|
-
continue
|
|
159
|
-
fi
|
|
160
|
-
_ls=$(( ${#_line} + 1 ))
|
|
161
|
-
if (( _total + _ls > _max )); then
|
|
162
|
-
printf "[long-task: log capped at %s bytes; further output discarded]\n" "$_max"
|
|
163
|
-
_capped=1
|
|
164
|
-
continue
|
|
165
|
-
fi
|
|
166
|
-
printf "%s\n" "$_line"
|
|
167
|
-
_total=$(( _total + _ls ))
|
|
168
|
-
done
|
|
169
|
-
}
|
|
170
|
-
trap "_on_signal TERM" TERM
|
|
171
|
-
trap "_on_signal INT" INT
|
|
172
|
-
trap "_on_signal HUP" HUP
|
|
173
|
-
"$@" 2>&1 | _log_capper >> "$_log"
|
|
174
|
-
rc=${PIPESTATUS[0]}
|
|
175
|
-
_write "complete rc=$rc ended=$(date -u -Iseconds)"
|
|
176
|
-
' -- "$status_file" "$log_file" "$log_max_bytes" "$@" < /dev/null > /dev/null 2>&1 &
|
|
177
|
-
|
|
178
|
-
pid=$!
|
|
179
|
-
disown "$pid" 2>/dev/null || true
|
|
180
|
-
printf '%d\n' "$pid" > "$pid_file"
|
|
181
|
-
|
|
182
|
-
# Finalise the parent status with the PID once known. The child may race us
|
|
183
|
-
# and overwrite with "complete" if the command was very fast — that's fine,
|
|
184
|
-
# the mv is atomic and the final state wins.
|
|
185
|
-
write_status_atomic "running pid=$pid started=$started_at"
|
|
186
|
-
|
|
187
|
-
echo "spawned task=$task_id pid=$pid log=$log_file status=$status_file"
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nano-banana-pro
|
|
3
|
-
description: Generate or edit images via Gemini 3 Pro Image (Nano Banana Pro).
|
|
4
|
-
homepage: https://ai.google.dev/
|
|
5
|
-
metadata:
|
|
6
|
-
{
|
|
7
|
-
"symi":
|
|
8
|
-
{
|
|
9
|
-
"emoji": "🍌",
|
|
10
|
-
"requires": { "bins": ["uv"], "env": ["GEMINI_API_KEY"] },
|
|
11
|
-
"primaryEnv": "GEMINI_API_KEY",
|
|
12
|
-
"install":
|
|
13
|
-
[
|
|
14
|
-
{
|
|
15
|
-
"id": "uv-brew",
|
|
16
|
-
"kind": "brew",
|
|
17
|
-
"formula": "uv",
|
|
18
|
-
"bins": ["uv"],
|
|
19
|
-
"label": "Install uv (brew)",
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
},
|
|
23
|
-
}
|
|
24
|
-
triggers: [nano-banana-pro, nano, banana, pro]
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
# Nano Banana Pro (Gemini 3 Pro Image)
|
|
28
|
-
|
|
29
|
-
Use the bundled script to generate or edit images.
|
|
30
|
-
|
|
31
|
-
Generate
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
uv run {baseDir}/scripts/generate_image.py --prompt "your image description" --filename "output.png" --resolution 1K
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Edit (single image)
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
uv run {baseDir}/scripts/generate_image.py --prompt "edit instructions" --filename "output.png" -i "/path/in.png" --resolution 2K
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Multi-image composition (up to 14 images)
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
uv run {baseDir}/scripts/generate_image.py --prompt "combine these into one scene" --filename "output.png" -i img1.png -i img2.png -i img3.png
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
API key
|
|
50
|
-
|
|
51
|
-
- `GEMINI_API_KEY` env var
|
|
52
|
-
- Or set `skills."nano-banana-pro".apiKey` / `skills."nano-banana-pro".env.GEMINI_API_KEY` in `~/.symi/symi.json`
|
|
53
|
-
|
|
54
|
-
Notes
|
|
55
|
-
|
|
56
|
-
- Resolutions: `1K` (default), `2K`, `4K`.
|
|
57
|
-
- Use timestamps in filenames: `yyyy-mm-dd-hh-mm-ss-name.png`.
|
|
58
|
-
- The script prints a `MEDIA:` line for Symi to auto-attach on supported chat providers.
|
|
59
|
-
- Do not read the image back; report the saved path only.
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.10"
|
|
4
|
-
# dependencies = [
|
|
5
|
-
# "google-genai>=1.0.0",
|
|
6
|
-
# "pillow>=10.0.0",
|
|
7
|
-
# ]
|
|
8
|
-
# ///
|
|
9
|
-
"""
|
|
10
|
-
Generate images using Google's Nano Banana Pro (Gemini 3 Pro Image) API.
|
|
11
|
-
|
|
12
|
-
Usage:
|
|
13
|
-
uv run generate_image.py --prompt "your image description" --filename "output.png" [--resolution 1K|2K|4K] [--api-key KEY]
|
|
14
|
-
|
|
15
|
-
Multi-image editing (up to 14 images):
|
|
16
|
-
uv run generate_image.py --prompt "combine these images" --filename "output.png" -i img1.png -i img2.png -i img3.png
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
import argparse
|
|
20
|
-
import os
|
|
21
|
-
import sys
|
|
22
|
-
from pathlib import Path
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def get_api_key(provided_key: str | None) -> str | None:
|
|
26
|
-
"""Get API key from argument first, then environment."""
|
|
27
|
-
if provided_key:
|
|
28
|
-
return provided_key
|
|
29
|
-
return os.environ.get("GEMINI_API_KEY")
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def main():
|
|
33
|
-
parser = argparse.ArgumentParser(
|
|
34
|
-
description="Generate images using Nano Banana Pro (Gemini 3 Pro Image)"
|
|
35
|
-
)
|
|
36
|
-
parser.add_argument(
|
|
37
|
-
"--prompt", "-p",
|
|
38
|
-
required=True,
|
|
39
|
-
help="Image description/prompt"
|
|
40
|
-
)
|
|
41
|
-
parser.add_argument(
|
|
42
|
-
"--filename", "-f",
|
|
43
|
-
required=True,
|
|
44
|
-
help="Output filename (e.g., sunset-mountains.png)"
|
|
45
|
-
)
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--input-image", "-i",
|
|
48
|
-
action="append",
|
|
49
|
-
dest="input_images",
|
|
50
|
-
metavar="IMAGE",
|
|
51
|
-
help="Input image path(s) for editing/composition. Can be specified multiple times (up to 14 images)."
|
|
52
|
-
)
|
|
53
|
-
parser.add_argument(
|
|
54
|
-
"--resolution", "-r",
|
|
55
|
-
choices=["1K", "2K", "4K"],
|
|
56
|
-
default="1K",
|
|
57
|
-
help="Output resolution: 1K (default), 2K, or 4K"
|
|
58
|
-
)
|
|
59
|
-
parser.add_argument(
|
|
60
|
-
"--api-key", "-k",
|
|
61
|
-
help="Gemini API key (overrides GEMINI_API_KEY env var)"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
args = parser.parse_args()
|
|
65
|
-
|
|
66
|
-
# Get API key
|
|
67
|
-
api_key = get_api_key(args.api_key)
|
|
68
|
-
if not api_key:
|
|
69
|
-
print("Error: No API key provided.", file=sys.stderr)
|
|
70
|
-
print("Please either:", file=sys.stderr)
|
|
71
|
-
print(" 1. Provide --api-key argument", file=sys.stderr)
|
|
72
|
-
print(" 2. Set GEMINI_API_KEY environment variable", file=sys.stderr)
|
|
73
|
-
sys.exit(1)
|
|
74
|
-
|
|
75
|
-
# Import here after checking API key to avoid slow import on error
|
|
76
|
-
from google import genai
|
|
77
|
-
from google.genai import types
|
|
78
|
-
from PIL import Image as PILImage
|
|
79
|
-
|
|
80
|
-
# Initialise client
|
|
81
|
-
client = genai.Client(api_key=api_key)
|
|
82
|
-
|
|
83
|
-
# Set up output path
|
|
84
|
-
output_path = Path(args.filename)
|
|
85
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
-
|
|
87
|
-
# Load input images if provided (up to 14 supported by Nano Banana Pro)
|
|
88
|
-
input_images = []
|
|
89
|
-
output_resolution = args.resolution
|
|
90
|
-
if args.input_images:
|
|
91
|
-
if len(args.input_images) > 14:
|
|
92
|
-
print(f"Error: Too many input images ({len(args.input_images)}). Maximum is 14.", file=sys.stderr)
|
|
93
|
-
sys.exit(1)
|
|
94
|
-
|
|
95
|
-
max_input_dim = 0
|
|
96
|
-
for img_path in args.input_images:
|
|
97
|
-
try:
|
|
98
|
-
img = PILImage.open(img_path)
|
|
99
|
-
input_images.append(img)
|
|
100
|
-
print(f"Loaded input image: {img_path}")
|
|
101
|
-
|
|
102
|
-
# Track largest dimension for auto-resolution
|
|
103
|
-
width, height = img.size
|
|
104
|
-
max_input_dim = max(max_input_dim, width, height)
|
|
105
|
-
except Exception as e:
|
|
106
|
-
print(f"Error loading input image '{img_path}': {e}", file=sys.stderr)
|
|
107
|
-
sys.exit(1)
|
|
108
|
-
|
|
109
|
-
# Auto-detect resolution from largest input if not explicitly set
|
|
110
|
-
if args.resolution == "1K" and max_input_dim > 0: # Default value
|
|
111
|
-
if max_input_dim >= 3000:
|
|
112
|
-
output_resolution = "4K"
|
|
113
|
-
elif max_input_dim >= 1500:
|
|
114
|
-
output_resolution = "2K"
|
|
115
|
-
else:
|
|
116
|
-
output_resolution = "1K"
|
|
117
|
-
print(f"Auto-detected resolution: {output_resolution} (from max input dimension {max_input_dim})")
|
|
118
|
-
|
|
119
|
-
# Build contents (images first if editing, prompt only if generating)
|
|
120
|
-
if input_images:
|
|
121
|
-
contents = [*input_images, args.prompt]
|
|
122
|
-
img_count = len(input_images)
|
|
123
|
-
print(f"Processing {img_count} image{'s' if img_count > 1 else ''} with resolution {output_resolution}...")
|
|
124
|
-
else:
|
|
125
|
-
contents = args.prompt
|
|
126
|
-
print(f"Generating image with resolution {output_resolution}...")
|
|
127
|
-
|
|
128
|
-
try:
|
|
129
|
-
response = client.models.generate_content(
|
|
130
|
-
model="gemini-3-pro-image-preview",
|
|
131
|
-
contents=contents,
|
|
132
|
-
config=types.GenerateContentConfig(
|
|
133
|
-
response_modalities=["TEXT", "IMAGE"],
|
|
134
|
-
image_config=types.ImageConfig(
|
|
135
|
-
image_size=output_resolution
|
|
136
|
-
)
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
# Process response and convert to PNG
|
|
141
|
-
image_saved = False
|
|
142
|
-
for part in response.parts:
|
|
143
|
-
if part.text is not None:
|
|
144
|
-
print(f"Model response: {part.text}")
|
|
145
|
-
elif part.inline_data is not None:
|
|
146
|
-
# Convert inline data to PIL Image and save as PNG
|
|
147
|
-
from io import BytesIO
|
|
148
|
-
|
|
149
|
-
# inline_data.data is already bytes, not base64
|
|
150
|
-
image_data = part.inline_data.data
|
|
151
|
-
if isinstance(image_data, str):
|
|
152
|
-
# If it's a string, it might be base64
|
|
153
|
-
import base64
|
|
154
|
-
image_data = base64.b64decode(image_data)
|
|
155
|
-
|
|
156
|
-
image = PILImage.open(BytesIO(image_data))
|
|
157
|
-
|
|
158
|
-
# Ensure RGB mode for PNG (convert RGBA to RGB with white background if needed)
|
|
159
|
-
if image.mode == 'RGBA':
|
|
160
|
-
rgb_image = PILImage.new('RGB', image.size, (255, 255, 255))
|
|
161
|
-
rgb_image.paste(image, mask=image.split()[3])
|
|
162
|
-
rgb_image.save(str(output_path), 'PNG')
|
|
163
|
-
elif image.mode == 'RGB':
|
|
164
|
-
image.save(str(output_path), 'PNG')
|
|
165
|
-
else:
|
|
166
|
-
image.convert('RGB').save(str(output_path), 'PNG')
|
|
167
|
-
image_saved = True
|
|
168
|
-
|
|
169
|
-
if image_saved:
|
|
170
|
-
full_path = output_path.resolve()
|
|
171
|
-
print(f"\nImage saved: {full_path}")
|
|
172
|
-
# Symi parses MEDIA tokens and will attach the file on supported providers.
|
|
173
|
-
print(f"MEDIA: {full_path}")
|
|
174
|
-
else:
|
|
175
|
-
print("Error: No image was generated in the response.", file=sys.stderr)
|
|
176
|
-
sys.exit(1)
|
|
177
|
-
|
|
178
|
-
except Exception as e:
|
|
179
|
-
print(f"Error generating image: {e}", file=sys.stderr)
|
|
180
|
-
sys.exit(1)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if __name__ == "__main__":
|
|
184
|
-
main()
|
package/skills/nano-pdf/SKILL.md
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: nano-pdf
|
|
3
|
-
description: Edit PDFs with natural-language instructions using the nano-pdf CLI.
|
|
4
|
-
homepage: https://pypi.org/project/nano-pdf/
|
|
5
|
-
metadata:
|
|
6
|
-
{
|
|
7
|
-
"symi":
|
|
8
|
-
{
|
|
9
|
-
"emoji": "📄",
|
|
10
|
-
"requires": { "bins": ["nano-pdf"] },
|
|
11
|
-
"install":
|
|
12
|
-
[
|
|
13
|
-
{
|
|
14
|
-
"id": "uv",
|
|
15
|
-
"kind": "uv",
|
|
16
|
-
"package": "nano-pdf",
|
|
17
|
-
"bins": ["nano-pdf"],
|
|
18
|
-
"label": "Install nano-pdf (uv)",
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
},
|
|
22
|
-
}
|
|
23
|
-
triggers: [nano-pdf, nano, pdf]
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
# nano-pdf
|
|
27
|
-
|
|
28
|
-
Use `nano-pdf` to apply edits to a specific page in a PDF using a natural-language instruction.
|
|
29
|
-
|
|
30
|
-
## Quick start
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
nano-pdf edit deck.pdf 1 "Change the title to 'Q3 Results' and fix the typo in the subtitle"
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Notes:
|
|
37
|
-
|
|
38
|
-
- Page numbers are 0-based or 1-based depending on the tool’s version/config; if the result looks off by one, retry with the other.
|
|
39
|
-
- Always sanity-check the output PDF before sending it out.
|