crewly 1.20.35 → 1.20.47
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/config/skills/_common/desktop-guards.sh +485 -0
- package/config/skills/_common/desktop-guards.test.sh +242 -0
- package/config/skills/_common/desktop-perceive.swift +530 -0
- package/config/skills/_common/desktop-presence.swift +343 -0
- package/config/skills/_common/lib.sh +6 -0
- package/config/skills/agent/_common/desktop-guards.sh +4 -0
- package/config/skills/agent/computer-use/SKILL.md +88 -0
- package/config/skills/agent/computer-use/execute.sh +249 -3
- package/config/skills/agent/core/calendar-create/SKILL.md +10 -0
- package/config/skills/agent/core/calendar-create/execute.sh +6 -0
- package/config/skills/agent/core/calendar-list/SKILL.md +10 -0
- package/config/skills/agent/core/calendar-list/execute.sh +6 -0
- package/config/skills/agent/core/docs-read/SKILL.md +10 -0
- package/config/skills/agent/core/docs-read/execute.sh +6 -1
- package/config/skills/agent/core/docs-write/SKILL.md +10 -0
- package/config/skills/agent/core/docs-write/execute.sh +6 -1
- package/config/skills/agent/core/drive-read/SKILL.md +10 -0
- package/config/skills/agent/core/drive-read/execute.sh +6 -1
- package/config/skills/agent/core/drive-search/SKILL.md +10 -0
- package/config/skills/agent/core/drive-search/execute.sh +6 -1
- package/config/skills/agent/core/drive-upload/SKILL.md +10 -0
- package/config/skills/agent/core/drive-upload/execute.sh +6 -1
- package/config/skills/agent/core/gmail-read/SKILL.md +10 -0
- package/config/skills/agent/core/gmail-read/execute.sh +6 -0
- package/config/skills/agent/core/gmail-search/SKILL.md +10 -0
- package/config/skills/agent/core/gmail-search/execute.sh +6 -0
- package/config/skills/agent/core/gmail-send/SKILL.md +10 -0
- package/config/skills/agent/core/gmail-send/execute.sh +6 -0
- package/config/skills/agent/core/sheets-read/SKILL.md +10 -0
- package/config/skills/agent/core/sheets-read/execute.sh +6 -1
- package/config/skills/agent/core/sheets-write/SKILL.md +10 -0
- package/config/skills/agent/core/sheets-write/execute.sh +6 -1
- package/config/skills/agent/core/slides-create/SKILL.md +10 -0
- package/config/skills/agent/core/slides-create/execute.sh +6 -1
- package/config/skills/agent/core/slides-read/SKILL.md +10 -0
- package/config/skills/agent/core/slides-read/execute.sh +6 -1
- package/config/skills/agent/desktop-app-control/SKILL.md +19 -0
- package/config/skills/agent/remote-browser/SKILL.md +19 -0
- package/config/slack-app-manifest.json +16 -9
- package/dist/backend/backend/src/constants.d.ts +18 -4
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +16 -4
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/desktop/desktop.controller.d.ts +105 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.controller.js +278 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.routes.d.ts +21 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.routes.js +31 -0
- package/dist/backend/backend/src/controllers/desktop/desktop.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts +8 -0
- package/dist/backend/backend/src/controllers/google/google.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.controller.js +137 -37
- package/dist/backend/backend/src/controllers/google/google.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts +2 -1
- package/dist/backend/backend/src/controllers/google/google.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/google/google.routes.js +4 -2
- package/dist/backend/backend/src/controllers/google/google.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack-error.utils.d.ts +46 -0
- package/dist/backend/backend/src/controllers/slack/slack-error.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack-error.utils.js +54 -0
- package/dist/backend/backend/src/controllers/slack/slack-error.utils.js.map +1 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +5 -12
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +3 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +9 -0
- package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.d.ts +23 -2
- package/dist/backend/backend/src/services/google/google-api.client.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-api.client.js +5 -2
- package/dist/backend/backend/src/services/google/google-api.client.js.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts +61 -11
- package/dist/backend/backend/src/services/google/google-workspace-token.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js +108 -31
- package/dist/backend/backend/src/services/google/google-workspace-token.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +24 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +87 -5
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +17 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +32 -0
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +10 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/backend/src/utils/incomplete-turn.utils.d.ts +1 -1
- package/dist/backend/backend/src/utils/incomplete-turn.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/incomplete-turn.utils.js +4 -0
- package/dist/backend/backend/src/utils/incomplete-turn.utils.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +18 -4
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +16 -4
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +24 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +87 -5
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +17 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +32 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +10 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/utils/incomplete-turn.utils.d.ts +1 -1
- package/dist/cli/backend/src/utils/incomplete-turn.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/utils/incomplete-turn.utils.js +4 -0
- package/dist/cli/backend/src/utils/incomplete-turn.utils.js.map +1 -1
- package/frontend/dist/assets/{index-e079a375.js → index-e7785269.js} +267 -267
- package/frontend/dist/index.html +1 -1
- package/package.json +1 -1
- package/packages/crewly-agent/src/eval/desktop/desktop-tasks.test.ts +96 -0
- package/packages/crewly-agent/src/eval/desktop/desktop-tasks.ts +226 -0
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +10 -1
- package/packages/crewly-agent/src/runtime/agent-runner.service.ts +199 -4
- package/packages/crewly-agent/src/runtime/computer.tool.test.ts +219 -0
- package/packages/crewly-agent/src/runtime/computer.tool.ts +405 -0
- package/packages/crewly-agent/src/runtime/desktop-checkpoint.test.ts +136 -0
- package/packages/crewly-agent/src/runtime/desktop-checkpoint.ts +231 -0
- package/packages/crewly-agent/src/runtime/desktop-recovery.test.ts +100 -0
- package/packages/crewly-agent/src/runtime/desktop-recovery.ts +195 -0
- package/packages/crewly-agent/src/runtime/desktop-task-runtime.test.ts +251 -0
- package/packages/crewly-agent/src/runtime/desktop-task-runtime.ts +423 -0
- package/packages/crewly-agent/src/runtime/desktop-task.tool.test.ts +218 -0
- package/packages/crewly-agent/src/runtime/desktop-task.tool.ts +343 -0
- package/packages/crewly-agent/src/runtime/text-tool-calls.test.ts +144 -0
- package/packages/crewly-agent/src/runtime/text-tool-calls.ts +316 -0
- package/packages/crewly-agent/src/runtime/text-tool-salvage.test.ts +190 -0
- package/packages/crewly-agent/src/runtime/tool-registry.test.ts +17 -0
- package/packages/crewly-agent/src/runtime/tool-registry.ts +54 -0
- package/packages/crewly-agent/src/runtime/types.ts +16 -1
- package/config/skills/agent/vnc-browser/SKILL.md +0 -140
|
@@ -35,6 +35,14 @@ INPUT=$(read_json_input "${1:-}")
|
|
|
35
35
|
ACTION=$(echo "$INPUT" | jq -r '.action // empty')
|
|
36
36
|
[ -z "$ACTION" ] && error_exit "Missing required parameter: action"
|
|
37
37
|
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Safety rails (permissions, stop switch, desktop lock, destructive-key and
|
|
40
|
+
# secure-field refusals, audit) — shared with the marketplace fork.
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
source "${SCRIPT_DIR}/../../_common/desktop-guards.sh"
|
|
43
|
+
cu_apply_guards
|
|
44
|
+
|
|
45
|
+
|
|
38
46
|
# ---------------------------------------------------------------------------
|
|
39
47
|
# Helper: get screen dimensions and scale factor
|
|
40
48
|
# ---------------------------------------------------------------------------
|
|
@@ -56,9 +64,15 @@ get_screen_info() {
|
|
|
56
64
|
# optionally overlays a grid, optionally crops.
|
|
57
65
|
# ---------------------------------------------------------------------------
|
|
58
66
|
do_screenshot() {
|
|
59
|
-
local output
|
|
67
|
+
local output=$(echo "$INPUT" | jq -r '.output // empty')
|
|
68
|
+
[ -z "$output" ] && output="${TMPDIR_CU}/screen_$(date +%s%N).png"
|
|
60
69
|
local grid=$(echo "$INPUT" | jq -r '.grid // empty')
|
|
61
70
|
local crop_json=$(echo "$INPUT" | jq -r '.crop // empty')
|
|
71
|
+
# Cap the width the caller wants to reason in. A model's pointing accuracy
|
|
72
|
+
# falls off on a wide image because it is downsampled before the model sees
|
|
73
|
+
# it, and one reasoning in the original coordinate space then points at the
|
|
74
|
+
# wrong place — so the caller asks for the width it will think in.
|
|
75
|
+
local max_width=$(echo "$INPUT" | jq -r '.maxWidth // empty')
|
|
62
76
|
|
|
63
77
|
# Capture full screen (silent)
|
|
64
78
|
screencapture -x "$output"
|
|
@@ -82,6 +96,15 @@ do_screenshot() {
|
|
|
82
96
|
local img_h
|
|
83
97
|
img_h=$(sips -g pixelHeight "$output" | tail -1 | awk '{print $2}')
|
|
84
98
|
|
|
99
|
+
# Then, if asked, shrink further to the caller's reasoning width. Never
|
|
100
|
+
# enlarge: a small screen is already easier to point at, and scaling up
|
|
101
|
+
# would invent precision the model does not have.
|
|
102
|
+
if [ -n "$max_width" ] && [ "$max_width" -gt 0 ] 2>/dev/null && [ "$img_w" -gt "$max_width" ]; then
|
|
103
|
+
sips --resampleWidth "$max_width" "$output" --out "$output" >/dev/null 2>&1
|
|
104
|
+
img_w=$(sips -g pixelWidth "$output" | tail -1 | awk '{print $2}')
|
|
105
|
+
img_h=$(sips -g pixelHeight "$output" | tail -1 | awk '{print $2}')
|
|
106
|
+
fi
|
|
107
|
+
|
|
85
108
|
# Crop FIRST, then grid — so grid labels show absolute screen coordinates
|
|
86
109
|
local origin_x=0 origin_y=0
|
|
87
110
|
if [ -n "$crop_json" ]; then
|
|
@@ -289,7 +312,9 @@ do_key() {
|
|
|
289
312
|
if [ ${#parts[@]} -eq 1 ]; then
|
|
290
313
|
key_name="${parts[0]}"
|
|
291
314
|
else
|
|
292
|
-
|
|
315
|
+
# macOS ships bash 3.2, which has no negative array indices — using one
|
|
316
|
+
# here made every modifier combo fail with "bad array subscript".
|
|
317
|
+
key_name="${parts[$(( ${#parts[@]} - 1 ))]}"
|
|
293
318
|
for ((i=0; i<${#parts[@]}-1; i++)); do
|
|
294
319
|
case "${parts[$i]}" in
|
|
295
320
|
command|cmd) modifiers="${modifiers}command down, " ;;
|
|
@@ -844,9 +869,221 @@ do_click_at() {
|
|
|
844
869
|
" >/dev/null 2>&1
|
|
845
870
|
}
|
|
846
871
|
|
|
872
|
+
# =============================================================================
|
|
873
|
+
# Element-level perception and action (Phase 2)
|
|
874
|
+
#
|
|
875
|
+
# Everything above works in screen coordinates: the agent takes a screenshot,
|
|
876
|
+
# guesses where a button is, and clicks a point. That misses, costs an image
|
|
877
|
+
# per step, and breaks on a different theme or window position.
|
|
878
|
+
#
|
|
879
|
+
# These work in elements. `snapshot` returns refs (@e1, @e2 …) with roles,
|
|
880
|
+
# names and frames; `click`/`type` accept a ref and go through the
|
|
881
|
+
# accessibility action, which does not care where the window sits or whether
|
|
882
|
+
# something overlaps it.
|
|
883
|
+
# =============================================================================
|
|
884
|
+
|
|
885
|
+
# ---------------------------------------------------------------------------
|
|
886
|
+
# snapshot — the elements of an app, as refs the agent can act on.
|
|
887
|
+
# ---------------------------------------------------------------------------
|
|
888
|
+
do_snapshot() {
|
|
889
|
+
local app max flags=()
|
|
890
|
+
app=$(printf '%s' "$INPUT" | jq -r '.app // empty')
|
|
891
|
+
max=$(printf '%s' "$INPUT" | jq -r '.max // empty')
|
|
892
|
+
[ -n "$app" ] && flags+=(--app "$app")
|
|
893
|
+
[ -n "$max" ] && flags+=(--max "$max")
|
|
894
|
+
[ "$(printf '%s' "$INPUT" | jq -r '.allWindows // false')" = "true" ] && flags+=(--all-windows)
|
|
895
|
+
[ "$(printf '%s' "$INPUT" | jq -r '.menus // false')" = "true" ] && flags+=(--menus)
|
|
896
|
+
cu_perceive snapshot ${flags[@]+"${flags[@]}"}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
# ---------------------------------------------------------------------------
|
|
900
|
+
# click-ref — press an element by ref.
|
|
901
|
+
#
|
|
902
|
+
# AXPress first: it reaches a button that is scrolled out of view or covered
|
|
903
|
+
# by another window, which a coordinate click cannot. Falls back to clicking
|
|
904
|
+
# the element's centre for the many controls that expose no press action.
|
|
905
|
+
# ---------------------------------------------------------------------------
|
|
906
|
+
do_click_ref() {
|
|
907
|
+
local ref result ok cx cy
|
|
908
|
+
ref=$(printf '%s' "$INPUT" | jq -r '.ref // empty')
|
|
909
|
+
require_param "ref" "$ref"
|
|
910
|
+
|
|
911
|
+
result=$(cu_perceive press --ref "$ref" 2>&1) || { printf '%s\n' "$result"; exit 1; }
|
|
912
|
+
ok=$(printf '%s' "$result" | jq -r '.success // false')
|
|
913
|
+
if [ "$ok" = "true" ]; then
|
|
914
|
+
printf '%s' "$result" | jq -c '{success:true, action:"click-ref", ref:.ref, method:"AXPress"}'
|
|
915
|
+
return 0
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
# No press action (or it refused) — click where the element is.
|
|
919
|
+
cx=$(printf '%s' "$result" | jq -r '.center[0] // empty')
|
|
920
|
+
cy=$(printf '%s' "$result" | jq -r '.center[1] // empty')
|
|
921
|
+
if [ -z "$cx" ] || [ -z "$cy" ]; then
|
|
922
|
+
printf '%s\n' "$result"
|
|
923
|
+
exit 1
|
|
924
|
+
fi
|
|
925
|
+
do_click_at "$cx" "$cy"
|
|
926
|
+
jq -n --arg r "$ref" --argjson x "$cx" --argjson y "$cy" \
|
|
927
|
+
'{success:true, action:"click-ref", ref:$r, method:"coordinate-fallback", x:$x, y:$y}'
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
# ---------------------------------------------------------------------------
|
|
931
|
+
# fill-ref — set a field's value directly.
|
|
932
|
+
#
|
|
933
|
+
# More reliable than typing: no dependence on focus, on the keyboard layout,
|
|
934
|
+
# or on an input method being in the right mode.
|
|
935
|
+
# ---------------------------------------------------------------------------
|
|
936
|
+
do_fill_ref() {
|
|
937
|
+
local ref text
|
|
938
|
+
ref=$(printf '%s' "$INPUT" | jq -r '.ref // empty')
|
|
939
|
+
text=$(printf '%s' "$INPUT" | jq -r '.text // empty')
|
|
940
|
+
require_param "ref" "$ref"
|
|
941
|
+
require_param "text" "$text"
|
|
942
|
+
cu_perceive set-value --ref "$ref" --text "$text"
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
# ---------------------------------------------------------------------------
|
|
946
|
+
# resolve — what is at this ref now.
|
|
947
|
+
#
|
|
948
|
+
# Worth calling after something may have changed the window: it reports
|
|
949
|
+
# whether the element still matches what the snapshot recorded.
|
|
950
|
+
# ---------------------------------------------------------------------------
|
|
951
|
+
do_resolve() {
|
|
952
|
+
local ref
|
|
953
|
+
ref=$(printf '%s' "$INPUT" | jq -r '.ref // empty')
|
|
954
|
+
require_param "ref" "$ref"
|
|
955
|
+
cu_perceive resolve --ref "$ref"
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
# ---------------------------------------------------------------------------
|
|
959
|
+
# ocr — the text on screen and where it is.
|
|
960
|
+
#
|
|
961
|
+
# Covers what accessibility does not expose: a canvas, a PDF page, an app
|
|
962
|
+
# that simply does not implement AX. Local and free (macOS Vision).
|
|
963
|
+
# ---------------------------------------------------------------------------
|
|
964
|
+
do_ocr() {
|
|
965
|
+
local region flags=()
|
|
966
|
+
region=$(printf '%s' "$INPUT" | jq -r 'if .region then "\(.region.x),\(.region.y),\(.region.w),\(.region.h)" else empty end')
|
|
967
|
+
[ -n "$region" ] && flags+=(--region "$region")
|
|
968
|
+
local image; image=$(printf '%s' "$INPUT" | jq -r '.image // empty')
|
|
969
|
+
[ -n "$image" ] && flags+=(--image "$image")
|
|
970
|
+
cu_perceive ocr ${flags[@]+"${flags[@]}"}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
# ---------------------------------------------------------------------------
|
|
974
|
+
# displays — every screen and its frame, so coordinates are unambiguous.
|
|
975
|
+
# ---------------------------------------------------------------------------
|
|
976
|
+
do_displays() {
|
|
977
|
+
cu_perceive displays
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
# ---------------------------------------------------------------------------
|
|
981
|
+
# wait-for — block until the screen is in the expected state.
|
|
982
|
+
#
|
|
983
|
+
# The alternative is sleeping and hoping: an agent that clicks during an
|
|
984
|
+
# animation hits nothing, and one that sleeps long enough to be safe is slow
|
|
985
|
+
# on every step instead.
|
|
986
|
+
#
|
|
987
|
+
# {"action":"wait-for","app":"Numbers"} that app is frontmost
|
|
988
|
+
# {"action":"wait-for","ref":"@e12"} that element resolves
|
|
989
|
+
# {"action":"wait-for","text":"Export"} that text is on screen
|
|
990
|
+
# {"action":"wait-for","idle":true} the screen stopped changing
|
|
991
|
+
# ---------------------------------------------------------------------------
|
|
992
|
+
do_wait_for() {
|
|
993
|
+
local app ref text idle timeout deadline
|
|
994
|
+
app=$(printf '%s' "$INPUT" | jq -r '.app // empty')
|
|
995
|
+
ref=$(printf '%s' "$INPUT" | jq -r '.ref // empty')
|
|
996
|
+
text=$(printf '%s' "$INPUT" | jq -r '.text // empty')
|
|
997
|
+
idle=$(printf '%s' "$INPUT" | jq -r '.idle // false')
|
|
998
|
+
timeout=$(printf '%s' "$INPUT" | jq -r '.timeoutMs // 10000')
|
|
999
|
+
deadline=$(( $(date +%s) + timeout / 1000 ))
|
|
1000
|
+
|
|
1001
|
+
if [ -z "$app" ] && [ -z "$ref" ] && [ -z "$text" ] && [ "$idle" != "true" ]; then
|
|
1002
|
+
error_exit "wait-for needs one of: app, ref, text, idle"
|
|
1003
|
+
fi
|
|
1004
|
+
|
|
1005
|
+
local previous="" current
|
|
1006
|
+
while [ "$(date +%s)" -le "$deadline" ]; do
|
|
1007
|
+
if [ -n "$app" ]; then
|
|
1008
|
+
local front
|
|
1009
|
+
front=$(osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null)
|
|
1010
|
+
if [ "$front" = "$app" ]; then
|
|
1011
|
+
jq -n --arg a "$app" '{success:true, action:"wait-for", matched:"app", app:$a}'; return 0
|
|
1012
|
+
fi
|
|
1013
|
+
fi
|
|
1014
|
+
if [ -n "$ref" ]; then
|
|
1015
|
+
if cu_perceive resolve --ref "$ref" 2>/dev/null | jq -e '.success and .matches' >/dev/null 2>&1; then
|
|
1016
|
+
jq -n --arg r "$ref" '{success:true, action:"wait-for", matched:"ref", ref:$r}'; return 0
|
|
1017
|
+
fi
|
|
1018
|
+
fi
|
|
1019
|
+
if [ -n "$text" ]; then
|
|
1020
|
+
if cu_perceive ocr 2>/dev/null | jq -e --arg t "$text" '[.items[]?.text | select(contains($t))] | length > 0' >/dev/null 2>&1; then
|
|
1021
|
+
jq -n --arg t "$text" '{success:true, action:"wait-for", matched:"text", text:$t}'; return 0
|
|
1022
|
+
fi
|
|
1023
|
+
fi
|
|
1024
|
+
if [ "$idle" = "true" ]; then
|
|
1025
|
+
# Two consecutive identical frames mean nothing is animating.
|
|
1026
|
+
local shot="${TMPDIR_CU}/idle-$$.png"
|
|
1027
|
+
screencapture -x -t jpg "$shot" 2>/dev/null
|
|
1028
|
+
current=$(shasum -a 1 "$shot" 2>/dev/null | cut -d' ' -f1)
|
|
1029
|
+
rm -f "$shot"
|
|
1030
|
+
if [ -n "$previous" ] && [ "$current" = "$previous" ]; then
|
|
1031
|
+
jq -n '{success:true, action:"wait-for", matched:"idle"}'; return 0
|
|
1032
|
+
fi
|
|
1033
|
+
previous="$current"
|
|
1034
|
+
fi
|
|
1035
|
+
sleep 0.4
|
|
1036
|
+
done
|
|
1037
|
+
|
|
1038
|
+
cu_fail "wait_timeout" \
|
|
1039
|
+
"Nothing matched within ${timeout}ms. Take a snapshot to see what is actually on screen." \
|
|
1040
|
+
"$(jq -n --argjson t "$timeout" '{timeoutMs:$t}')"
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
# ---------------------------------------------------------------------------
|
|
1044
|
+
# request-human — hand the machine back for something an agent must not do.
|
|
1045
|
+
#
|
|
1046
|
+
# The deleted `vnc-browser` skill described this and shipped no script. The
|
|
1047
|
+
# honest version is smaller than a VNC tunnel and covers the cases that
|
|
1048
|
+
# actually arise: a CAPTCHA, a two-factor prompt, a login, a decision the
|
|
1049
|
+
# owner has to make. The agent pauses itself, says what it needs and why, and
|
|
1050
|
+
# waits — rather than trying to be clever, which for a login means typing a
|
|
1051
|
+
# password it must never type.
|
|
1052
|
+
#
|
|
1053
|
+
# Pausing (not stopping) is the point: the owner deals with it and the task
|
|
1054
|
+
# picks up, using the same banner button they already have.
|
|
1055
|
+
# ---------------------------------------------------------------------------
|
|
1056
|
+
do_request_human() {
|
|
1057
|
+
local reason detail shot
|
|
1058
|
+
reason=$(printf '%s' "$INPUT" | jq -r '.reason // empty')
|
|
1059
|
+
detail=$(printf '%s' "$INPUT" | jq -r '.detail // empty')
|
|
1060
|
+
require_param "reason" "$reason"
|
|
1061
|
+
|
|
1062
|
+
# A picture of what the agent is stuck on is worth more than its
|
|
1063
|
+
# description of it, and the owner may be reading this on a phone.
|
|
1064
|
+
shot=$(capture_thumb handover 2>/dev/null || true)
|
|
1065
|
+
|
|
1066
|
+
# Pause rather than stop: the task is not cancelled, it is waiting.
|
|
1067
|
+
printf 'awaiting-human\n' > "$DESKTOP_PAUSE" 2>/dev/null || true
|
|
1068
|
+
cu_presence begin --agent "$HOLDER" --goal "needs you: $reason"
|
|
1069
|
+
|
|
1070
|
+
jq -n --arg r "$reason" --arg d "$detail" --arg s "$HOLDER" --arg shot "$shot" \
|
|
1071
|
+
'{success:true, action:"request-human", waiting:true, reason:$r,
|
|
1072
|
+
detail:$d, agent:$s,
|
|
1073
|
+
note:"Desktop control is paused until the owner resumes it (banner → Resume, or remove ~/.crewly/desktop.pause). Do not try to work around this."}
|
|
1074
|
+
+ (if $shot == "" then {} else {screenshot:$shot} end)'
|
|
1075
|
+
}
|
|
1076
|
+
|
|
847
1077
|
# ---------------------------------------------------------------------------
|
|
848
1078
|
# Action dispatch
|
|
1079
|
+
#
|
|
1080
|
+
# The "after" audit shot is taken on the way out, so the log holds a pair
|
|
1081
|
+
# showing what the action actually changed — the point of keeping evidence at
|
|
1082
|
+
# all. It runs on every exit path, including a failure, because a failed
|
|
1083
|
+
# action that moved something is exactly what one wants to look at later.
|
|
849
1084
|
# ---------------------------------------------------------------------------
|
|
1085
|
+
trap 'cu_log_after 2>/dev/null || true' EXIT
|
|
1086
|
+
|
|
850
1087
|
case "$ACTION" in
|
|
851
1088
|
screenshot) do_screenshot ;;
|
|
852
1089
|
click) do_click ;;
|
|
@@ -860,5 +1097,14 @@ case "$ACTION" in
|
|
|
860
1097
|
list-apps) do_list_apps ;;
|
|
861
1098
|
find) do_find ;;
|
|
862
1099
|
click-text) do_click_text ;;
|
|
863
|
-
|
|
1100
|
+
check-permissions) do_check_permissions ;;
|
|
1101
|
+
snapshot) do_snapshot ;;
|
|
1102
|
+
click-ref) do_click_ref ;;
|
|
1103
|
+
fill-ref) do_fill_ref ;;
|
|
1104
|
+
resolve) do_resolve ;;
|
|
1105
|
+
ocr) do_ocr ;;
|
|
1106
|
+
displays) do_displays ;;
|
|
1107
|
+
wait-for) do_wait_for ;;
|
|
1108
|
+
request-human) do_request_human ;;
|
|
1109
|
+
*) error_exit "Unknown action: $ACTION. Valid: screenshot, click, move, type, key, scroll, drag, focus, open-url, list-apps, find, click-text, check-permissions, snapshot, click-ref, fill-ref, resolve, ocr, displays, wait-for, request-human" ;;
|
|
864
1110
|
esac
|
|
@@ -60,3 +60,13 @@ bash execute.sh '{"summary":"Standup","start":"2026-09-21T09:00:00Z","end":"2026
|
|
|
60
60
|
Missing `--summary` / `--start` / `--end` → `{"error":"Missing required parameter: …"}` on stderr, exit 1.
|
|
61
61
|
`{"success":false,"reason":"not_connected","hint":"<connect URL>"}` (exit 1) when the owner has not connected Google Workspace;
|
|
62
62
|
`reason` `validation` when Google cannot parse a time.
|
|
63
|
+
|
|
64
|
+
## Choosing a Google account
|
|
65
|
+
|
|
66
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
bash execute.sh --account work@company.com ...
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -28,6 +28,7 @@ Options:
|
|
|
28
28
|
--description Body text
|
|
29
29
|
--attendee Attendee email (repeat for several)
|
|
30
30
|
--calendar Calendar id (default: primary)
|
|
31
|
+
--account Which connected Google account to act as (default: your primary)
|
|
31
32
|
--help | -h Show this help
|
|
32
33
|
EOF_USAGE
|
|
33
34
|
}
|
|
@@ -41,6 +42,7 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
41
42
|
shift || true
|
|
42
43
|
fi
|
|
43
44
|
|
|
45
|
+
ACCOUNT=""
|
|
44
46
|
while [[ $# -gt 0 ]]; do
|
|
45
47
|
case "$1" in
|
|
46
48
|
--summary|--title) [ $# -ge 2 ] || error_exit "--summary requires a value"; SUMMARY="$2"; shift 2 ;;
|
|
@@ -51,6 +53,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
51
53
|
--calendar|-c) [ $# -ge 2 ] || error_exit "--calendar requires a value"; CALENDAR="$2"; shift 2 ;;
|
|
52
54
|
--attendee) [ $# -ge 2 ] || error_exit "--attendee requires a value"
|
|
53
55
|
ATTENDEES_JSON=$(jq -cn --argjson arr "$ATTENDEES_JSON" --arg v "$2" '$arr + [$v]'); shift 2 ;;
|
|
56
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
54
57
|
--help|-h) print_usage; exit 0 ;;
|
|
55
58
|
*) error_exit "Unknown option: $1" ;;
|
|
56
59
|
esac
|
|
@@ -67,7 +70,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
67
70
|
if [ "$(printf '%s' "$ATTENDEES_JSON" | jq 'length')" -eq 0 ]; then
|
|
68
71
|
ATTENDEES_JSON=$(printf '%s' "$INPUT" | jq -c '(.attendees // []) | if type == "string" then split(",") | map(gsub("^\\s+|\\s+$"; "")) | map(select(. != "")) else . end')
|
|
69
72
|
fi
|
|
73
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
70
74
|
fi
|
|
75
|
+
# Route the call at one connected Google account; unset means the default.
|
|
76
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
71
77
|
|
|
72
78
|
require_param "summary (--summary)" "$SUMMARY"
|
|
73
79
|
require_param "start (--start)" "$START"
|
|
@@ -56,3 +56,13 @@ bash execute.sh '{"calendarId":"team@group.calendar.google.com","from":"…","to
|
|
|
56
56
|
|
|
57
57
|
`{"success":false,"reason":"not_connected","hint":"<connect URL>"}` (exit 1)
|
|
58
58
|
when the owner has not connected Google Workspace.
|
|
59
|
+
|
|
60
|
+
## Choosing a Google account
|
|
61
|
+
|
|
62
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bash execute.sh --account work@company.com ...
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -24,6 +24,7 @@ Options:
|
|
|
24
24
|
--to Upper bound (default: now + 7 days)
|
|
25
25
|
--calendar Calendar id (default: primary)
|
|
26
26
|
--max Result cap (default 50, max 250)
|
|
27
|
+
--account Which connected Google account to act as (default: your primary)
|
|
27
28
|
--help | -h Show this help
|
|
28
29
|
EOF_USAGE
|
|
29
30
|
}
|
|
@@ -48,12 +49,14 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
48
49
|
shift || true
|
|
49
50
|
fi
|
|
50
51
|
|
|
52
|
+
ACCOUNT=""
|
|
51
53
|
while [[ $# -gt 0 ]]; do
|
|
52
54
|
case "$1" in
|
|
53
55
|
--from) [ $# -ge 2 ] || error_exit "--from requires a value"; FROM="$2"; shift 2 ;;
|
|
54
56
|
--to) [ $# -ge 2 ] || error_exit "--to requires a value"; TO="$2"; shift 2 ;;
|
|
55
57
|
--calendar|-c) [ $# -ge 2 ] || error_exit "--calendar requires a value"; CALENDAR="$2"; shift 2 ;;
|
|
56
58
|
--max|-n) [ $# -ge 2 ] || error_exit "--max requires a value"; MAX="$2"; shift 2 ;;
|
|
59
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
57
60
|
--help|-h) print_usage; exit 0 ;;
|
|
58
61
|
*) error_exit "Unknown option: $1" ;;
|
|
59
62
|
esac
|
|
@@ -65,7 +68,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
65
68
|
[ -z "$TO" ] && TO=$(printf '%s' "$INPUT" | jq -r '.to // .timeMax // empty')
|
|
66
69
|
[ -z "$CALENDAR" ] && CALENDAR=$(printf '%s' "$INPUT" | jq -r '.calendarId // .calendar // empty')
|
|
67
70
|
[ -z "$MAX" ] && MAX=$(printf '%s' "$INPUT" | jq -r '.max // empty')
|
|
71
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
68
72
|
fi
|
|
73
|
+
# Route the call at one connected Google account; unset means the default.
|
|
74
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
69
75
|
|
|
70
76
|
[ -z "$FROM" ] && FROM=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
71
77
|
[ -z "$TO" ] && TO=$(iso_now_plus_days "$DEFAULT_WINDOW_DAYS")
|
|
@@ -48,3 +48,13 @@ bash execute.sh --id "https://docs.google.com/document/d/1AbC…/edit"
|
|
|
48
48
|
when the owner has not connected Google Workspace (Settings → Integrations).
|
|
49
49
|
A `403` with `reason: "google_error"` on a write means the file was not
|
|
50
50
|
created by Crewly — the grant only edits files Crewly made (`drive.file`).
|
|
51
|
+
|
|
52
|
+
## Choosing a Google account
|
|
53
|
+
|
|
54
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
bash execute.sh --account work@company.com ...
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -20,6 +20,7 @@ Usage:
|
|
|
20
20
|
|
|
21
21
|
Options:
|
|
22
22
|
--id Document id (from drive-search or the docs.google.com URL)
|
|
23
|
+
--account Which connected Google account to act as (default: your primary)
|
|
23
24
|
--help | -h Show this help
|
|
24
25
|
EOF_USAGE
|
|
25
26
|
}
|
|
@@ -48,10 +49,11 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
48
49
|
INPUT_JSON="$1"
|
|
49
50
|
shift || true
|
|
50
51
|
fi
|
|
51
|
-
ID=""
|
|
52
|
+
ID=""; ACCOUNT=""
|
|
52
53
|
while [[ $# -gt 0 ]]; do
|
|
53
54
|
case "$1" in
|
|
54
55
|
--id) [ $# -ge 2 ] || error_exit "--id requires a value"; ID="$2"; shift 2 ;;
|
|
56
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
55
57
|
--help|-h) print_usage; exit 0 ;;
|
|
56
58
|
*) error_exit "Unknown option: $1" ;;
|
|
57
59
|
esac
|
|
@@ -59,7 +61,10 @@ done
|
|
|
59
61
|
if [ -n "$INPUT_JSON" ]; then
|
|
60
62
|
INPUT=$(read_json_input "$INPUT_JSON")
|
|
61
63
|
[ -z "$ID" ] && ID=$(printf '%s' "$INPUT" | jq -r '.id // .documentId // empty')
|
|
64
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
62
65
|
fi
|
|
66
|
+
# Route the call at one connected Google account; unset means the default.
|
|
67
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
63
68
|
# Accept a full docs.google.com URL too.
|
|
64
69
|
ID=$(printf '%s' "$ID" | sed -E 's#.*/document/d/([^/?]+).*#\1#')
|
|
65
70
|
require_param "id (--id)" "$ID"
|
|
@@ -53,3 +53,13 @@ re-connect Google Workspace once the scope is enabled".
|
|
|
53
53
|
when the owner has not connected Google Workspace (Settings → Integrations).
|
|
54
54
|
A `403` with `reason: "google_error"` on a write means the file was not
|
|
55
55
|
created by Crewly — the grant only edits files Crewly made (`drive.file`).
|
|
56
|
+
|
|
57
|
+
## Choosing a Google account
|
|
58
|
+
|
|
59
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bash execute.sh --account work@company.com ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -27,6 +27,7 @@ Options:
|
|
|
27
27
|
--id Append to this document instead (must be one Crewly created, or the grant needs the Docs scope)
|
|
28
28
|
--text Body text (plain; blank line = new paragraph)
|
|
29
29
|
--text-file Read the body from a file
|
|
30
|
+
--account Which connected Google account to act as (default: your primary)
|
|
30
31
|
--help | -h Show this help
|
|
31
32
|
EOF_USAGE
|
|
32
33
|
}
|
|
@@ -55,13 +56,14 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
55
56
|
INPUT_JSON="$1"
|
|
56
57
|
shift || true
|
|
57
58
|
fi
|
|
58
|
-
TITLE=""; ID=""; TEXT=""; TEXT_FILE=""
|
|
59
|
+
TITLE=""; ID=""; TEXT=""; TEXT_FILE=""; ACCOUNT=""
|
|
59
60
|
while [[ $# -gt 0 ]]; do
|
|
60
61
|
case "$1" in
|
|
61
62
|
--title) [ $# -ge 2 ] || error_exit "--title requires a value"; TITLE="$2"; shift 2 ;;
|
|
62
63
|
--id) [ $# -ge 2 ] || error_exit "--id requires a value"; ID="$2"; shift 2 ;;
|
|
63
64
|
--text) [ $# -ge 2 ] || error_exit "--text requires a value"; TEXT="$2"; shift 2 ;;
|
|
64
65
|
--text-file) [ $# -ge 2 ] || error_exit "--text-file requires a value"; TEXT_FILE="$2"; shift 2 ;;
|
|
66
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
65
67
|
--help|-h) print_usage; exit 0 ;;
|
|
66
68
|
*) error_exit "Unknown option: $1" ;;
|
|
67
69
|
esac
|
|
@@ -71,7 +73,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
71
73
|
[ -z "$TITLE" ] && TITLE=$(printf '%s' "$INPUT" | jq -r '.title // empty')
|
|
72
74
|
[ -z "$ID" ] && ID=$(printf '%s' "$INPUT" | jq -r '.id // .documentId // empty')
|
|
73
75
|
[ -z "$TEXT" ] && TEXT=$(printf '%s' "$INPUT" | jq -r '.text // .body // empty')
|
|
76
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
74
77
|
fi
|
|
78
|
+
# Route the call at one connected Google account; unset means the default.
|
|
79
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
75
80
|
if [ -n "$TEXT_FILE" ]; then
|
|
76
81
|
[ -f "$TEXT_FILE" ] || error_exit "text file not found: $TEXT_FILE"
|
|
77
82
|
TEXT=$(cat "$TEXT_FILE")
|
|
@@ -51,3 +51,13 @@ Files over 10 MB are refused with a `webViewLink` to open instead.
|
|
|
51
51
|
when the owner has not connected Google Workspace (Settings → Integrations).
|
|
52
52
|
A `403` with `reason: "google_error"` on a write means the file was not
|
|
53
53
|
created by Crewly — the grant only edits files Crewly made (`drive.file`).
|
|
54
|
+
|
|
55
|
+
## Choosing a Google account
|
|
56
|
+
|
|
57
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
bash execute.sh --account work@company.com ...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -21,6 +21,7 @@ Usage:
|
|
|
21
21
|
Options:
|
|
22
22
|
--id Drive file id (required; from drive-search)
|
|
23
23
|
--out Save the content to this path instead of printing it (binary files are decoded)
|
|
24
|
+
--account Which connected Google account to act as (default: your primary)
|
|
24
25
|
--help | -h Show this help
|
|
25
26
|
EOF_USAGE
|
|
26
27
|
}
|
|
@@ -49,11 +50,12 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
49
50
|
INPUT_JSON="$1"
|
|
50
51
|
shift || true
|
|
51
52
|
fi
|
|
52
|
-
ID=""; OUT_PATH=""
|
|
53
|
+
ID=""; OUT_PATH=""; ACCOUNT=""
|
|
53
54
|
while [[ $# -gt 0 ]]; do
|
|
54
55
|
case "$1" in
|
|
55
56
|
--id) [ $# -ge 2 ] || error_exit "--id requires a value"; ID="$2"; shift 2 ;;
|
|
56
57
|
--out|-o) [ $# -ge 2 ] || error_exit "--out requires a value"; OUT_PATH="$2"; shift 2 ;;
|
|
58
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
57
59
|
--help|-h) print_usage; exit 0 ;;
|
|
58
60
|
*) error_exit "Unknown option: $1" ;;
|
|
59
61
|
esac
|
|
@@ -62,7 +64,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
62
64
|
INPUT=$(read_json_input "$INPUT_JSON")
|
|
63
65
|
[ -z "$ID" ] && ID=$(printf '%s' "$INPUT" | jq -r '.id // .fileId // empty')
|
|
64
66
|
[ -z "$OUT_PATH" ] && OUT_PATH=$(printf '%s' "$INPUT" | jq -r '.out // empty')
|
|
67
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
65
68
|
fi
|
|
69
|
+
# Route the call at one connected Google account; unset means the default.
|
|
70
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
66
71
|
require_param "id (--id)" "$ID"
|
|
67
72
|
RESPONSE=$(call GET "/google/drive/files/$(uri "$ID")/content") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
68
73
|
if printf '%s' "$RESPONSE" | jq -e '.truncated == true' >/dev/null 2>&1; then printf '%s\n' "$RESPONSE"; exit 0; fi
|
|
@@ -53,3 +53,13 @@ else → `drive-read`.
|
|
|
53
53
|
when the owner has not connected Google Workspace (Settings → Integrations).
|
|
54
54
|
A `403` with `reason: "google_error"` on a write means the file was not
|
|
55
55
|
created by Crewly — the grant only edits files Crewly made (`drive.file`).
|
|
56
|
+
|
|
57
|
+
## Choosing a Google account
|
|
58
|
+
|
|
59
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bash execute.sh --account work@company.com ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -23,6 +23,7 @@ Options:
|
|
|
23
23
|
--mime Restrict to a type: doc | sheet | slides | pdf | folder | any MIME type
|
|
24
24
|
--folder Restrict to a Drive folder id
|
|
25
25
|
--max Result cap (default 20, max 100)
|
|
26
|
+
--account Which connected Google account to act as (default: your primary)
|
|
26
27
|
--help | -h Show this help
|
|
27
28
|
EOF_USAGE
|
|
28
29
|
}
|
|
@@ -51,13 +52,14 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
51
52
|
INPUT_JSON="$1"
|
|
52
53
|
shift || true
|
|
53
54
|
fi
|
|
54
|
-
QUERY=""; MIME=""; FOLDER=""; MAX=""
|
|
55
|
+
QUERY=""; MIME=""; FOLDER=""; MAX=""; ACCOUNT=""
|
|
55
56
|
while [[ $# -gt 0 ]]; do
|
|
56
57
|
case "$1" in
|
|
57
58
|
--query|-q) [ $# -ge 2 ] || error_exit "--query requires a value"; QUERY="$2"; shift 2 ;;
|
|
58
59
|
--mime) [ $# -ge 2 ] || error_exit "--mime requires a value"; MIME="$2"; shift 2 ;;
|
|
59
60
|
--folder) [ $# -ge 2 ] || error_exit "--folder requires a value"; FOLDER="$2"; shift 2 ;;
|
|
60
61
|
--max|-n) [ $# -ge 2 ] || error_exit "--max requires a value"; MAX="$2"; shift 2 ;;
|
|
62
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
61
63
|
--help|-h) print_usage; exit 0 ;;
|
|
62
64
|
*) error_exit "Unknown option: $1" ;;
|
|
63
65
|
esac
|
|
@@ -68,7 +70,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
68
70
|
[ -z "$MIME" ] && MIME=$(printf '%s' "$INPUT" | jq -r '.mime // .mimeType // empty')
|
|
69
71
|
[ -z "$FOLDER" ] && FOLDER=$(printf '%s' "$INPUT" | jq -r '.folder // .folderId // empty')
|
|
70
72
|
[ -z "$MAX" ] && MAX=$(printf '%s' "$INPUT" | jq -r '.max // empty')
|
|
73
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
71
74
|
fi
|
|
75
|
+
# Route the call at one connected Google account; unset means the default.
|
|
76
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
72
77
|
case "$MIME" in
|
|
73
78
|
doc|docs) MIME="application/vnd.google-apps.document" ;;
|
|
74
79
|
sheet|sheets) MIME="application/vnd.google-apps.spreadsheet" ;;
|
|
@@ -52,3 +52,13 @@ permissions are the owner's — this skill does not change sharing).
|
|
|
52
52
|
when the owner has not connected Google Workspace (Settings → Integrations).
|
|
53
53
|
A `403` with `reason: "google_error"` on a write means the file was not
|
|
54
54
|
created by Crewly — the grant only edits files Crewly made (`drive.file`).
|
|
55
|
+
|
|
56
|
+
## Choosing a Google account
|
|
57
|
+
|
|
58
|
+
Several Google accounts can be connected at once. Without `--account` the call uses the default one (the first you connected, or whichever you marked default on the Connections page). Name one explicitly when it matters:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
bash execute.sh --account work@company.com ...
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The account must be connected *for this product* — Google consent is per product (Gmail / Calendar / Drive), so an account connected only for Calendar cannot read Drive.
|
|
@@ -28,6 +28,7 @@ Options:
|
|
|
28
28
|
--mime MIME type override
|
|
29
29
|
--folder Destination folder id (default: My Drive root)
|
|
30
30
|
--convert Convert on upload: doc | sheet | slides (e.g. a .docx/.csv/.pptx becomes a Google-native file)
|
|
31
|
+
--account Which connected Google account to act as (default: your primary)
|
|
31
32
|
--help | -h Show this help
|
|
32
33
|
EOF_USAGE
|
|
33
34
|
}
|
|
@@ -56,7 +57,7 @@ if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
|
|
|
56
57
|
INPUT_JSON="$1"
|
|
57
58
|
shift || true
|
|
58
59
|
fi
|
|
59
|
-
FILE=""; NAME=""; TEXT=""; MIME=""; FOLDER=""; CONVERT=""
|
|
60
|
+
FILE=""; NAME=""; TEXT=""; MIME=""; FOLDER=""; CONVERT=""; ACCOUNT=""
|
|
60
61
|
while [[ $# -gt 0 ]]; do
|
|
61
62
|
case "$1" in
|
|
62
63
|
--path|-p) [ $# -ge 2 ] || error_exit "--path requires a value"; FILE="$2"; shift 2 ;;
|
|
@@ -65,6 +66,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
65
66
|
--mime) [ $# -ge 2 ] || error_exit "--mime requires a value"; MIME="$2"; shift 2 ;;
|
|
66
67
|
--folder) [ $# -ge 2 ] || error_exit "--folder requires a value"; FOLDER="$2"; shift 2 ;;
|
|
67
68
|
--convert) [ $# -ge 2 ] || error_exit "--convert requires a value"; CONVERT="$2"; shift 2 ;;
|
|
69
|
+
--account) [ $# -ge 2 ] || error_exit "--account requires a value"; ACCOUNT="$2"; shift 2 ;;
|
|
68
70
|
--help|-h) print_usage; exit 0 ;;
|
|
69
71
|
*) error_exit "Unknown option: $1" ;;
|
|
70
72
|
esac
|
|
@@ -77,7 +79,10 @@ if [ -n "$INPUT_JSON" ]; then
|
|
|
77
79
|
[ -z "$MIME" ] && MIME=$(printf '%s' "$INPUT" | jq -r '.mime // .mimeType // empty')
|
|
78
80
|
[ -z "$FOLDER" ] && FOLDER=$(printf '%s' "$INPUT" | jq -r '.folder // .folderId // empty')
|
|
79
81
|
[ -z "$CONVERT" ] && CONVERT=$(printf '%s' "$INPUT" | jq -r '.convert // .convertTo // empty')
|
|
82
|
+
[ -z "$ACCOUNT" ] && ACCOUNT=$(printf '%s' "$INPUT" | jq -r '.account // empty')
|
|
80
83
|
fi
|
|
84
|
+
# Route the call at one connected Google account; unset means the default.
|
|
85
|
+
[ -n "$ACCOUNT" ] && export CREWLY_GOOGLE_ACCOUNT="$ACCOUNT"
|
|
81
86
|
case "$CONVERT" in
|
|
82
87
|
doc|docs) CONVERT="application/vnd.google-apps.document" ;;
|
|
83
88
|
sheet|sheets) CONVERT="application/vnd.google-apps.spreadsheet" ;;
|