crewly 1.5.13 → 1.5.15

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.
@@ -21,8 +21,10 @@ CREWLY_API_URL="${CREWLY_API_URL:-http://localhost:8787}"
21
21
  # and replaces the script's positional parameters with the file contents.
22
22
  # All downstream parsing (read_json_input, custom arg loops) works unchanged.
23
23
  #
24
- # Usage (by LLM CLI):
25
- # printf '%s' '{"summary":"text with 'quotes'"}' > /tmp/crewly_input.json
24
+ # Usage (by LLM CLI — use heredoc to avoid single-quote EOF issues):
25
+ # cat > /tmp/crewly_input.json << 'CREWLY_EOF'
26
+ # {"summary":"text with 'quotes' and special chars"}
27
+ # CREWLY_EOF
26
28
  # bash execute.sh --file /tmp/crewly_input.json
27
29
  #
28
30
  # This runs at source-time, so every script that sources lib.sh gets it for free.
@@ -156,6 +156,34 @@ echo '{}' > "$TEMP_DIR/empty.json"
156
156
  RESULT=$(bash "$TEMP_DIR/test_read_json.sh" --file "$TEMP_DIR/empty.json")
157
157
  assert_eq "--file with empty JSON" '{}' "$RESULT"
158
158
 
159
+ # ---- Test 14: --file with heredoc-created file (single quotes in JSON) ----
160
+ # This simulates the exact pattern Gemini CLI agents now use:
161
+ # cat > /tmp/file << 'CREWLY_EOF'
162
+ # {"summary":"it's working — don't worry"}
163
+ # CREWLY_EOF
164
+ cat > "$TEMP_DIR/heredoc_single_quotes.json" << 'CREWLY_EOF'
165
+ {"summary":"it's working — don't worry about 'edge cases'"}
166
+ CREWLY_EOF
167
+ RESULT=$(bash "$TEMP_DIR/test_skill.sh" --file "$TEMP_DIR/heredoc_single_quotes.json")
168
+ assert_contains "heredoc with single quotes" "it's working" "$RESULT"
169
+ assert_contains "heredoc with multiple single quotes" "edge cases" "$RESULT"
170
+
171
+ # ---- Test 15: --file with heredoc-created file (backticks and $vars) ----
172
+ cat > "$TEMP_DIR/heredoc_special.json" << 'CREWLY_EOF'
173
+ {"text":"Use `jq` to parse $HOME and $(whoami) safely"}
174
+ CREWLY_EOF
175
+ RESULT=$(bash "$TEMP_DIR/test_skill.sh" --file "$TEMP_DIR/heredoc_special.json")
176
+ assert_contains "heredoc preserves backticks" '`jq`' "$RESULT"
177
+ assert_contains "heredoc preserves \$HOME" '$HOME' "$RESULT"
178
+ assert_contains "heredoc preserves \$()" '$(whoami)' "$RESULT"
179
+
180
+ # ---- Test 16: --file with heredoc-created file (mixed quotes) ----
181
+ cat > "$TEMP_DIR/heredoc_mixed.json" << 'CREWLY_EOF'
182
+ {"text":"He said \"it's fine\" and she said 'OK'"}
183
+ CREWLY_EOF
184
+ RESULT=$(bash "$TEMP_DIR/test_skill.sh" --file "$TEMP_DIR/heredoc_mixed.json")
185
+ assert_contains "heredoc handles mixed quotes" "it's fine" "$RESULT"
186
+
159
187
  echo ""
160
188
  echo "=== Results: $PASS/$TOTAL passed, $FAIL failed ==="
161
189
 
@@ -15,7 +15,7 @@
15
15
  set -euo pipefail
16
16
 
17
17
  # ── Configuration ──────────────────────────────────────────────────────────────
18
- MODEL="${GEMINI_MODEL:-gemini-2.5-flash-preview-05-20}"
18
+ MODEL="${GEMINI_MODEL:-gemini-2.0-flash}"
19
19
  GEMINI_API_BASE="https://generativelanguage.googleapis.com"
20
20
  GEMINI_CONTENT_URL="${GEMINI_API_BASE}/v1beta/models/${MODEL}:generateContent"
21
21
  MAX_IMAGE_SIZE_MB=4
@@ -149,12 +149,12 @@ REQUEST_BODY=$(cat <<ENDJSON
149
149
  ENDJSON
150
150
  )
151
151
 
152
- TMPFILE=$(mktemp /tmp/gemini-compare-XXXXXX.json)
152
+ TMPFILE="/tmp/gemini-compare-$(date +%s).json"
153
153
  echo "$REQUEST_BODY" > "$TMPFILE"
154
- RESPONSE=$(curl -sf -X POST \
154
+ RESPONSE=$(curl -s -X POST \
155
155
  "${GEMINI_CONTENT_URL}?key=${GEMINI_API_KEY}" \
156
156
  -H "Content-Type: application/json" \
157
- -d "@${TMPFILE}" 2>&1)
157
+ -d "@${TMPFILE}")
158
158
  rm -f "$TMPFILE"
159
159
 
160
160
  HTTP_CODE=$?
@@ -0,0 +1,52 @@
1
+ # xhs-article-to-image
2
+
3
+ Convert markdown articles into XiaoHongShu (小红书) style image cards.
4
+
5
+ ## Description
6
+
7
+ Generates multiple PNG images (1080×1440, 3:4 portrait ratio) from markdown article content, styled in the popular XiaoHongShu magazine-layout format: bold titles, clean white backgrounds, structured paragraphs with visual hierarchy.
8
+
9
+ ## Usage
10
+
11
+ ```bash
12
+ bash execute.sh '{"content":"# Title\n\nArticle body...","outputDir":"/tmp/xhs-output"}'
13
+ ```
14
+
15
+ Or from a file:
16
+ ```bash
17
+ bash execute.sh '{"sourceFile":"/path/to/article.md","outputDir":"/tmp/xhs-output"}'
18
+ ```
19
+
20
+ ## Parameters
21
+
22
+ | Parameter | Required | Default | Description |
23
+ |-----------|----------|---------|-------------|
24
+ | content | Yes* | - | Markdown article content (required if no sourceFile) |
25
+ | sourceFile | Yes* | - | Path to markdown file (required if no content) |
26
+ | outputDir | Yes | - | Directory to write PNG images to |
27
+ | title | No | auto | Override the article title (auto-extracts from first H1) |
28
+ | author | No | "" | Author name shown at bottom of pages |
29
+ | accentColor | No | "#1a1a1a" | Accent color for decorative elements |
30
+ | bgColor | No | "#fafaf8" | Background color |
31
+ | maxCharsPerPage | No | 350 | Approximate characters per page before splitting |
32
+ | coverImage | No | "" | URL or path to image for the cover page |
33
+
34
+ ## Output
35
+
36
+ - Multiple PNG files: `page-01.png`, `page-02.png`, etc.
37
+ - First page is the title/cover page
38
+ - Returns JSON with file paths and metadata
39
+
40
+ ## Dependencies
41
+
42
+ - Node.js 18+
43
+ - Playwright (chromium)
44
+
45
+ ## Style Features
46
+
47
+ - 3:4 portrait ratio (1080×1440px) optimized for XHS
48
+ - Bold sans-serif Chinese/English typography (Noto Sans SC)
49
+ - Clean white/cream background with subtle decorative elements
50
+ - Magazine-style paragraph layout with visual breathing room
51
+ - Page numbering (current/total)
52
+ - Optional author attribution
@@ -0,0 +1,120 @@
1
+ #!/bin/bash
2
+ # Convert markdown articles into XiaoHongShu-style image cards (1080x1440 PNG)
3
+ # Uses Playwright to render HTML+CSS templates and capture screenshots.
4
+ set -euo pipefail
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ source "${SCRIPT_DIR}/../_common/lib.sh"
7
+
8
+ INPUT=$(read_json_input "${1:-}")
9
+ [ -z "$INPUT" ] && error_exit "Usage: execute.sh '{\"content\":\"# Title\\n\\nBody...\",\"outputDir\":\"/tmp/xhs\"}' or '{\"sourceFile\":\"/path/to/article.md\",\"outputDir\":\"/tmp/xhs\"}'"
10
+
11
+ # Parse parameters
12
+ CONTENT=$(printf '%s' "$INPUT" | jq -r '.content // empty')
13
+ SOURCE_FILE=$(printf '%s' "$INPUT" | jq -r '.sourceFile // empty')
14
+ OUTPUT_DIR=$(printf '%s' "$INPUT" | jq -r '.outputDir // empty')
15
+ TITLE_OVERRIDE=$(printf '%s' "$INPUT" | jq -r '.title // empty')
16
+ AUTHOR=$(printf '%s' "$INPUT" | jq -r '.author // empty')
17
+ ACCENT_COLOR=$(printf '%s' "$INPUT" | jq -r '.accentColor // "#1a1a1a"')
18
+ BG_COLOR=$(printf '%s' "$INPUT" | jq -r '.bgColor // "#fafaf8"')
19
+ MAX_CHARS=$(printf '%s' "$INPUT" | jq -r '.maxCharsPerPage // "350"')
20
+ COVER_IMAGE=$(printf '%s' "$INPUT" | jq -r '.coverImage // empty')
21
+
22
+ # Load content from file if sourceFile is provided
23
+ if [ -n "$SOURCE_FILE" ]; then
24
+ if [ ! -f "$SOURCE_FILE" ]; then
25
+ error_exit "Source file not found: $SOURCE_FILE"
26
+ fi
27
+ CONTENT=$(cat "$SOURCE_FILE")
28
+ fi
29
+
30
+ require_param "content" "$CONTENT"
31
+ require_param "outputDir" "$OUTPUT_DIR"
32
+
33
+ # Create output directory
34
+ mkdir -p "$OUTPUT_DIR"
35
+
36
+ # Extract title from markdown (first # heading)
37
+ if [ -n "$TITLE_OVERRIDE" ]; then
38
+ TITLE="$TITLE_OVERRIDE"
39
+ else
40
+ TITLE=$(echo "$CONTENT" | grep -m1 '^# ' | sed 's/^# //' || echo "Untitled")
41
+ if [ -z "$TITLE" ]; then
42
+ TITLE="Untitled"
43
+ fi
44
+ fi
45
+
46
+ # Remove title line from content body
47
+ BODY=$(echo "$CONTENT" | sed '0,/^# .*$/d')
48
+ if [ -z "$BODY" ]; then
49
+ BODY="$CONTENT"
50
+ fi
51
+
52
+ # ─── Split content into pages ─────────────────────────────────────────────────
53
+ # Split on explicit --- page breaks first, then by character count
54
+
55
+ # Use node to do the splitting and rendering (complex text processing)
56
+ RENDER_INPUT=$(jq -n \
57
+ --arg title "$TITLE" \
58
+ --arg author "$AUTHOR" \
59
+ --arg accentColor "$ACCENT_COLOR" \
60
+ --arg bgColor "$BG_COLOR" \
61
+ --arg outputDir "$OUTPUT_DIR" \
62
+ --arg coverImage "$COVER_IMAGE" \
63
+ --arg body "$BODY" \
64
+ --argjson maxChars "$MAX_CHARS" \
65
+ '{
66
+ title: $title,
67
+ author: $author,
68
+ accentColor: $accentColor,
69
+ bgColor: $bgColor,
70
+ outputDir: $outputDir,
71
+ coverImage: $coverImage,
72
+ maxChars: $maxChars,
73
+ body: $body
74
+ }')
75
+
76
+ # Use Node.js to split content into pages (handles complex markdown parsing)
77
+ PAGES_JSON=$(node -e "
78
+ const input = JSON.parse(process.argv[1]);
79
+ const body = input.body;
80
+ const maxChars = input.maxChars;
81
+
82
+ function splitIntoPages(markdown, maxChars) {
83
+ const explicitSections = markdown.split(/\n---\n/).filter(s => s.trim());
84
+ const pages = [];
85
+ for (const section of explicitSections) {
86
+ if (section.length <= maxChars) {
87
+ pages.push(section.trim());
88
+ continue;
89
+ }
90
+ const blocks = section.split(/\n\n/).filter(b => b.trim());
91
+ let currentPage = '';
92
+ for (const block of blocks) {
93
+ if (currentPage.length > 0 && currentPage.length + block.length > maxChars) {
94
+ pages.push(currentPage.trim());
95
+ currentPage = block;
96
+ } else {
97
+ currentPage += (currentPage ? '\n\n' : '') + block;
98
+ }
99
+ }
100
+ if (currentPage.trim()) pages.push(currentPage.trim());
101
+ }
102
+ return pages.length > 0 ? pages : [markdown.trim()];
103
+ }
104
+
105
+ const pages = splitIntoPages(body, maxChars);
106
+ console.log(JSON.stringify(pages));
107
+ " "$RENDER_INPUT")
108
+
109
+ # Build final render input with pages array
110
+ FINAL_INPUT=$(printf '%s' "$RENDER_INPUT" | jq --argjson pages "$PAGES_JSON" '. + {pages: $pages} | del(.body, .maxChars)')
111
+
112
+ # Run Playwright renderer
113
+ RESULT=$(node "$SCRIPT_DIR/render.mjs" "$FINAL_INPUT" 2>&1)
114
+
115
+ # Check for errors
116
+ if echo "$RESULT" | jq -e '.success == true' > /dev/null 2>&1; then
117
+ echo "$RESULT" | jq --arg title "$TITLE" '. + {title: $title}'
118
+ else
119
+ error_exit "Rendering failed: $RESULT"
120
+ fi
@@ -0,0 +1,601 @@
1
+ /**
2
+ * XHS Article-to-Image Renderer
3
+ *
4
+ * Converts markdown article content into multiple XiaoHongShu-style
5
+ * PNG image cards (1080×1440, 3:4 portrait) using Playwright.
6
+ *
7
+ * Input: JSON via argv[2] with { pages, title, author, accentColor, bgColor, outputDir, coverImage }
8
+ * Output: PNG files written to outputDir, JSON result to stdout.
9
+ */
10
+ import { chromium } from 'playwright';
11
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
12
+ import { join, resolve } from 'path';
13
+
14
+ const WIDTH = 1080;
15
+ const HEIGHT = 1440;
16
+
17
+ const input = JSON.parse(process.argv[2]);
18
+ const {
19
+ pages,
20
+ title = 'Untitled',
21
+ author = '',
22
+ accentColor = '#1a1a1a',
23
+ bgColor = '#fafaf8',
24
+ outputDir,
25
+ coverImage = '',
26
+ } = input;
27
+
28
+ mkdirSync(outputDir, { recursive: true });
29
+
30
+ /**
31
+ * Generate the HTML for the cover (first) page.
32
+ *
33
+ * @param {string} title - Article title
34
+ * @param {string} author - Author name
35
+ * @param {string} accentColor - Accent color hex
36
+ * @param {string} bgColor - Background color hex
37
+ * @param {string} coverImage - Optional cover image URL/path
38
+ * @param {number} totalPages - Total page count
39
+ * @returns {string} Complete HTML string
40
+ */
41
+ function buildCoverHTML(title, author, accentColor, bgColor, coverImage, totalPages) {
42
+ const imageBlock = coverImage
43
+ ? `<div class="cover-image"><img src="${coverImage}" alt="cover" /></div>`
44
+ : '';
45
+
46
+ return `<!DOCTYPE html>
47
+ <html>
48
+ <head>
49
+ <meta charset="utf-8">
50
+ <style>
51
+ ${getBaseStyles(bgColor, accentColor)}
52
+
53
+ .cover-container {
54
+ display: flex;
55
+ flex-direction: column;
56
+ justify-content: center;
57
+ align-items: center;
58
+ height: 100%;
59
+ padding: 100px 80px;
60
+ box-sizing: border-box;
61
+ text-align: center;
62
+ }
63
+
64
+ .cover-decoration-top {
65
+ width: 60px;
66
+ height: 4px;
67
+ background: ${accentColor};
68
+ margin-bottom: 50px;
69
+ border-radius: 2px;
70
+ }
71
+
72
+ .cover-title {
73
+ font-size: 72px;
74
+ font-weight: 900;
75
+ line-height: 1.4;
76
+ color: ${accentColor};
77
+ letter-spacing: -1px;
78
+ margin-bottom: 40px;
79
+ max-width: 900px;
80
+ word-break: break-word;
81
+ padding: 0 20px;
82
+ }
83
+
84
+ .cover-container::before {
85
+ content: '';
86
+ position: absolute;
87
+ top: 0; left: 0; right: 0; height: 15px;
88
+ background: ${accentColor};
89
+ }
90
+
91
+ .cover-decoration-bottom {
92
+ width: 120px;
93
+ height: 2px;
94
+ background: ${accentColor}33;
95
+ margin-bottom: 40px;
96
+ }
97
+
98
+ .cover-author {
99
+ font-size: 28px;
100
+ color: #888;
101
+ font-weight: 400;
102
+ letter-spacing: 2px;
103
+ }
104
+
105
+ .cover-image {
106
+ width: 100%;
107
+ max-width: 700px;
108
+ margin-bottom: 50px;
109
+ border-radius: 16px;
110
+ overflow: hidden;
111
+ }
112
+
113
+ .cover-image img {
114
+ width: 100%;
115
+ height: auto;
116
+ display: block;
117
+ object-fit: cover;
118
+ max-height: 500px;
119
+ }
120
+
121
+ .page-indicator {
122
+ position: absolute;
123
+ bottom: 50px;
124
+ right: 80px;
125
+ font-size: 22px;
126
+ color: #ccc;
127
+ font-weight: 300;
128
+ }
129
+ </style>
130
+ </head>
131
+ <body>
132
+ <div class="cover-container">
133
+ <div class="cover-decoration-top"></div>
134
+ ${imageBlock}
135
+ <div class="cover-title">${escapeHTML(title)}</div>
136
+ <div class="cover-decoration-bottom"></div>
137
+ ${author ? `<div class="cover-author">${escapeHTML(author)}</div>` : ''}
138
+ </div>
139
+ <div class="page-indicator">1 / ${totalPages}</div>
140
+ </body>
141
+ </html>`;
142
+ }
143
+
144
+ /**
145
+ * Generate the HTML for a content page.
146
+ *
147
+ * @param {string} contentHTML - Pre-rendered HTML content for this page
148
+ * @param {number} pageNum - Current page number (1-based)
149
+ * @param {number} totalPages - Total page count
150
+ * @param {string} title - Article title (shown as header)
151
+ * @param {string} accentColor - Accent color hex
152
+ * @param {string} bgColor - Background color hex
153
+ * @returns {string} Complete HTML string
154
+ */
155
+ function buildContentPageHTML(contentHTML, pageNum, totalPages, title, accentColor, bgColor) {
156
+ return `<!DOCTYPE html>
157
+ <html>
158
+ <head>
159
+ <meta charset="utf-8">
160
+ <style>
161
+ ${getBaseStyles(bgColor, accentColor)}
162
+
163
+ .page-header {
164
+ padding: 60px 80px 0 80px;
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 16px;
168
+ }
169
+
170
+ .page-header-line {
171
+ width: 30px;
172
+ height: 3px;
173
+ background: ${accentColor};
174
+ border-radius: 2px;
175
+ flex-shrink: 0;
176
+ }
177
+
178
+ .page-header-title {
179
+ font-size: 20px;
180
+ font-weight: 600;
181
+ color: #bbb;
182
+ letter-spacing: 1px;
183
+ white-space: nowrap;
184
+ overflow: hidden;
185
+ text-overflow: ellipsis;
186
+ }
187
+
188
+ .content-area {
189
+ padding: 40px 80px 80px 80px;
190
+ flex: 1;
191
+ }
192
+
193
+ .content-area h2 {
194
+ font-size: 42px;
195
+ font-weight: 800;
196
+ color: ${accentColor};
197
+ margin: 0 0 28px 0;
198
+ line-height: 1.35;
199
+ letter-spacing: -0.5px;
200
+ }
201
+
202
+ .content-area h3 {
203
+ font-size: 34px;
204
+ font-weight: 700;
205
+ color: ${accentColor};
206
+ margin: 0 0 22px 0;
207
+ line-height: 1.35;
208
+ }
209
+
210
+ .content-area p {
211
+ font-size: 30px;
212
+ line-height: 1.75;
213
+ color: #333;
214
+ margin: 0 0 24px 0;
215
+ font-weight: 400;
216
+ }
217
+
218
+ .content-area ul, .content-area ol {
219
+ padding-left: 40px;
220
+ margin: 0 0 24px 0;
221
+ }
222
+
223
+ .content-area li {
224
+ font-size: 30px;
225
+ line-height: 1.7;
226
+ color: #333;
227
+ margin-bottom: 12px;
228
+ }
229
+
230
+ .content-area strong {
231
+ font-weight: 700;
232
+ color: ${accentColor};
233
+ }
234
+
235
+ .content-area em {
236
+ font-style: italic;
237
+ color: #555;
238
+ }
239
+
240
+ .content-area blockquote {
241
+ border-left: 4px solid ${accentColor}44;
242
+ margin: 0 0 24px 0;
243
+ padding: 16px 24px;
244
+ background: ${accentColor}08;
245
+ border-radius: 0 12px 12px 0;
246
+ }
247
+
248
+ .content-area blockquote p {
249
+ font-size: 28px;
250
+ color: #555;
251
+ margin: 0;
252
+ font-style: italic;
253
+ }
254
+
255
+ .content-area img {
256
+ max-width: 100%;
257
+ border-radius: 12px;
258
+ margin: 16px 0;
259
+ }
260
+
261
+ .content-area code {
262
+ background: #f0f0f0;
263
+ padding: 2px 8px;
264
+ border-radius: 4px;
265
+ font-size: 26px;
266
+ font-family: 'SF Mono', 'Menlo', monospace;
267
+ }
268
+
269
+ .page-indicator {
270
+ position: absolute;
271
+ bottom: 50px;
272
+ right: 80px;
273
+ font-size: 22px;
274
+ color: #ccc;
275
+ font-weight: 300;
276
+ }
277
+
278
+ .page-footer-decoration {
279
+ position: absolute;
280
+ bottom: 45px;
281
+ left: 80px;
282
+ width: 40px;
283
+ height: 3px;
284
+ background: ${accentColor}22;
285
+ border-radius: 2px;
286
+ }
287
+ </style>
288
+ </head>
289
+ <body>
290
+ <div class="page-header">
291
+ <div class="page-header-line"></div>
292
+ <div class="page-header-title">${escapeHTML(title)}</div>
293
+ </div>
294
+ <div class="content-area">
295
+ ${contentHTML}
296
+ </div>
297
+ <div class="page-footer-decoration"></div>
298
+ <div class="page-indicator">${pageNum} / ${totalPages}</div>
299
+ </body>
300
+ </html>`;
301
+ }
302
+
303
+ /**
304
+ * Shared base CSS styles for all pages.
305
+ *
306
+ * @param {string} bgColor - Background color
307
+ * @param {string} accentColor - Accent/text color
308
+ * @returns {string} CSS string
309
+ */
310
+ function getBaseStyles(bgColor, accentColor) {
311
+ return `
312
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;600;700;800;900&display=swap');
313
+
314
+ * {
315
+ margin: 0;
316
+ padding: 0;
317
+ box-sizing: border-box;
318
+ }
319
+
320
+ body {
321
+ width: ${WIDTH}px;
322
+ height: ${HEIGHT}px;
323
+ background: ${bgColor};
324
+ font-family: 'Noto Sans SC', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', -apple-system, BlinkMacSystemFont, sans-serif;
325
+ overflow: hidden;
326
+ position: relative;
327
+ }
328
+ `;
329
+ }
330
+
331
+ /**
332
+ * Escape HTML special characters.
333
+ *
334
+ * @param {string} str - Raw string
335
+ * @returns {string} Escaped HTML string
336
+ */
337
+ function escapeHTML(str) {
338
+ return str
339
+ .replace(/&/g, '&amp;')
340
+ .replace(/</g, '&lt;')
341
+ .replace(/>/g, '&gt;')
342
+ .replace(/"/g, '&quot;');
343
+ }
344
+
345
+ /**
346
+ * Simple markdown to HTML converter.
347
+ * Handles: h1-h3, bold, italic, lists, blockquotes, images, links, code, paragraphs.
348
+ *
349
+ * @param {string} md - Markdown text
350
+ * @returns {string} HTML string
351
+ */
352
+ function markdownToHTML(md) {
353
+ let html = md;
354
+
355
+ // Images: ![alt](url)
356
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" />');
357
+
358
+ // Links: [text](url) → just text (no clickable links in images)
359
+ html = html.replace(/\[([^\]]+)\]\([^)]+\)/g, '<strong>$1</strong>');
360
+
361
+ // Bold: **text** or __text__
362
+ html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
363
+ html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
364
+
365
+ // Italic: *text* or _text_
366
+ html = html.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
367
+ html = html.replace(/(?<!_)_(?!_)(.+?)(?<!_)_(?!_)/g, '<em>$1</em>');
368
+
369
+ // Inline code: `code`
370
+ html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
371
+
372
+ // Process line by line
373
+ const lines = html.split('\n');
374
+ const result = [];
375
+ let inList = false;
376
+ let listType = '';
377
+ let inBlockquote = false;
378
+ let blockquoteLines = [];
379
+
380
+ for (let i = 0; i < lines.length; i++) {
381
+ const line = lines[i];
382
+ const trimmed = line.trim();
383
+
384
+ // End blockquote
385
+ if (inBlockquote && !trimmed.startsWith('>')) {
386
+ result.push(`<blockquote><p>${blockquoteLines.join('<br/>')}</p></blockquote>`);
387
+ blockquoteLines = [];
388
+ inBlockquote = false;
389
+ }
390
+
391
+ // End list
392
+ if (inList && !trimmed.match(/^[-*]\s/) && !trimmed.match(/^\d+\.\s/) && trimmed !== '') {
393
+ result.push(listType === 'ul' ? '</ul>' : '</ol>');
394
+ inList = false;
395
+ }
396
+
397
+ // Skip empty lines
398
+ if (trimmed === '') {
399
+ if (inList) {
400
+ result.push(listType === 'ul' ? '</ul>' : '</ol>');
401
+ inList = false;
402
+ }
403
+ continue;
404
+ }
405
+
406
+ // Headers (skip h1 — it's the title, shown on cover)
407
+ if (trimmed.startsWith('### ')) {
408
+ result.push(`<h3>${trimmed.slice(4)}</h3>`);
409
+ } else if (trimmed.startsWith('## ')) {
410
+ result.push(`<h2>${trimmed.slice(3)}</h2>`);
411
+ } else if (trimmed.startsWith('# ')) {
412
+ // Skip h1 (title page handles it)
413
+ continue;
414
+ }
415
+ // Blockquote
416
+ else if (trimmed.startsWith('>')) {
417
+ inBlockquote = true;
418
+ blockquoteLines.push(trimmed.slice(1).trim());
419
+ }
420
+ // Unordered list
421
+ else if (trimmed.match(/^[-*]\s/)) {
422
+ if (!inList || listType !== 'ul') {
423
+ if (inList) result.push(listType === 'ul' ? '</ul>' : '</ol>');
424
+ result.push('<ul>');
425
+ inList = true;
426
+ listType = 'ul';
427
+ }
428
+ result.push(`<li>${trimmed.slice(2)}</li>`);
429
+ }
430
+ // Ordered list
431
+ else if (trimmed.match(/^\d+\.\s/)) {
432
+ if (!inList || listType !== 'ol') {
433
+ if (inList) result.push(listType === 'ul' ? '</ul>' : '</ol>');
434
+ result.push('<ol>');
435
+ inList = true;
436
+ listType = 'ol';
437
+ }
438
+ result.push(`<li>${trimmed.replace(/^\d+\.\s/, '')}</li>`);
439
+ }
440
+ // Horizontal rule → page break hint (handled at split level)
441
+ else if (trimmed.match(/^[-*_]{3,}$/)) {
442
+ result.push('<hr/>');
443
+ }
444
+ // Paragraph
445
+ else {
446
+ result.push(`<p>${trimmed}</p>`);
447
+ }
448
+ }
449
+
450
+ // Close any open structures
451
+ if (inBlockquote) {
452
+ result.push(`<blockquote><p>${blockquoteLines.join('<br/>')}</p></blockquote>`);
453
+ }
454
+ if (inList) {
455
+ result.push(listType === 'ul' ? '</ul>' : '</ol>');
456
+ }
457
+
458
+ return result.join('\n');
459
+ }
460
+
461
+ /**
462
+ * Split markdown content into pages.
463
+ * Splits on --- (horizontal rules) first, then by approximate character count.
464
+ *
465
+ * @param {string} markdown - Full markdown (without title line)
466
+ * @param {number} maxChars - Max chars per page
467
+ * @returns {string[]} Array of markdown chunks, one per page
468
+ */
469
+ function splitIntoPages(markdown, maxChars) {
470
+ // First split on explicit --- page breaks
471
+ const explicitSections = markdown.split(/\n---\n/).filter((s) => s.trim());
472
+
473
+ const pages = [];
474
+
475
+ for (const section of explicitSections) {
476
+ // If section fits, keep as one page
477
+ if (section.length <= maxChars) {
478
+ pages.push(section.trim());
479
+ continue;
480
+ }
481
+
482
+ // Split further by paragraphs (double newline)
483
+ const blocks = section.split(/\n\n/).filter((b) => b.trim());
484
+ let currentPage = '';
485
+
486
+ for (const block of blocks) {
487
+ // If adding this block exceeds limit, start a new page
488
+ if (currentPage.length > 0 && currentPage.length + block.length > maxChars) {
489
+ pages.push(currentPage.trim());
490
+ currentPage = block;
491
+ } else {
492
+ currentPage += (currentPage ? '\n\n' : '') + block;
493
+ }
494
+ }
495
+
496
+ if (currentPage.trim()) {
497
+ pages.push(currentPage.trim());
498
+ }
499
+ }
500
+
501
+ return pages.length > 0 ? pages : [markdown.trim()];
502
+ }
503
+
504
+ /**
505
+ * Extract the title from markdown content (first # heading).
506
+ *
507
+ * @param {string} markdown - Full markdown content
508
+ * @returns {{ title: string, body: string }} Extracted title and remaining body
509
+ */
510
+ function extractTitle(markdown) {
511
+ const lines = markdown.split('\n');
512
+ let title = '';
513
+ let bodyStart = 0;
514
+
515
+ for (let i = 0; i < lines.length; i++) {
516
+ const trimmed = lines[i].trim();
517
+ if (trimmed.startsWith('# ') && !trimmed.startsWith('## ')) {
518
+ title = trimmed.slice(2).trim();
519
+ bodyStart = i + 1;
520
+ break;
521
+ }
522
+ if (trimmed !== '') {
523
+ // No h1 found before content — use first non-empty line
524
+ break;
525
+ }
526
+ }
527
+
528
+ const body = lines.slice(bodyStart).join('\n').trim();
529
+ return { title, body };
530
+ }
531
+
532
+ // ─── Main ───────────────────────────────────────────────────────────────────
533
+
534
+ async function main() {
535
+ const totalContentPages = pages.length;
536
+ const totalPages = totalContentPages + 1; // +1 for cover
537
+
538
+ const browser = await chromium.launch({ headless: true });
539
+ const context = await browser.newContext({
540
+ viewport: { width: WIDTH, height: HEIGHT },
541
+ deviceScaleFactor: 2, // Retina for crisp text
542
+ });
543
+
544
+ const filePaths = [];
545
+
546
+ try {
547
+ // Page 1: Cover
548
+ const coverHTML = buildCoverHTML(title, author, accentColor, bgColor, coverImage, totalPages);
549
+ const coverPage = await context.newPage();
550
+ await coverPage.setContent(coverHTML, { waitUntil: 'networkidle' });
551
+ // Wait for fonts to load
552
+ await coverPage.waitForTimeout(500);
553
+ const coverPath = join(outputDir, 'page-01.png');
554
+ await coverPage.screenshot({ path: coverPath, type: 'png' });
555
+ filePaths.push(coverPath);
556
+ await coverPage.close();
557
+
558
+ // Content pages
559
+ for (let i = 0; i < pages.length; i++) {
560
+ const pageNum = i + 2; // 1-based, after cover
561
+ const contentHTML = markdownToHTML(pages[i]);
562
+ const pageHTML = buildContentPageHTML(
563
+ contentHTML,
564
+ pageNum,
565
+ totalPages,
566
+ title,
567
+ accentColor,
568
+ bgColor
569
+ );
570
+
571
+ const contentPage = await context.newPage();
572
+ await contentPage.setContent(pageHTML, { waitUntil: 'networkidle' });
573
+ await contentPage.waitForTimeout(300);
574
+
575
+ const pagePath = join(outputDir, `page-${String(pageNum).padStart(2, '0')}.png`);
576
+ await contentPage.screenshot({ path: pagePath, type: 'png' });
577
+ filePaths.push(pagePath);
578
+ await contentPage.close();
579
+ }
580
+ } finally {
581
+ await browser.close();
582
+ }
583
+
584
+ return {
585
+ success: true,
586
+ totalPages,
587
+ files: filePaths,
588
+ dimensions: { width: WIDTH, height: HEIGHT },
589
+ outputDir: resolve(outputDir),
590
+ };
591
+ }
592
+
593
+ main()
594
+ .then((result) => {
595
+ console.log(JSON.stringify(result));
596
+ process.exit(0);
597
+ })
598
+ .catch((err) => {
599
+ console.error(JSON.stringify({ success: false, error: err.message }));
600
+ process.exit(1);
601
+ });
@@ -31,12 +31,20 @@ export declare class CommunicationModule implements PromptModule {
31
31
  build(config: ModuleConfig): Promise<string>;
32
32
  /**
33
33
  * Build a skill call example snippet. For gemini-cli runtime, uses --file
34
- * pattern to avoid shell escaping EOF errors. For other runtimes, uses
35
- * inline JSON argument.
34
+ * pattern with heredoc to avoid shell escaping EOF errors. For other runtimes,
35
+ * uses inline JSON argument.
36
+ *
37
+ * Uses heredoc with single-quoted delimiter (`<< 'CREWLY_EOF'`) instead of
38
+ * `printf '%s' '...'` because heredoc prevents ALL shell interpretation —
39
+ * single quotes, double quotes, backticks, $variables, and parentheses all
40
+ * pass through literally. The previous printf approach broke when JSON
41
+ * contained single quotes (e.g., "it's working").
36
42
  *
37
43
  * @param config - Module configuration with runtime type
38
44
  * @param skillPath - Relative path to the skill (e.g., 'core/report-status')
39
45
  * @param jsonExample - Example JSON string for inline mode
46
+ * @param basePath - Optional base path override (defaults to config.agentSkillsPath).
47
+ * Use config.tlSkillsPath for team-leader skills like delegate-task.
40
48
  * @returns Formatted bash code block
41
49
  */
42
50
  private buildSkillExample;
@@ -1 +1 @@
1
- {"version":3,"file":"communication.module.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/communication.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAoB,MAAM,8BAA8B,CAAC;AAE5F;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACvD,IAAI,SAAmB;IACvB,QAAQ,SAAK;IACb,SAAS,SAAQ;IACjB,WAAW,UAAQ;IAEnB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI7C;;;;;;;OAOG;IACG,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBlD;;;;;;;;;OASG;IACH,OAAO,CAAC,iBAAiB;IAYzB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA2D9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAwBpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAqBxB"}
1
+ {"version":3,"file":"communication.module.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/communication.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAoB,MAAM,8BAA8B,CAAC;AAE5F;;;;;;;;;;;GAWG;AACH,qBAAa,mBAAoB,YAAW,YAAY;IACvD,IAAI,SAAmB;IACvB,QAAQ,SAAK;IACb,SAAS,SAAQ;IACjB,WAAW,UAAQ;IAEnB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI7C;;;;;;;OAOG;IACG,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAkBlD;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,iBAAiB;IAezB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IAyE9B;;OAEG;IACH,OAAO,CAAC,YAAY;IA8BpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CAqBxB"}
@@ -48,23 +48,34 @@ export class CommunicationModule {
48
48
  }
49
49
  /**
50
50
  * Build a skill call example snippet. For gemini-cli runtime, uses --file
51
- * pattern to avoid shell escaping EOF errors. For other runtimes, uses
52
- * inline JSON argument.
51
+ * pattern with heredoc to avoid shell escaping EOF errors. For other runtimes,
52
+ * uses inline JSON argument.
53
+ *
54
+ * Uses heredoc with single-quoted delimiter (`<< 'CREWLY_EOF'`) instead of
55
+ * `printf '%s' '...'` because heredoc prevents ALL shell interpretation —
56
+ * single quotes, double quotes, backticks, $variables, and parentheses all
57
+ * pass through literally. The previous printf approach broke when JSON
58
+ * contained single quotes (e.g., "it's working").
53
59
  *
54
60
  * @param config - Module configuration with runtime type
55
61
  * @param skillPath - Relative path to the skill (e.g., 'core/report-status')
56
62
  * @param jsonExample - Example JSON string for inline mode
63
+ * @param basePath - Optional base path override (defaults to config.agentSkillsPath).
64
+ * Use config.tlSkillsPath for team-leader skills like delegate-task.
57
65
  * @returns Formatted bash code block
58
66
  */
59
- buildSkillExample(config, skillPath, jsonExample) {
67
+ buildSkillExample(config, skillPath, jsonExample, basePath) {
68
+ const resolvedBase = basePath || config.agentSkillsPath;
60
69
  if (config.runtimeType === 'gemini-cli') {
61
70
  return `\`\`\`bash
62
- printf '%s' '${jsonExample}' > /tmp/crewly_skill_input.json
63
- bash ${config.agentSkillsPath}/${skillPath}/execute.sh --file /tmp/crewly_skill_input.json
71
+ cat > /tmp/crewly_skill_input.json << 'CREWLY_EOF'
72
+ ${jsonExample}
73
+ CREWLY_EOF
74
+ bash ${resolvedBase}/${skillPath}/execute.sh --file /tmp/crewly_skill_input.json
64
75
  \`\`\``;
65
76
  }
66
77
  return `\`\`\`bash
67
- bash ${config.agentSkillsPath}/${skillPath}/execute.sh '${jsonExample}'
78
+ bash ${resolvedBase}/${skillPath}/execute.sh '${jsonExample}'
68
79
  \`\`\``;
69
80
  }
70
81
  /**
@@ -85,14 +96,19 @@ You have access to multiple communication channels:
85
96
  - **Google Chat** — Alternative messaging platform
86
97
  - **NOTIFY markers** — Internal agent-to-orchestrator signaling
87
98
 
88
- ### Message Routing Rules
89
- 1. **User messages** → Reply on the same channel they used (Slack Slack, Chat → Chat)
90
- 2. **Agent status updates** → Process internally, do not forward to user unless requested
91
- 3. **Error notifications** → Notify user on their active channel
92
- 4. **Task completions** → Summarize and notify user on their active channel
99
+ ### Message Routing Rules — Reply on the SAME Channel
100
+ 1. **\`[CHAT:...]\` prefix** → Message from Chat UIUse \`reply-chat\` skill
101
+ 2. **\`[GCHAT:...]\` prefix** → Message from Google Chat Use \`reply-gchat\` skill
102
+ 3. **\`[SLACK:...]\` marker** → Message from Slack Use \`reply-slack\` skill
103
+ 4. **\`[REMOTE:...]\` marker** → Message from remote device Use \`reply-remote\` skill
104
+ 5. **Agent status updates** → Process internally, do not forward to user unless requested
105
+ 6. **Task completions / Error notifications** → Notify user on their active channel
106
+
107
+ **CRITICAL:** Match the reply skill to the message source prefix. Never use \`reply-slack\` for a \`[GCHAT:...]\` message or vice versa.
93
108
 
94
109
  ### Slack Communication
95
110
  - Use \`reply-slack\` skill to send messages to Slack threads
111
+ - Triggered when you see a \`[SLACK:channelId:threadTs]\` marker on the message
96
112
  - Always reply in the **same thread** the user messaged from
97
113
  - Format messages using Slack mrkdwn (not standard Markdown):
98
114
  - Bold: \`*text*\` (not \`**text**\`)
@@ -101,8 +117,17 @@ You have access to multiple communication channels:
101
117
  - Links: \`<url|text>\` (not \`[text](url)\`)
102
118
  - Keep messages concise — avoid walls of text in Slack
103
119
 
120
+ ### Google Chat Communication
121
+ - Use \`reply-gchat\` skill to send messages to Google Chat threads
122
+ - Triggered when you see a \`[GCHAT:conversationId thread=spaceName]\` prefix on the message
123
+ - Extract the \`thread=\` value from the prefix and pass it as \`--thread\` to reply-gchat
124
+ - Extract the space from the conversationId and pass it as \`--space\` to reply-gchat
125
+ - Standard Markdown formatting is supported in Google Chat
126
+ - Keep messages concise and actionable
127
+
104
128
  ### Chat UI Communication
105
129
  - Use \`reply-chat\` skill for Chat UI responses
130
+ - Triggered when you see a \`[CHAT:...]\` prefix without \`GCHAT\` or \`SLACK\` markers
106
131
  - Standard Markdown formatting is supported
107
132
  - Include structured data (tables, code blocks) when helpful
108
133
 
@@ -134,8 +159,10 @@ ${sendExample}`;
134
159
  buildTLComms(config) {
135
160
  const reportStatusJson = `{"sessionName":"${config.sessionName}","status":"<status>","summary":"<summary>","projectPath":"${config.projectPath || config.projectRoot}"}`;
136
161
  const sendMessageJson = '{"to":"<worker-session>","message":"<task or feedback>"}';
162
+ const delegateTaskJson = `{"to":"<worker-session>","task":"<task description>","priority":"high","teamId":"${config.teamId || ''}","tlMemberId":"${config.memberId}","projectPath":"${config.projectPath || config.projectRoot}"}`;
137
163
  const reportExample = this.buildSkillExample(config, 'core/report-status', reportStatusJson);
138
164
  const sendExample = this.buildSkillExample(config, 'core/send-message', sendMessageJson);
165
+ const delegateExample = this.buildSkillExample(config, 'delegate-task', delegateTaskJson, config.tlSkillsPath);
139
166
  return `## Communication Protocol
140
167
 
141
168
  ### Reporting to Orchestrator
@@ -146,6 +173,10 @@ ${reportExample}
146
173
  Use \`send-message\` to communicate with your subordinates:
147
174
  ${sendExample}
148
175
 
176
+ ### Delegating Tasks
177
+ Use \`delegate-task\` to assign tasks to your subordinates:
178
+ ${delegateExample}
179
+
149
180
  ### Communication Rules
150
181
  1. **Report up** — Keep orchestrator informed of progress and blockers
151
182
  2. **Direct workers** — Send clear, actionable messages to subordinates
@@ -1 +1 @@
1
- {"version":3,"file":"communication.module.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/communication.module.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"communication.module.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/communication.module.ts"],"names":[],"mappings":"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"}
@@ -39,8 +39,14 @@ export declare class SkillsReferenceModule implements PromptModule {
39
39
  *
40
40
  * Gemini CLI's run_shell_command mangles JSON arguments containing quotes,
41
41
  * backticks, and parentheses, causing "unexpected EOF" shell errors.
42
- * This guide instructs the agent to write JSON to a temp file first,
43
- * then pass --file <path> to avoid shell interpretation entirely.
42
+ * This guide instructs the agent to write JSON to a temp file using heredoc
43
+ * with a single-quoted delimiter, then pass --file <path>.
44
+ *
45
+ * Uses `<< 'CREWLY_EOF'` instead of `printf '%s' '...'` because:
46
+ * - Single-quoted heredoc delimiter prevents ALL shell interpretation
47
+ * - Single quotes, double quotes, backticks, $, () all pass through literally
48
+ * - The previous printf approach broke when JSON contained single quotes
49
+ * (e.g., "it's working" or "don't forget")
44
50
  *
45
51
  * Only included for gemini-cli runtime type.
46
52
  *
@@ -1 +1 @@
1
- {"version":3,"file":"skills-reference.module.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/skills-reference.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,YAAW,YAAY;IACzD,IAAI,SAAuB;IAC3B,QAAQ,SAAK;IACb,SAAS,SAAO;IAChB,WAAW,UAAS;IAEpB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI7C;;;;;;OAMG;IACG,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAalD;;OAEG;IACH,OAAO,CAAC,eAAe;IAoDvB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAiCzB;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,kBAAkB;IA2B1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAkB1B"}
1
+ {"version":3,"file":"skills-reference.module.d.ts","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/skills-reference.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;;;GAQG;AACH,qBAAa,qBAAsB,YAAW,YAAY;IACzD,IAAI,SAAuB;IAC3B,QAAQ,SAAK;IACb,SAAS,SAAO;IAChB,WAAW,UAAS;IAEpB;;OAEG;IACH,aAAa,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI7C;;;;;;OAMG;IACG,KAAK,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAalD;;OAEG;IACH,OAAO,CAAC,eAAe;IAoDvB;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAiCzB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,kBAAkB;IAsC1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAkB1B"}
@@ -84,8 +84,14 @@ export class SkillsReferenceModule {
84
84
  *
85
85
  * Gemini CLI's run_shell_command mangles JSON arguments containing quotes,
86
86
  * backticks, and parentheses, causing "unexpected EOF" shell errors.
87
- * This guide instructs the agent to write JSON to a temp file first,
88
- * then pass --file <path> to avoid shell interpretation entirely.
87
+ * This guide instructs the agent to write JSON to a temp file using heredoc
88
+ * with a single-quoted delimiter, then pass --file <path>.
89
+ *
90
+ * Uses `<< 'CREWLY_EOF'` instead of `printf '%s' '...'` because:
91
+ * - Single-quoted heredoc delimiter prevents ALL shell interpretation
92
+ * - Single quotes, double quotes, backticks, $, () all pass through literally
93
+ * - The previous printf approach broke when JSON contained single quotes
94
+ * (e.g., "it's working" or "don't forget")
89
95
  *
90
96
  * Only included for gemini-cli runtime type.
91
97
  *
@@ -98,24 +104,35 @@ export class SkillsReferenceModule {
98
104
  }
99
105
  return `## Safe Skill Calling (MANDATORY)
100
106
 
101
- **CRITICAL:** When calling any bash skill, you MUST use the \`--file\` pattern to avoid shell escaping errors.
107
+ **CRITICAL:** When calling any bash skill, you MUST use the \`--file\` pattern with heredoc to avoid shell escaping errors.
102
108
  Passing JSON directly as a shell argument will cause "unexpected EOF" errors when the content contains quotes, backticks, or parentheses.
103
109
 
104
110
  ### Required Pattern
105
111
  \`\`\`bash
106
- # Step 1: Write JSON to a temp file (printf preserves all special characters)
107
- printf '%s' '{"sessionName":"my-agent","status":"done","summary":"Fixed the bug"}' > /tmp/crewly_skill_input.json
112
+ # Step 1: Write JSON to a temp file using heredoc (single-quoted delimiter prevents ALL shell interpretation)
113
+ cat > /tmp/crewly_skill_input.json << 'CREWLY_EOF'
114
+ {"sessionName":"my-agent","status":"done","summary":"Fixed the bug — it's working now"}
115
+ CREWLY_EOF
108
116
 
109
117
  # Step 2: Call the skill with --file flag
110
118
  bash ${config.agentSkillsPath}/core/report-status/execute.sh --file /tmp/crewly_skill_input.json
111
- \`\`\`
119
+ \`\`\`${config.canDelegate ? `
120
+
121
+ ### Team Leader Skills (delegate-task, verify-output, schedule-check)
122
+ \`\`\`bash
123
+ cat > /tmp/crewly_skill_input.json << 'CREWLY_EOF'
124
+ {"to":"worker-session","task":"implement feature","priority":"high"}
125
+ CREWLY_EOF
126
+ bash ${config.tlSkillsPath}/delegate-task/execute.sh --file /tmp/crewly_skill_input.json
127
+ \`\`\`` : ''}
112
128
 
113
129
  ### Rules
114
- 1. **ALWAYS** use \`printf '%s' '<json>' > /tmp/crewly_skill_input.json\` then \`--file\`
115
- 2. **NEVER** pass JSON directly as a shell argument: \`bash execute.sh '{"key":"value with 'quotes'"}'\`
116
- 3. Use \`/tmp/crewly_skill_input.json\` as the temp file (overwritten each call)
117
- 4. This applies to ALL skills: report-status, send-message, remember, recall, delegate-task, reply-slack, reply-gchat, etc.
118
- 5. For skills that support named flags (reply-slack, reply-gchat), you may also use \`--text-file\` for the message body`;
130
+ 1. **ALWAYS** use heredoc to write JSON: \`cat > /tmp/crewly_skill_input.json << 'CREWLY_EOF'\` then the JSON, then \`CREWLY_EOF\` on its own line
131
+ 2. **NEVER** pass JSON directly as a shell argument: \`bash execute.sh '{"key":"value"}'\`
132
+ 3. **NEVER** use \`printf '%s' '...'\` single quotes inside JSON will break it
133
+ 4. Use \`/tmp/crewly_skill_input.json\` as the temp file (overwritten each call)
134
+ 5. This applies to ALL skills: report-status, send-message, remember, recall, delegate-task, reply-slack, reply-gchat, etc.
135
+ 6. For skills that support named flags (reply-slack, reply-gchat), you may also use \`--text-file\` for the message body`;
119
136
  }
120
137
  /**
121
138
  * Build communication and memory tool instructions.
@@ -1 +1 @@
1
- {"version":3,"file":"skills-reference.module.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/skills-reference.module.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"skills-reference.module.js","sourceRoot":"","sources":["../../../../../../../backend/src/services/ai/prompt-modules/skills-reference.module.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewly",
3
- "version": "1.5.13",
3
+ "version": "1.5.15",
4
4
  "type": "module",
5
5
  "description": "Multi-agent orchestration platform for AI coding teams — coordinates Claude Code, Gemini CLI, and Codex agents with a real-time web dashboard",
6
6
  "main": "dist/cli/cli/src/index.js",