crewly 1.20.321 → 1.20.323
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/agent/core/publish-app/SKILL.md +4 -2
- package/config/skills/agent/core/publish-app/execute.sh +17 -5
- package/config/skills/agent/core/publish-app/execute.test.sh +13 -2
- package/config/skills/agent/core/share-file/SKILL.md +73 -0
- package/config/skills/agent/core/share-file/execute.sh +184 -0
- package/config/skills/agent/core/share-file/execute.test.sh +122 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts +12 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.controller.js +39 -0
- package/dist/backend/backend/src/controllers/apps/apps.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts +5 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/apps/apps.routes.js +13 -2
- package/dist/backend/backend/src/controllers/apps/apps.routes.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts +15 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.js +6 -1
- package/dist/backend/backend/src/services/apps/apps-registry.service.js.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.d.ts +22 -3
- package/dist/backend/backend/src/services/apps/apps.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/apps/apps.service.js +43 -11
- package/dist/backend/backend/src/services/apps/apps.service.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -1
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -1
- package/package.json +1 -1
|
@@ -51,7 +51,8 @@ bash execute.sh --dir ./groceries-app --note "add totals" # next version, s
|
|
|
51
51
|
bash execute.sh --html ./timer.html --name "Timer"
|
|
52
52
|
bash execute.sh --app 28au74d9cj --rollback 2 # back to version 2
|
|
53
53
|
bash execute.sh --app 28au74d9cj --versions
|
|
54
|
-
bash execute.sh --app 28au74d9cj --transfer-to edu-game-milo-13e8d3ca # hand the app to another agent / team
|
|
54
|
+
bash execute.sh --app 28au74d9cj --transfer-to edu-game-milo-13e8d3ca # hand the app to another agent / team (orchestrator or owner only)
|
|
55
|
+
bash execute.sh --app 28au74d9cj --delete # delete (unpublish) a private app your team published
|
|
55
56
|
bash execute.sh --dir ./daily-brief --owner 'channel:#daily-brief' # publish, and send the owner's comments to a channel
|
|
56
57
|
bash execute.sh --app 28au74d9cj --owner 'team:Dev' # change only who gets the comments
|
|
57
58
|
bash execute.sh --list # apps published from this machine
|
|
@@ -140,7 +141,8 @@ bash execute.sh --app 28au74d9cj --private # pr
|
|
|
140
141
|
collections) still needs a new request the owner approves.
|
|
141
142
|
- Collaborators: an app another team published is not yours to republish. `--app <id>` from a team that did not publish it is refused (`not_your_app`), including when the owner added your team as a collaborator: collaborators get the app's **data and comments** only (use `app-data`; ask with `app-data --app <id> --request-access`). To change the page itself, ask the publisher or the owner to transfer the app (`--transfer-to`). An owner agent can let an agent on any of the owner's machines work in the app's data: `app-data --app <id> --add-collaborator Rex` (or `Rex@iriss-air` when the name is on several machines).
|
|
142
143
|
- Owner: `--owner channel:#<name> | team:<name> | agent:<name> | default` sets who the owner's **comments** go to (a channel's members from every team see them; see `app-data` → Owner). It does not change who publishes. Only the app's owner agents may change it (by default the publisher); with a publish the owner is set right after it, and an `ownerError` in the output means only that part failed.
|
|
143
|
-
-
|
|
144
|
+
- Delete: `--app <appId> --delete` removes an app your team published (URL stops working at once; versions, files and data are removed, so it cannot be undone). Agents may delete only PRIVATE apps; for a public app run `--private` first, or ask the owner. `--list` shows each app's owning team.
|
|
145
|
+
- Transfer: `--app <appId> --transfer-to <session>` hands the app to another agent, e.g. when the owner moves the work to a new team. Only the orchestrator or the owner may do it (team agents and leads ask the orchestrator), every hand-over is recorded (who, when, from, to), versions, data, comments and public/private status stay as they are, and the target must be a member of an active team on this machine. Afterwards the new publisher publishes with `--app <appId> --dir <its directory>`, comments and changes go to it, and the old team can no longer publish, roll back or use the app's data. Both agents get a short note.
|
|
144
146
|
- Thumbnails: the owner's portal list shows a small screenshot of each app. Publishing (and rollback) takes it automatically in the background when this machine has Chrome or Chromium; `bash execute.sh --app <appId> --refresh-thumbnail` re-takes it now (answers `captured:false` with a reason such as `no_browser` when it cannot).
|
|
145
147
|
- `--private` is always allowed and takes effect at once. Use it if anything
|
|
146
148
|
looks wrong (spam, abuse, a leak).
|
|
@@ -28,7 +28,8 @@ Usage:
|
|
|
28
28
|
bash execute.sh --app <appId> --dir ./my-app # republish to a known app explicitly
|
|
29
29
|
bash execute.sh --app <appId> --rollback <version> # make an earlier version current
|
|
30
30
|
bash execute.sh --app <appId> --versions # list versions
|
|
31
|
-
bash execute.sh --app <appId> --transfer-to <session> # hand the app to another agent / team
|
|
31
|
+
bash execute.sh --app <appId> --transfer-to <session> # hand the app to another agent / team (orchestrator or owner only)
|
|
32
|
+
bash execute.sh --app <appId> --delete # delete (unpublish) an app your team published (private apps)
|
|
32
33
|
bash execute.sh --app <appId> --owner 'channel:#daily-brief' # who gets the owner's comments (also with a publish)
|
|
33
34
|
bash execute.sh --list # apps published from this machine
|
|
34
35
|
bash execute.sh --app <appId> --share [--ttl-days 7] # fresh one-tap link card to the owner (no publish)
|
|
@@ -81,12 +82,14 @@ Options:
|
|
|
81
82
|
--unlist-template Take a template off the Marketplace (apps made from it are not affected)
|
|
82
83
|
--rollback Version number to make current (needs --app)
|
|
83
84
|
--versions List versions (needs --app)
|
|
85
|
+
--delete Delete (unpublish) the app: its URL stops working and its versions, files and data are removed. Needs --app.
|
|
86
|
+
Only for a PRIVATE app your team published; a public app is the owner's to delete (use --private first or ask the owner).
|
|
84
87
|
--transfer-to Hand the app to another agent (its session name; it must be on a team on this machine).
|
|
85
88
|
--owner Who owns the app's comments: channel:#<name>, team:<name>, agent:<name> or default (the publishing agent).
|
|
86
89
|
With a publish it is set after the publish; alone it needs --app. Only the app's owner agents may change it.
|
|
87
|
-
Allowed for the
|
|
90
|
+
Allowed for the orchestrator or the owner only; the transfer is recorded (who, when, from, to).
|
|
88
91
|
The new publisher publishes with --app <appId> --dir <its directory>; the old team loses access.
|
|
89
|
-
--list List apps published from this machine
|
|
92
|
+
--list List apps published from this machine, with the owning team
|
|
90
93
|
--help | -h Show this help
|
|
91
94
|
EOF_USAGE
|
|
92
95
|
}
|
|
@@ -104,7 +107,7 @@ call() {
|
|
|
104
107
|
printf '%s\n' "$out" | tail -n 1
|
|
105
108
|
}
|
|
106
109
|
|
|
107
|
-
DIR=""; FILE=""; NAME=""; APP=""; ENTRY=""; NOTE=""; NOTIFY=false; ROLLBACK=""; TRANSFER_TO=""; VERSIONS=false; LIST=false
|
|
110
|
+
DIR=""; FILE=""; NAME=""; APP=""; ENTRY=""; NOTE=""; NOTIFY=false; ROLLBACK=""; TRANSFER_TO=""; DELETE_APP=false; VERSIONS=false; LIST=false
|
|
108
111
|
SHARE=false; TTL_DAYS=""; LINKS=false; REVOKE_LINK=""; REVOKE_LINKS=false; OWNER_SPEC=""
|
|
109
112
|
CAPABILITY=""; CAPABILITY_NOTE=""; CANCEL_CAPABILITY=false
|
|
110
113
|
PUBLIC=false; PUBLIC_READ=""; PUBLIC_SUBMIT=""; PUBLIC_NOTE=""; CANCEL_PUBLIC=false; PRIVATE=false; REFRESH_THUMB=false
|
|
@@ -138,6 +141,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
138
141
|
--transfer-to) [ $# -ge 2 ] || error_exit "--transfer-to requires a session name"; TRANSFER_TO="$2"; shift 2 ;;
|
|
139
142
|
--owner) [ $# -ge 2 ] || error_exit "--owner requires channel:#<name>, team:<name>, agent:<name> or default"; OWNER_SPEC="$2"; shift 2 ;;
|
|
140
143
|
--list) LIST=true; shift ;;
|
|
144
|
+
--delete) DELETE_APP=true; shift ;;
|
|
141
145
|
--as-template) AS_TEMPLATE=true; shift ;;
|
|
142
146
|
--description) [ $# -ge 2 ] || error_exit "--description requires a value"; TPL_DESCRIPTION="$2"; shift 2 ;;
|
|
143
147
|
--category) [ $# -ge 2 ] || error_exit "--category requires a value"; TPL_CATEGORY="$2"; shift 2 ;;
|
|
@@ -200,7 +204,7 @@ fi
|
|
|
200
204
|
|
|
201
205
|
if $LIST; then
|
|
202
206
|
RESPONSE=$(call GET "/apps") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
203
|
-
printf '%s' "$RESPONSE" | jq -c '{success: true, apps: [.data[]? | select(.deleted != true) | {appId, name, url, agent: .agentSession, currentVersion, source}]}'
|
|
207
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, apps: [.data[]? | select(.deleted != true) | {appId, name, url, agent: .agentSession, team, currentVersion, source}]}'
|
|
204
208
|
exit 0
|
|
205
209
|
fi
|
|
206
210
|
|
|
@@ -300,6 +304,14 @@ if [ -n "$OWNER_SPEC" ] && [ -z "$DIR" ] && [ -z "$FILE" ]; then
|
|
|
300
304
|
exit 0
|
|
301
305
|
fi
|
|
302
306
|
|
|
307
|
+
if $DELETE_APP; then
|
|
308
|
+
[ -n "$APP" ] || error_exit "--app is required with --delete"
|
|
309
|
+
if [ -n "$DIR" ] || [ -n "$FILE" ] || [ -n "$TRANSFER_TO" ] || [ -n "$ROLLBACK" ]; then error_exit "--delete cannot be combined with a publish, --transfer-to or --rollback"; fi
|
|
310
|
+
RESPONSE=$(call DELETE "/apps/${APP}") || { printf '%s\n' "$RESPONSE"; exit 1; }
|
|
311
|
+
printf '%s' "$RESPONSE" | jq -c '{success: true, appId: .data.appId, deleted: .data.deleted}'
|
|
312
|
+
exit 0
|
|
313
|
+
fi
|
|
314
|
+
|
|
303
315
|
if [ -n "$TRANSFER_TO" ]; then
|
|
304
316
|
[ -n "$APP" ] || error_exit "--app is required with --transfer-to"
|
|
305
317
|
BODY=$(jq -cn --arg s "$TRANSFER_TO" '{toSession: $s}')
|
|
@@ -82,6 +82,10 @@ class H(BaseHTTPRequestHandler):
|
|
|
82
82
|
if data.get('owner') == 'channel:#nope':
|
|
83
83
|
return send(400, {'success': False, 'error': 'validation', 'message': 'No channel "#nope" on this machine.'})
|
|
84
84
|
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'owner': {'kind': 'channel', 'explicit': True, 'name': '#daily-brief'}, 'previous': None}})
|
|
85
|
+
if method == 'DELETE' and self.path == '/api/apps/28au74d9cj':
|
|
86
|
+
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'deleted': True}})
|
|
87
|
+
if method == 'DELETE' and self.path == '/api/apps/bcdfghjkmn':
|
|
88
|
+
return send(403, {'success': False, 'error': 'forbidden', 'message': 'This app is public, so only the owner can delete it.'})
|
|
85
89
|
if self.path == '/api/apps/28au74d9cj/transfer':
|
|
86
90
|
return send(200, {'success': True, 'data': {'appId': '28au74d9cj', 'name': 'G', 'publisher': data['toSession'], 'previous': 'dev-ella', 'changed': True, 'notified': [data['toSession']]}})
|
|
87
91
|
if self.path.endswith('/rollback'):
|
|
@@ -90,7 +94,7 @@ class H(BaseHTTPRequestHandler):
|
|
|
90
94
|
if self.path.endswith('/versions'):
|
|
91
95
|
return send(200, {'success': True, 'data': [{'version': 2, 'current': True, 'note': None, 'files': 1, 'totalBytes': 9, 'createdAt': 't', 'entry': 'index.html'}]})
|
|
92
96
|
if self.path == '/api/apps':
|
|
93
|
-
return send(200, {'success': True, 'data': [{'appId': 'a', 'name': 'A', 'url': 'u', 'agentSession': 's', 'currentVersion': 1, 'source': '/x'},
|
|
97
|
+
return send(200, {'success': True, 'data': [{'appId': 'a', 'name': 'A', 'url': 'u', 'agentSession': 's', 'team': 'Alpha', 'currentVersion': 1, 'source': '/x'},
|
|
94
98
|
{'appId': 'b', 'name': 'B', 'deleted': True}]})
|
|
95
99
|
send(404, {'success': False, 'error': 'not_found'})
|
|
96
100
|
def do_GET(self): self._reply('GET')
|
|
@@ -167,10 +171,17 @@ check "transfer: output" "$OUT" '{"success":true,"appId":"28au74d9cj","publisher
|
|
|
167
171
|
check "transfer: request" "$(jq -c '{path, body}' "$STUB_LOG")" '{"path":"/api/apps/28au74d9cj/transfer","body":{"toSession":"edu-game-milo-13e8d3ca"}}'
|
|
168
172
|
check "transfer needs --app" "$(run_err --transfer-to x | grep -c -- '--app is required with --transfer-to')" "1"
|
|
169
173
|
|
|
174
|
+
OUT=$(run --app 28au74d9cj --delete)
|
|
175
|
+
check "delete: output" "$OUT" '{"success":true,"appId":"28au74d9cj","deleted":true}'
|
|
176
|
+
check "delete: request" "$(jq -c '{method, path}' "$STUB_LOG")" '{"method":"DELETE","path":"/api/apps/28au74d9cj"}'
|
|
177
|
+
check "delete needs --app" "$(run_err --delete | grep -c -- '--app is required with --delete')" "1"
|
|
178
|
+
OUT=$(run --app bcdfghjkmn --delete || true)
|
|
179
|
+
check "delete: public app refused" "$(printf '%s' "$OUT" | jq -r '.success, .reason' | tr '\n' ' ')" "false forbidden "
|
|
180
|
+
|
|
170
181
|
OUT=$(run --app 28au74d9cj --versions)
|
|
171
182
|
check "versions" "$OUT" '{"success":true,"versions":[{"version":2,"current":true,"note":null,"files":1,"totalBytes":9,"createdAt":"t"}]}'
|
|
172
183
|
OUT=$(run --list)
|
|
173
|
-
check "list hides deleted" "$OUT" '{"success":true,"apps":[{"appId":"a","name":"A","url":"u","agent":"s","currentVersion":1,"source":"/x"}]}'
|
|
184
|
+
check "list hides deleted" "$OUT" '{"success":true,"apps":[{"appId":"a","name":"A","url":"u","agent":"s","team":"Alpha","currentVersion":1,"source":"/x"}]}'
|
|
174
185
|
|
|
175
186
|
OUT=$(run --dir "$APPDIR" --name nologin; true)
|
|
176
187
|
check "backend error mapped" "$OUT" '{"success":false,"status":409,"reason":"not_logged_in","message":"not signed in","hint":"crewly cloud login"}'
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Share File
|
|
3
|
+
description: Hand the OWNER a LARGE file you made (an HD video, an archive, up to 4 GB) as a private link they open on their phone, watch right in the page and download. Reverse of request-file. Use instead of Slack, Drive or asking the owner to come to the machine.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
category: productivity
|
|
6
|
+
skillType: claude-skill
|
|
7
|
+
assignableRoles:
|
|
8
|
+
- "*"
|
|
9
|
+
triggers:
|
|
10
|
+
- share file
|
|
11
|
+
- share the video
|
|
12
|
+
- send the owner a file
|
|
13
|
+
- put the video on a link
|
|
14
|
+
- the video is ready
|
|
15
|
+
- download link for the owner
|
|
16
|
+
- hand the file to the owner
|
|
17
|
+
tags:
|
|
18
|
+
- files
|
|
19
|
+
- owner
|
|
20
|
+
- video
|
|
21
|
+
execution:
|
|
22
|
+
type: script
|
|
23
|
+
script:
|
|
24
|
+
file: execute.sh
|
|
25
|
+
interpreter: bash
|
|
26
|
+
timeoutMs: 7200000
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
# Share File
|
|
30
|
+
|
|
31
|
+
Gives **the owner**, who is probably on a phone and away from the machine, a
|
|
32
|
+
private link to a big file you made. They open it, **watch or listen right in
|
|
33
|
+
the page** (video, images and audio play inline and can be scrubbed) and tap
|
|
34
|
+
**Download** to save it to the phone. Needs the machine to be signed in to
|
|
35
|
+
Crewly Cloud. The file is streamed from disk (never loaded into memory).
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
bash execute.sh --path out/final.mp4 --note "the HD cut, 1.2 GB"
|
|
39
|
+
bash execute.sh --status # your shared files and their links
|
|
40
|
+
bash execute.sh --status sf_AbCdEfGhIjKlMnOpQrStUv
|
|
41
|
+
bash execute.sh --cancel sf_AbCdEfGhIjKlMnOpQrStUv
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Prints, once the upload is finished (big files print `uploaded 10%` ... lines to stderr while it runs):
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{"success":true,"shareId":"sf_…","url":"https://crewlyai.com/portal/files/sf_…","fileName":"final.mp4",
|
|
48
|
+
"sizeBytes":1288490188,"expiresAt":"…","instruction":"Post the url to the owner…"}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## What to do with it
|
|
52
|
+
|
|
53
|
+
1. **Post the `url` to the owner** where you are talking to them (the Slack
|
|
54
|
+
thread, the chat), in your own words: what the file is, that they can open it
|
|
55
|
+
on their phone, watch it in the page and download it, and that it stays for
|
|
56
|
+
7 days. One short message. Do not paste this skill's JSON.
|
|
57
|
+
2. The owner signs in to their Crewly account if asked. Nobody else can open
|
|
58
|
+
the link. If they say it expired, run the skill again for a fresh link.
|
|
59
|
+
3. `--cancel` takes a file down you no longer want shared. A mistake (wrong
|
|
60
|
+
file) is a good reason to cancel straight away.
|
|
61
|
+
|
|
62
|
+
## Rules
|
|
63
|
+
|
|
64
|
+
- `--path` must be a regular file **inside your project directory**: not a
|
|
65
|
+
symlink, not inside Crewly's home. Up to 4 GB. Copy files from elsewhere
|
|
66
|
+
into the project first.
|
|
67
|
+
- `--name` is the name the owner sees and saves it under (default: the file's
|
|
68
|
+
name). `--note` is one line for the owner (up to 500 characters).
|
|
69
|
+
- The account may hold 20 GB / 20 shared files at once; cancel old ones or wait
|
|
70
|
+
for them to expire (7 days after upload).
|
|
71
|
+
- Use for the finished thing the owner wants to see or keep. Not for secrets
|
|
72
|
+
or credentials, and not for files under ~5 MB that fit in a message. To get
|
|
73
|
+
a file FROM the owner, use `request-file`.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# share-file — put a LARGE file you made (an HD video, an archive) on a private
|
|
4
|
+
# link for the OWNER, who watches it in the page and downloads it on the phone.
|
|
5
|
+
# The reverse of request-file (crewly-services apps/SPEC.md section 20).
|
|
6
|
+
#
|
|
7
|
+
# The backend (POST /api/apps/shares) asks Cloud for a presigned upload URL;
|
|
8
|
+
# this script then streams the file straight to object storage with curl -T
|
|
9
|
+
# (from disk, never buffered), and confirms with /complete.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# bash execute.sh --path <file> [--name <name>] [--note <text>] [--content-type <type>]
|
|
13
|
+
# bash execute.sh --status [<shareId>]
|
|
14
|
+
# bash execute.sh --cancel <shareId>
|
|
15
|
+
# =============================================================================
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
source "${SCRIPT_DIR}/../../_common/lib.sh"
|
|
19
|
+
|
|
20
|
+
# Progress lines (one per 10%) are printed for uploads at least this big.
|
|
21
|
+
PROGRESS_MIN_BYTES="${SHARE_FILE_PROGRESS_MIN_BYTES:-52428800}"
|
|
22
|
+
|
|
23
|
+
print_usage() {
|
|
24
|
+
cat <<'EOF_USAGE'
|
|
25
|
+
Usage:
|
|
26
|
+
bash execute.sh --path <file> [--name <name>] [--note <text>] [--content-type <type>]
|
|
27
|
+
bash execute.sh --status [<shareId>]
|
|
28
|
+
bash execute.sh --cancel <shareId>
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--path Regular file inside your project directory, up to 4 GB (required)
|
|
32
|
+
--name Name the owner sees and saves it under (default: the file's name)
|
|
33
|
+
--note One line for the owner (what it is, e.g. "the HD cut, 1.2 GB")
|
|
34
|
+
--content-type MIME type (default: from the extension)
|
|
35
|
+
--status List your shared files (with their links), or show one
|
|
36
|
+
--cancel Take a shared file down (deletes the uploaded file)
|
|
37
|
+
--help | -h Show this help
|
|
38
|
+
EOF_USAGE
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fail_from() {
|
|
42
|
+
printf '%s' "$1" | jq -c '{success: false, status: (.status // 0), reason: (.details.error // .error // "unknown"), message: (.details.message // .details // "")}' 2>/dev/null \
|
|
43
|
+
|| jq -cn --arg r "$1" '{success: false, reason: $r}'
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
call() {
|
|
48
|
+
local out
|
|
49
|
+
out=$(api_call "$@" 2>&1) || { fail_from "$(printf '%s\n' "$out" | tail -n 1)"; }
|
|
50
|
+
printf '%s\n' "$out" | tail -n 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
content_type_for() {
|
|
54
|
+
case "$(printf '%s' "${1##*.}" | tr '[:upper:]' '[:lower:]')" in
|
|
55
|
+
mp4|m4v) echo video/mp4 ;;
|
|
56
|
+
mov) echo video/quicktime ;;
|
|
57
|
+
webm) echo video/webm ;;
|
|
58
|
+
mkv) echo video/x-matroska ;;
|
|
59
|
+
mp3) echo audio/mpeg ;;
|
|
60
|
+
wav) echo audio/wav ;;
|
|
61
|
+
m4a) echo audio/mp4 ;;
|
|
62
|
+
png) echo image/png ;;
|
|
63
|
+
jpg|jpeg) echo image/jpeg ;;
|
|
64
|
+
gif) echo image/gif ;;
|
|
65
|
+
webp) echo image/webp ;;
|
|
66
|
+
pdf) echo application/pdf ;;
|
|
67
|
+
zip) echo application/zip ;;
|
|
68
|
+
json) echo application/json ;;
|
|
69
|
+
csv) echo text/csv ;;
|
|
70
|
+
txt|md|log) echo text/plain ;;
|
|
71
|
+
*) echo application/octet-stream ;;
|
|
72
|
+
esac
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Regular, non-symlink file inside the project directory, never under Crewly's
|
|
76
|
+
# home. Prints the real path, then the size in bytes.
|
|
77
|
+
check_file() {
|
|
78
|
+
command -v node >/dev/null 2>&1 || error_exit "node is required"
|
|
79
|
+
CF_PATH="$1" CF_PROJECT="${CREWLY_PROJECT_PATH:-$PWD}" node <<'NODE'
|
|
80
|
+
const fs = require('fs'); const path = require('path'); const os = require('os');
|
|
81
|
+
const fail = (m) => { process.stderr.write(JSON.stringify({ error: m }) + '\n'); process.exit(1); };
|
|
82
|
+
const real = (p) => { try { return fs.realpathSync(p); } catch { return null; } };
|
|
83
|
+
const within = (c, p) => c === p || c.startsWith(p.endsWith(path.sep) ? p : p + path.sep);
|
|
84
|
+
const given = process.env.CF_PATH;
|
|
85
|
+
const abs = path.resolve(given);
|
|
86
|
+
let st;
|
|
87
|
+
try { st = fs.lstatSync(abs); } catch { fail(`file not found: ${given}`); }
|
|
88
|
+
if (st.isSymbolicLink()) fail(`${given} is a symbolic link; pass the real file`);
|
|
89
|
+
if (!st.isFile()) fail(`${given} is not a regular file`);
|
|
90
|
+
if (st.size === 0) fail(`${given} is empty`);
|
|
91
|
+
const r = real(abs); const project = real(process.env.CF_PROJECT || process.cwd());
|
|
92
|
+
if (!project) fail('cannot resolve the project directory');
|
|
93
|
+
const homes = [path.join(os.homedir(), '.crewly'), process.env.CREWLY_HOME].filter(Boolean).map((p) => real(p) || path.resolve(p));
|
|
94
|
+
if (homes.some((h) => within(r, h))) fail(`${given} is inside Crewly's home directory`);
|
|
95
|
+
if (!within(r, project)) fail(`${given} is outside your project directory (${project}); copy it there first`);
|
|
96
|
+
process.stdout.write(r + '\n' + st.size + '\n');
|
|
97
|
+
NODE
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# Stream the file to the presigned URL (curl -T reads from disk). Prints the HTTP
|
|
101
|
+
# status. Big files report progress (one stderr line per 10%) so the run is not silent.
|
|
102
|
+
upload() {
|
|
103
|
+
local file="$1" size="$2" url="$3"; shift 3
|
|
104
|
+
if [ "$size" -ge "$PROGRESS_MIN_BYTES" ]; then
|
|
105
|
+
echo "uploading $(( size / 1048576 )) MB..." >&2
|
|
106
|
+
curl --progress-bar -S -o /dev/null -w '%{http_code}' --connect-timeout 30 --retry 3 --retry-delay 3 -X PUT -T "$file" "$@" "$url" \
|
|
107
|
+
2> >(tr '\r' '\n' | awk 'BEGIN{last=0} match($0,/[0-9]+\.[0-9]%/){p=int(substr($0,RSTART)+0); if(p>=last+10){last=p-(p%10); printf "uploaded %d%%\n", last > "/dev/stderr"; fflush("/dev/stderr")}}') \
|
|
108
|
+
|| printf '000'
|
|
109
|
+
else
|
|
110
|
+
curl -sS -o /dev/null -w '%{http_code}' --connect-timeout 30 --retry 3 --retry-delay 3 -X PUT -T "$file" "$@" "$url" 2>/dev/null || printf '000'
|
|
111
|
+
fi
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
FILE=""; NAME=""; NOTE=""; CTYPE=""; CANCEL=""; STATUS=0; STATUS_ID=""
|
|
115
|
+
while [[ $# -gt 0 ]]; do
|
|
116
|
+
case "$1" in
|
|
117
|
+
--path) [ $# -ge 2 ] || error_exit "--path requires a value"; FILE="$2"; shift 2 ;;
|
|
118
|
+
--name) [ $# -ge 2 ] || error_exit "--name requires a value"; NAME="$2"; shift 2 ;;
|
|
119
|
+
--note) [ $# -ge 2 ] || error_exit "--note requires a value"; NOTE="$2"; shift 2 ;;
|
|
120
|
+
--content-type) [ $# -ge 2 ] || error_exit "--content-type requires a value"; CTYPE="$2"; shift 2 ;;
|
|
121
|
+
--cancel) [ $# -ge 2 ] || error_exit "--cancel requires a share id"; CANCEL="$2"; shift 2 ;;
|
|
122
|
+
--status) STATUS=1; shift
|
|
123
|
+
if [ $# -ge 1 ] && [[ "$1" =~ ^sf_ ]]; then STATUS_ID="$1"; shift; fi ;;
|
|
124
|
+
--help|-h) print_usage; exit 0 ;;
|
|
125
|
+
*) unknown_arg_exit "$1" ;;
|
|
126
|
+
esac
|
|
127
|
+
done
|
|
128
|
+
|
|
129
|
+
ID_RE='^sf_[A-Za-z0-9_-]{22}$'
|
|
130
|
+
|
|
131
|
+
if [ -n "$CANCEL" ]; then
|
|
132
|
+
[[ "$CANCEL" =~ $ID_RE ]] || error_exit "--cancel needs a share id like sf_…"
|
|
133
|
+
RESULT=$(call DELETE "/apps/shares/${CANCEL}") || { printf '%s\n' "$RESULT"; exit 1; }
|
|
134
|
+
jq -cn --arg id "$CANCEL" '{success: true, shareId: $id, cancelled: true}'
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
if [ "$STATUS" = "1" ]; then
|
|
139
|
+
if [ -n "$STATUS_ID" ]; then
|
|
140
|
+
[[ "$STATUS_ID" =~ $ID_RE ]] || error_exit "--status needs a share id like sf_…"
|
|
141
|
+
RESULT=$(call GET "/apps/shares/${STATUS_ID}") || { printf '%s\n' "$RESULT"; exit 1; }
|
|
142
|
+
printf '%s' "$RESULT" | jq -c '{success: true} + (.data | {shareId, status, goneReason, fileName, sizeBytes, url, expiresAt})'
|
|
143
|
+
else
|
|
144
|
+
RESULT=$(call GET "/apps/shares") || { printf '%s\n' "$RESULT"; exit 1; }
|
|
145
|
+
printf '%s' "$RESULT" | jq -c '{success: true, shares: [.data.shares[]? | {shareId, status, fileName, sizeBytes, url, expiresAt}]}'
|
|
146
|
+
fi
|
|
147
|
+
exit 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
[ -n "$FILE" ] || error_exit "--path is required"
|
|
151
|
+
INFO=$(check_file "$FILE") || { printf '%s\n' "$INFO" >&2; exit 1; }
|
|
152
|
+
REAL=$(printf '%s\n' "$INFO" | sed -n 1p)
|
|
153
|
+
SIZE=$(printf '%s\n' "$INFO" | sed -n 2p)
|
|
154
|
+
[ -n "$NAME" ] || NAME="$(basename "$REAL")"
|
|
155
|
+
[ -n "$CTYPE" ] || CTYPE="$(content_type_for "$REAL")"
|
|
156
|
+
|
|
157
|
+
BODY=$(jq -cn --arg n "$NAME" --argjson s "$SIZE" --arg c "$CTYPE" --arg note "$NOTE" \
|
|
158
|
+
'{fileName: $n, sizeBytes: $s, contentType: $c} + (if $note != "" then {note: $note} else {} end)')
|
|
159
|
+
CREATED=$(call POST "/apps/shares" "$BODY") || { printf '%s\n' "$CREATED"; exit 1; }
|
|
160
|
+
ID=$(printf '%s' "$CREATED" | jq -r '.data.shareId // empty')
|
|
161
|
+
URL=$(printf '%s' "$CREATED" | jq -r '.data.upload.url // empty')
|
|
162
|
+
LINK=$(printf '%s' "$CREATED" | jq -r '.data.url // empty')
|
|
163
|
+
[ -n "$ID" ] && [ -n "$URL" ] || error_exit "Cloud answered without an upload URL"
|
|
164
|
+
|
|
165
|
+
# Headers the presigned URL was signed with. The signed URL itself is never printed or logged.
|
|
166
|
+
HDRS=()
|
|
167
|
+
while IFS= read -r line; do HDRS+=(-H "$line"); done < <(printf '%s' "$CREATED" | jq -r '.data.upload.headers // {} | to_entries[] | "\(.key): \(.value)"')
|
|
168
|
+
|
|
169
|
+
cancel_quietly() { api_call DELETE "/apps/shares/${ID}" >/dev/null 2>&1 || true; }
|
|
170
|
+
|
|
171
|
+
CODE=$(upload "$REAL" "$SIZE" "$URL" ${HDRS[@]+"${HDRS[@]}"})
|
|
172
|
+
if [ "$CODE" != "200" ]; then
|
|
173
|
+
cancel_quietly
|
|
174
|
+
jq -cn --arg c "$CODE" '{success: false, reason: "upload_failed", message: ("The upload to temporary storage failed (HTTP " + $c + "). Nothing was kept; try again.")}'
|
|
175
|
+
exit 1
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
DONE=$(api_call POST "/apps/shares/${ID}/complete" 2>&1) || { cancel_quietly; fail_from "$(printf '%s\n' "$DONE" | tail -n 1)"; }
|
|
179
|
+
EXPIRES=$(printf '%s\n' "$DONE" | tail -n 1 | jq -r '.data.expiresAt // empty')
|
|
180
|
+
[ -n "$LINK" ] || LINK=$(printf '%s\n' "$DONE" | tail -n 1 | jq -r '.data.url // empty')
|
|
181
|
+
|
|
182
|
+
jq -cn --arg id "$ID" --arg link "$LINK" --arg n "$NAME" --argjson s "$SIZE" --arg e "$EXPIRES" \
|
|
183
|
+
'{success: true, shareId: $id, url: $link, fileName: $n, sizeBytes: $s, expiresAt: $e,
|
|
184
|
+
instruction: "Post the url to the owner where you are talking to them, in your own words: what the file is, that they can open it on their phone, watch it right in the page and download it, and that it stays available for 7 days. One short message; do not paste this JSON. The link is private to their Crewly account. Withdraw it with: bash <skills>/core/share-file/execute.sh --cancel \($id)"}'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Tests for share-file — run with: bash execute.test.sh
|
|
3
|
+
# A Python stub plays both the Crewly backend and the object storage.
|
|
4
|
+
set -u
|
|
5
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
6
|
+
EXEC="$HERE/execute.sh"
|
|
7
|
+
PASS=0; FAIL=0
|
|
8
|
+
check() { if [ "$2" = "$3" ]; then PASS=$((PASS+1)); else FAIL=$((FAIL+1)); echo "FAIL: $1"; echo " want: $3"; echo " got: $2"; fi; }
|
|
9
|
+
|
|
10
|
+
PORT=18862
|
|
11
|
+
STUB_LOG="$(mktemp)"; STORE="$(mktemp)"; export STUB_LOG STORE STUB_PORT="$PORT"
|
|
12
|
+
python3 -u <<'PY' >/dev/null 2>&1 &
|
|
13
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
14
|
+
import json, os
|
|
15
|
+
LOG = os.environ['STUB_LOG']; STORE = os.environ['STORE']; PORT = int(os.environ['STUB_PORT'])
|
|
16
|
+
ID = 'sf_AbCdEfGhIjKlMnOpQrStUv'
|
|
17
|
+
LINK = 'https://crewlyai.com/portal/files/' + ID
|
|
18
|
+
def note(**kw):
|
|
19
|
+
with open(LOG, 'a') as f: f.write(json.dumps(kw) + '\n')
|
|
20
|
+
class H(BaseHTTPRequestHandler):
|
|
21
|
+
def send(self, code, obj):
|
|
22
|
+
self.send_response(code); self.send_header('Content-Type', 'application/json'); self.end_headers(); self.wfile.write(json.dumps(obj).encode())
|
|
23
|
+
def body(self):
|
|
24
|
+
n = int(self.headers.get('Content-Length') or 0); return self.rfile.read(n)
|
|
25
|
+
def do_POST(self):
|
|
26
|
+
b = self.body()
|
|
27
|
+
if self.path == '/api/apps/shares':
|
|
28
|
+
req = json.loads(b); note(op='create', req=req, session=self.headers.get('X-Agent-Session'))
|
|
29
|
+
if req['fileName'] == 'refuse.bin':
|
|
30
|
+
return self.send(413, {'success': False, 'error': 'too_large', 'message': 'A shared file can be at most 4096 MB.'})
|
|
31
|
+
return self.send(201, {'success': True, 'data': {'shareId': ID, 'status': 'pending', 'url': LINK, 'upload': {'url': f'http://127.0.0.1:{PORT}/store/obj?X-Amz-Signature=SECRETSIG', 'method': 'PUT', 'headers': {'Content-Type': req['contentType'], 'x-amz-server-side-encryption': 'AES256'}}}})
|
|
32
|
+
if self.path == f'/api/apps/shares/{ID}/complete':
|
|
33
|
+
note(op='complete'); return self.send(200, {'success': True, 'data': {'shareId': ID, 'status': 'ready', 'url': LINK, 'expiresAt': '2026-10-20T00:00:00.000Z'}})
|
|
34
|
+
self.send(404, {'success': False})
|
|
35
|
+
def do_GET(self):
|
|
36
|
+
note(op='get', path=self.path)
|
|
37
|
+
if self.path == f'/api/apps/shares/{ID}':
|
|
38
|
+
return self.send(200, {'success': True, 'data': {'shareId': ID, 'status': 'ready', 'fileName': 'daily.mp4', 'sizeBytes': 3000000, 'url': LINK, 'expiresAt': '2026-10-20T00:00:00.000Z', 'download': {'url': 'https://secret.example/x'}}})
|
|
39
|
+
if self.path == '/api/apps/shares':
|
|
40
|
+
return self.send(200, {'success': True, 'data': {'shares': [{'shareId': ID, 'status': 'ready', 'fileName': 'daily.mp4', 'sizeBytes': 3000000, 'url': LINK, 'expiresAt': '2026-10-20T00:00:00.000Z', 'from': {'id': 'dev-ella'}}]}})
|
|
41
|
+
self.send(404, {'success': False})
|
|
42
|
+
def do_DELETE(self):
|
|
43
|
+
note(op='cancel', path=self.path); self.send(200, {'success': True, 'data': {'deleted': True}})
|
|
44
|
+
def do_PUT(self):
|
|
45
|
+
b = self.body()
|
|
46
|
+
note(op='put', bytes=len(b), ctype=self.headers.get('Content-Type'), sse=self.headers.get('x-amz-server-side-encryption'), query=self.path.split('?')[-1])
|
|
47
|
+
self.send_response(500 if os.path.exists(STORE + '.fail') else 200); self.end_headers()
|
|
48
|
+
def log_message(self, *a, **k): pass
|
|
49
|
+
HTTPServer(('127.0.0.1', PORT), H).serve_forever()
|
|
50
|
+
PY
|
|
51
|
+
STUB_PID=$!
|
|
52
|
+
disown "$STUB_PID" 2>/dev/null || true
|
|
53
|
+
WORK="$(mktemp -d)"
|
|
54
|
+
trap 'kill "$STUB_PID" >/dev/null 2>&1; rm -rf "$STUB_LOG" "$STORE" "$STORE.fail" "$WORK"' EXIT
|
|
55
|
+
for i in $(seq 1 30); do curl -s -o /dev/null -m 0.2 "http://127.0.0.1:${PORT}/" 2>/dev/null && break; sleep 0.1; done
|
|
56
|
+
mkdir -p "$WORK/home/.crewly" "$WORK/proj/out"
|
|
57
|
+
ENVS=(CREWLY_API_URL="http://127.0.0.1:${PORT}" CREWLY_SESSION_NAME=dev-ella HOME="$WORK/home" CREWLY_HOME="$WORK/home/.crewly" CREWLY_PROJECT_PATH="$WORK/proj")
|
|
58
|
+
run() { env "${ENVS[@]}" bash "$EXEC" "$@" 2>/dev/null; }
|
|
59
|
+
run_err() { env "${ENVS[@]}" bash "$EXEC" "$@" 2>&1 >/dev/null; }
|
|
60
|
+
|
|
61
|
+
: > "$STUB_LOG" # drop the readiness probe
|
|
62
|
+
head -c 3000000 /dev/zero > "$WORK/proj/out/daily.mp4"
|
|
63
|
+
OUT=$(run --path "$WORK/proj/out/daily.mp4" --note "the HD cut")
|
|
64
|
+
check "result" "$(printf '%s' "$OUT" | jq -c '{success, shareId, url, fileName, sizeBytes, expiresAt}')" '{"success":true,"shareId":"sf_AbCdEfGhIjKlMnOpQrStUv","url":"https://crewlyai.com/portal/files/sf_AbCdEfGhIjKlMnOpQrStUv","fileName":"daily.mp4","sizeBytes":3000000,"expiresAt":"2026-10-20T00:00:00.000Z"}'
|
|
65
|
+
check "instruction tells the agent to post the url in its own words" "$(printf '%s' "$OUT" | jq -r '.instruction' | grep -c 'in your own words')" "1"
|
|
66
|
+
check "instruction names cancel" "$(printf '%s' "$OUT" | jq -r '.instruction' | grep -c 'share-file/execute.sh --cancel sf_AbCdEfGhIjKlMnOpQrStUv')" "1"
|
|
67
|
+
check "output never carries the signed url" "$(printf '%s' "$OUT" | grep -c 'SECRETSIG')" "0"
|
|
68
|
+
check "create request" "$(sed -n 1p "$STUB_LOG" | jq -c '{req, session}')" '{"req":{"fileName":"daily.mp4","sizeBytes":3000000,"contentType":"video/mp4","note":"the HD cut"},"session":"dev-ella"}'
|
|
69
|
+
check "upload streamed with signed headers" "$(sed -n 2p "$STUB_LOG" | jq -c '{bytes, ctype, sse}')" '{"bytes":3000000,"ctype":"video/mp4","sse":"AES256"}'
|
|
70
|
+
check "complete called" "$(sed -n 3p "$STUB_LOG" | jq -r .op)" "complete"
|
|
71
|
+
|
|
72
|
+
# --name overrides the file name; --content-type overrides the guess
|
|
73
|
+
: > "$STUB_LOG"
|
|
74
|
+
run --path "$WORK/proj/out/daily.mp4" --name "launch.mov" --content-type video/quicktime >/dev/null
|
|
75
|
+
check "name and content type" "$(sed -n 1p "$STUB_LOG" | jq -c '.req | {fileName, contentType}')" '{"fileName":"launch.mov","contentType":"video/quicktime"}'
|
|
76
|
+
|
|
77
|
+
# Big files report progress on stderr (threshold lowered for the test), and never leak the signed url
|
|
78
|
+
: > "$STUB_LOG"
|
|
79
|
+
ERR=$(env "${ENVS[@]}" SHARE_FILE_PROGRESS_MIN_BYTES=1 bash "$EXEC" --path "$WORK/proj/out/daily.mp4" 2>&1 >/dev/null)
|
|
80
|
+
check "progress announced" "$(printf '%s' "$ERR" | grep -c 'uploading 2 MB')" "1"
|
|
81
|
+
check "progress reaches 100%" "$(printf '%s' "$ERR" | grep -c 'uploaded 100%')" "1"
|
|
82
|
+
check "progress never prints the signed url" "$(printf '%s' "$ERR" | grep -c 'SECRETSIG')" "0"
|
|
83
|
+
check "progress run still uploads the whole file" "$(grep '"op": "put"' "$STUB_LOG" | jq -r .bytes)" "3000000"
|
|
84
|
+
|
|
85
|
+
: > "$STUB_LOG"
|
|
86
|
+
OUT=$(run --path "$WORK/proj/out/daily.mp4" --name "refuse.bin"; true)
|
|
87
|
+
check "cloud refusal passed through" "$(printf '%s' "$OUT" | jq -c '{success, reason}')" '{"success":false,"reason":"too_large"}'
|
|
88
|
+
|
|
89
|
+
: > "$STUB_LOG"; touch "$STORE.fail"
|
|
90
|
+
OUT=$(run --path "$WORK/proj/out/daily.mp4"; true)
|
|
91
|
+
check "failed upload reports and cancels" "$(printf '%s' "$OUT" | jq -r .reason)" "upload_failed"
|
|
92
|
+
check "cancel issued" "$(grep -c '"op": "cancel"' "$STUB_LOG")" "1"
|
|
93
|
+
rm -f "$STORE.fail"
|
|
94
|
+
|
|
95
|
+
# Path safety (same as send-file)
|
|
96
|
+
echo secret > "$WORK/outside.txt"; ln -s "$WORK/proj/out/daily.mp4" "$WORK/proj/link.mp4"
|
|
97
|
+
echo hidden > "$WORK/home/.crewly/token.txt"
|
|
98
|
+
mkdir -p "$WORK/proj/dir"; : > "$WORK/proj/empty.mp4"
|
|
99
|
+
check "outside project refused" "$(run_err --path "$WORK/outside.txt" | grep -c 'outside your project directory')" "1"
|
|
100
|
+
check "symlink refused" "$(run_err --path "$WORK/proj/link.mp4" | grep -c 'symbolic link')" "1"
|
|
101
|
+
check "directory refused" "$(run_err --path "$WORK/proj/dir" | grep -c 'not a regular file')" "1"
|
|
102
|
+
check "empty file refused" "$(run_err --path "$WORK/proj/empty.mp4" | grep -c 'is empty')" "1"
|
|
103
|
+
check "missing file refused" "$(run_err --path "$WORK/proj/nope" | grep -c 'file not found')" "1"
|
|
104
|
+
check "path required" "$(run_err | grep -c -- '--path is required')" "1"
|
|
105
|
+
: > "$STUB_LOG"
|
|
106
|
+
env "${ENVS[@]}" CREWLY_PROJECT_PATH="$WORK" bash "$EXEC" --path "$WORK/home/.crewly/token.txt" >/dev/null 2>&1
|
|
107
|
+
check "Crewly home refused even when it is inside the project" "$(grep -c '"op": "create"' "$STUB_LOG")" "0"
|
|
108
|
+
|
|
109
|
+
# --status and --cancel
|
|
110
|
+
: > "$STUB_LOG"
|
|
111
|
+
OUT=$(run --status)
|
|
112
|
+
check "status lists shares with links" "$(printf '%s' "$OUT" | jq -c '{success, shares}')" '{"success":true,"shares":[{"shareId":"sf_AbCdEfGhIjKlMnOpQrStUv","status":"ready","fileName":"daily.mp4","sizeBytes":3000000,"url":"https://crewlyai.com/portal/files/sf_AbCdEfGhIjKlMnOpQrStUv","expiresAt":"2026-10-20T00:00:00.000Z"}]}'
|
|
113
|
+
OUT=$(run --status sf_AbCdEfGhIjKlMnOpQrStUv)
|
|
114
|
+
check "status of one share drops signed urls" "$(printf '%s' "$OUT" | jq -c '{success, shareId, status, fileName}')" '{"success":true,"shareId":"sf_AbCdEfGhIjKlMnOpQrStUv","status":"ready","fileName":"daily.mp4"}'
|
|
115
|
+
check "status of one share never prints a download url" "$(printf '%s' "$OUT" | grep -c 'secret.example')" "0"
|
|
116
|
+
: > "$STUB_LOG"
|
|
117
|
+
run --cancel sf_AbCdEfGhIjKlMnOpQrStUv >/dev/null
|
|
118
|
+
check "cancel" "$(jq -r .path "$STUB_LOG")" "/api/apps/shares/sf_AbCdEfGhIjKlMnOpQrStUv"
|
|
119
|
+
check "cancel needs a share id" "$(run_err --cancel hf_AbCdEfGhIjKlMnOpQrStUv | grep -c 'share id like sf_')" "1"
|
|
120
|
+
|
|
121
|
+
echo "share-file: $PASS passed, $FAIL failed"
|
|
122
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -38,6 +38,8 @@ export declare const requestCapabilities: (req: Request, res: Response) => Promi
|
|
|
38
38
|
export declare const cancelCapabilityRequest: (req: Request, res: Response) => Promise<void>;
|
|
39
39
|
/** POST /api/apps/:appId/make-private */
|
|
40
40
|
export declare const makePrivate: (req: Request, res: Response) => Promise<void>;
|
|
41
|
+
/** DELETE /api/apps/:appId — delete (unpublish) an app: the owner, or an agent whose team published it and it is private */
|
|
42
|
+
export declare const deleteApp: (req: Request, res: Response) => Promise<void>;
|
|
41
43
|
/** POST /api/apps/:appId/transfer `{ toSession }` — hand the app to another agent / team */
|
|
42
44
|
export declare const transferApp: (req: Request, res: Response) => Promise<void>;
|
|
43
45
|
/** GET /api/apps/:appId/owner — who the app's comments go to (an agent, a team or a channel) */
|
|
@@ -146,4 +148,14 @@ export declare const createUploadRequest: (req: Request, res: Response) => Promi
|
|
|
146
148
|
export declare const getUploadRequest: (req: Request, res: Response) => Promise<void>;
|
|
147
149
|
/** DELETE /api/apps/upload-requests/:requestId — cancel the link */
|
|
148
150
|
export declare const cancelUploadRequest: (req: Request, res: Response) => Promise<void>;
|
|
151
|
+
/** POST /api/apps/shares `{ fileName, sizeBytes, contentType?, note? }` — reserve a share; answers the owner's link and a presigned upload URL */
|
|
152
|
+
export declare const createShare: (req: Request, res: Response) => Promise<void>;
|
|
153
|
+
/** POST /api/apps/shares/:shareId/complete — the upload finished; Cloud checks the size and starts the 7 days */
|
|
154
|
+
export declare const completeShare: (req: Request, res: Response) => Promise<void>;
|
|
155
|
+
/** GET /api/apps/shares/:shareId — metadata (signed URLs only for the owner) */
|
|
156
|
+
export declare const getShare: (req: Request, res: Response) => Promise<void>;
|
|
157
|
+
/** GET /api/apps/shares — the account's live shares, with their links */
|
|
158
|
+
export declare const listShares: (req: Request, res: Response) => Promise<void>;
|
|
159
|
+
/** DELETE /api/apps/shares/:shareId — withdraw; Cloud deletes the file */
|
|
160
|
+
export declare const cancelShare: (req: Request, res: Response) => Promise<void>;
|
|
149
161
|
//# sourceMappingURL=apps.controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apps.controller.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/apps/apps.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAuBjD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAgB/D;AA0BD,6BAA6B;AAC7B,eAAO,MAAM,UAAU,QAjBF,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAgCjC,CAAC;AAE1B,wFAAwF;AACxF,eAAO,MAAM,QAAQ,QAnCA,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAuCjC,CAAC;AAE1B,6DAA6D;AAC7D,eAAO,MAAM,SAAS,QA1CD,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CA0CqE,CAAC;AAEhI,4CAA4C;AAC5C,eAAO,MAAM,UAAU,QA7CF,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CA6CuE,CAAC;AAElI,iDAAiD;AACjD,eAAO,MAAM,WAAW,QAhDH,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAgDsD,CAAC;AAEjH,uHAAuH;AACvH,eAAO,MAAM,aAAa,QAnDL,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAwDjC,CAAC;AAE1B,iDAAiD;AACjD,eAAO,MAAM,mBAAmB,QA3DX,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CA2DsE,CAAC;AAEjI,wHAAwH;AACxH,eAAO,MAAM,mBAAmB,QA9DX,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAmExD,CAAC;AAEH,iDAAiD;AACjD,eAAO,MAAM,uBAAuB,QAtEf,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAsE8E,CAAC;AAEzI,yCAAyC;AACzC,eAAO,MAAM,WAAW,QAzEH,OAAO,OAAO,QAAQ,KAAG,OAAO,CAAC,IAAI,CAyEsD,CAAC;AAEjH,4FAA4F;AAC5F,eAAO,MAAM,WAAW,
|
|
1
|
+
{"version":3,"file":"apps.controller.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/controllers/apps/apps.controller.ts"],"names":[],"mappings":"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"}
|