juno-code 1.0.23 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ var __export = (target, all) => {
75
75
  exports.version = void 0;
76
76
  var init_version = __esm({
77
77
  "src/version.ts"() {
78
- exports.version = "1.0.23";
78
+ exports.version = "1.0.26";
79
79
  }
80
80
  });
81
81
  function isHeadlessEnvironment() {
package/dist/index.mjs CHANGED
@@ -44,7 +44,7 @@ var __export = (target, all) => {
44
44
  var version;
45
45
  var init_version = __esm({
46
46
  "src/version.ts"() {
47
- version = "1.0.23";
47
+ version = "1.0.26";
48
48
  }
49
49
  });
50
50
  function isHeadlessEnvironment() {
@@ -10,13 +10,19 @@
10
10
  # Usage: ./.juno_task/scripts/kanban.sh [juno-kanban arguments]
11
11
  # Example: ./.juno_task/scripts/kanban.sh list --limit 5
12
12
  #
13
+ # Environment Variables:
14
+ # JUNO_DEBUG=true - Show [DEBUG] diagnostic messages
15
+ # JUNO_VERBOSE=true - Show [KANBAN] informational messages
16
+ # (Both default to false for silent operation)
17
+ #
13
18
  # Created by: juno-code init command
14
19
  # Date: Auto-generated during project initialization
15
20
 
16
21
  set -euo pipefail # Exit on error, undefined variable, or pipe failure
17
22
 
18
- # DEBUG OUTPUT: Show that kanban.sh is being executed (only if JUNO_VERBOSE=true)
19
- if [ "${JUNO_VERBOSE:-false}" = "true" ]; then
23
+ # DEBUG OUTPUT: Show that kanban.sh is being executed (only if JUNO_DEBUG=true)
24
+ # Note: JUNO_DEBUG is separate from JUNO_VERBOSE for fine-grained control
25
+ if [ "${JUNO_DEBUG:-false}" = "true" ]; then
20
26
  echo "[DEBUG] kanban.sh is being executed from: $(pwd)" >&2
21
27
  fi
22
28
 
@@ -31,6 +31,8 @@ class ClaudeService:
31
31
  self.additional_args: List[str] = []
32
32
  self.message_counter = 0
33
33
  self.verbose = False
34
+ # User message truncation: -1 = no truncation, N = truncate to N lines
35
+ self.user_message_truncate = int(os.environ.get("CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE", "4"))
34
36
 
35
37
  def check_claude_installed(self) -> bool:
36
38
  """Check if claude CLI is installed and available"""
@@ -57,12 +59,13 @@ Examples:
57
59
  %(prog)s -p "Add tests" -m claude-opus-4-20250514 --tool "Bash Edit"
58
60
 
59
61
  Environment Variables:
60
- CLAUDE_PROJECT_PATH Project path (default: current directory)
61
- CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)
62
- CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt
63
- CLAUDE_PERMISSION_MODE Permission mode (default: default)
64
- CLAUDE_PRETTY Pretty print JSON output (default: true)
65
- CLAUDE_VERBOSE Enable verbose output (default: false)
62
+ CLAUDE_PROJECT_PATH Project path (default: current directory)
63
+ CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)
64
+ CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt
65
+ CLAUDE_PERMISSION_MODE Permission mode (default: default)
66
+ CLAUDE_PRETTY Pretty print JSON output (default: true)
67
+ CLAUDE_VERBOSE Enable verbose output (default: false)
68
+ CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)
66
69
  """
67
70
  )
68
71
 
@@ -214,10 +217,20 @@ Environment Variables:
214
217
  """
215
218
  Format JSON line for pretty output.
216
219
  For type=assistant: show datetime, message content, and counter
220
+ For type=user: show datetime, message content (truncated based on env var), and counter
217
221
  For other types: show full message with datetime and counter
218
222
  Returns None if line should be skipped
219
223
 
220
224
  IMPORTANT: Always preserve the 'type' field so shell backend can parse events
225
+
226
+ MULTI-LINE HANDLING: When content/result fields contain \\n escape sequences,
227
+ the output shows the JSON metadata on one line, then the actual content/result
228
+ value is printed below with newlines properly rendered (similar to jq -r or @text).
229
+ This keeps JSON structure compact while making multi-line strings readable.
230
+
231
+ USER MESSAGE TRUNCATION: User messages are truncated based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
232
+ environment variable (default: 4 lines, -1: no truncation). When truncated, a [Truncated...]
233
+ indicator is added. This only applies to user messages in pretty mode.
221
234
  """
222
235
  try:
223
236
  data = json.loads(json_line)
@@ -226,8 +239,53 @@ Environment Variables:
226
239
  # Get current datetime in readable format
227
240
  now = datetime.now().strftime("%I:%M:%S %p")
228
241
 
242
+ # For user messages, show simplified output with truncation
243
+ if data.get("type") == "user":
244
+ message = data.get("message", {})
245
+ content_list = message.get("content", [])
246
+
247
+ # Extract text content
248
+ text_content = ""
249
+ for item in content_list:
250
+ if isinstance(item, dict) and item.get("type") == "text":
251
+ text_content = item.get("text", "")
252
+ break
253
+
254
+ # Apply truncation for user messages based on CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE
255
+ # -1 means no truncation, otherwise truncate to N lines
256
+ if self.user_message_truncate != -1:
257
+ lines = text_content.split('\n')
258
+ if len(lines) > self.user_message_truncate:
259
+ # Truncate to N lines and add indicator
260
+ text_content = '\n'.join(lines[:self.user_message_truncate]) + '\n[Truncated...]'
261
+
262
+ # Create simplified output with datetime, content, and counter
263
+ simplified = {
264
+ "type": "user",
265
+ "datetime": now,
266
+ "counter": f"#{self.message_counter}"
267
+ }
268
+
269
+ # Check if content has newlines after potential truncation
270
+ if '\n' in text_content:
271
+ # Multi-line content: print JSON metadata, then raw content
272
+ metadata = {
273
+ "type": "user",
274
+ "datetime": now,
275
+ "counter": f"#{self.message_counter}"
276
+ }
277
+ # Print metadata as compact JSON on first line
278
+ output = json.dumps(metadata, ensure_ascii=False)
279
+ # Then print content label and raw multi-line text
280
+ output += "\ncontent:\n" + text_content
281
+ return output
282
+ else:
283
+ # Single-line content: normal JSON
284
+ simplified["content"] = text_content
285
+ return json.dumps(simplified, ensure_ascii=False)
286
+
229
287
  # For assistant messages, show simplified output
230
- if data.get("type") == "assistant":
288
+ elif data.get("type") == "assistant":
231
289
  message = data.get("message", {})
232
290
  content_list = message.get("content", [])
233
291
 
@@ -259,11 +317,61 @@ Environment Variables:
259
317
  # Add either content or tool_use data
260
318
  if tool_use_data:
261
319
  simplified["tool_use"] = tool_use_data
320
+ # Normal JSON output for tool_use
321
+ return json.dumps(simplified, ensure_ascii=False)
262
322
  else:
263
- simplified["content"] = text_content
264
-
265
- return json.dumps(simplified)
323
+ # For content, check if it has newlines
324
+ if '\n' in text_content:
325
+ # Multi-line content: print JSON metadata, then raw content
326
+ metadata = {
327
+ "type": "assistant",
328
+ "datetime": now,
329
+ "counter": f"#{self.message_counter}"
330
+ }
331
+ # Print metadata as compact JSON on first line
332
+ output = json.dumps(metadata, ensure_ascii=False)
333
+ # Then print content label and raw multi-line text
334
+ output += "\ncontent:\n" + text_content
335
+ return output
336
+ else:
337
+ # Single-line content: normal JSON
338
+ simplified["content"] = text_content
339
+ return json.dumps(simplified, ensure_ascii=False)
266
340
  else:
341
+ # For other message types, check if there's nested content to flatten
342
+ message = data.get("message", {})
343
+ content_list = message.get("content", [])
344
+
345
+ # Check if this is a message with nested tool_result or similar content
346
+ if content_list and isinstance(content_list, list) and len(content_list) > 0:
347
+ nested_item = content_list[0]
348
+ if isinstance(nested_item, dict) and nested_item.get("type") in ["tool_result"]:
349
+ # Flatten the nested structure by pulling nested fields to top level
350
+ flattened = {
351
+ "datetime": now,
352
+ "counter": f"#{self.message_counter}",
353
+ }
354
+
355
+ # Add tool_use_id if present
356
+ if "tool_use_id" in nested_item:
357
+ flattened["tool_use_id"] = nested_item["tool_use_id"]
358
+
359
+ # Add type from nested item
360
+ flattened["type"] = nested_item["type"]
361
+
362
+ # Handle content field with multiline support
363
+ nested_content = nested_item.get("content", "")
364
+ if isinstance(nested_content, str) and '\n' in nested_content:
365
+ # Multi-line content: separate metadata from content
366
+ # Print metadata as compact JSON
367
+ metadata_json = json.dumps(flattened, ensure_ascii=False)
368
+ # Then print content label and raw multi-line text
369
+ return metadata_json + "\ncontent:\n" + nested_content
370
+ else:
371
+ # Single-line content: normal JSON
372
+ flattened["content"] = nested_content
373
+ return json.dumps(flattened, ensure_ascii=False)
374
+
267
375
  # For other message types, show full message with datetime and counter
268
376
  # Type field is already present in data, so it's preserved
269
377
  output = {
@@ -271,7 +379,18 @@ Environment Variables:
271
379
  "counter": f"#{self.message_counter}",
272
380
  **data
273
381
  }
274
- return json.dumps(output)
382
+
383
+ # Check if 'result' field has multi-line content
384
+ if "result" in output and isinstance(output["result"], str) and '\n' in output["result"]:
385
+ # Multi-line result: separate metadata from content
386
+ result_value = output.pop("result")
387
+ # Print metadata as compact JSON
388
+ metadata_json = json.dumps(output, ensure_ascii=False)
389
+ # Then print result label and raw multi-line text
390
+ return metadata_json + "\nresult:\n" + result_value
391
+ else:
392
+ # Normal JSON output
393
+ return json.dumps(output, ensure_ascii=False)
275
394
 
276
395
  except json.JSONDecodeError:
277
396
  # If not valid JSON, return as-is
@@ -359,12 +478,13 @@ Environment Variables:
359
478
  )
360
479
  print("\nRun 'claude.py --help' for usage information.", file=sys.stderr)
361
480
  print("\nAvailable Environment Variables:", file=sys.stderr)
362
- print(" CLAUDE_PROJECT_PATH Project path (default: current directory)", file=sys.stderr)
363
- print(" CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)", file=sys.stderr)
364
- print(" CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt", file=sys.stderr)
365
- print(" CLAUDE_PERMISSION_MODE Permission mode (default: default)", file=sys.stderr)
366
- print(" CLAUDE_PRETTY Pretty print JSON output (default: true)", file=sys.stderr)
367
- print(" CLAUDE_VERBOSE Enable verbose output (default: false)", file=sys.stderr)
481
+ print(" CLAUDE_PROJECT_PATH Project path (default: current directory)", file=sys.stderr)
482
+ print(" CLAUDE_MODEL Model name (default: claude-sonnet-4-5-20250929)", file=sys.stderr)
483
+ print(" CLAUDE_AUTO_INSTRUCTION Auto instruction to prepend to prompt", file=sys.stderr)
484
+ print(" CLAUDE_PERMISSION_MODE Permission mode (default: default)", file=sys.stderr)
485
+ print(" CLAUDE_PRETTY Pretty print JSON output (default: true)", file=sys.stderr)
486
+ print(" CLAUDE_VERBOSE Enable verbose output (default: false)", file=sys.stderr)
487
+ print(" CLAUDE_USER_MESSAGE_PRETTY_TRUNCATE Max lines for user messages in pretty mode (default: 4, -1: no truncation)", file=sys.stderr)
368
488
  return 1
369
489
 
370
490
  # Check if claude is installed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juno-code",
3
- "version": "1.0.23",
3
+ "version": "1.0.26",
4
4
  "description": "TypeScript CLI tool for AI subagent orchestration with code automation",
5
5
  "keywords": [
6
6
  "ai",