juno-code 1.0.35 → 1.0.36
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/README.md +364 -64
- package/dist/bin/cli.js +445 -181
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +444 -180
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +1 -1
- package/dist/templates/scripts/clean_logs_folder.sh +0 -0
- package/dist/templates/scripts/run_until_completion.sh +202 -0
- package/dist/templates/services/README.md +43 -0
- package/dist/templates/services/claude.py +1 -1
- package/dist/templates/services/codex.py +4 -4
- package/dist/templates/services/gemini.py +473 -0
- package/package.json +3 -3
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Gemini Service Script for juno-code
|
|
4
|
+
Headless wrapper around the Gemini CLI with JSON streaming and shorthand model support.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import json
|
|
9
|
+
import os
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import List, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class GeminiService:
|
|
17
|
+
"""Service wrapper for Gemini CLI headless mode."""
|
|
18
|
+
|
|
19
|
+
DEFAULT_MODEL = "gemini-2.5-pro"
|
|
20
|
+
DEFAULT_OUTPUT_FORMAT = "stream-json"
|
|
21
|
+
VALID_OUTPUT_FORMATS = ["stream-json", "json", "text"]
|
|
22
|
+
|
|
23
|
+
# Common shorthand mappings (extendable as models evolve)
|
|
24
|
+
MODEL_SHORTHANDS = {
|
|
25
|
+
":pro": "gemini-2.5-pro",
|
|
26
|
+
":flash": "gemini-2.5-flash",
|
|
27
|
+
":pro-2.5": "gemini-2.5-pro",
|
|
28
|
+
":flash-2.5": "gemini-2.5-flash",
|
|
29
|
+
":pro-3": "gemini-3.0-pro",
|
|
30
|
+
":flash-3": "gemini-3.0-flash",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self):
|
|
34
|
+
self.model_name = self.DEFAULT_MODEL
|
|
35
|
+
self.output_format = self.DEFAULT_OUTPUT_FORMAT
|
|
36
|
+
self.project_path = os.getcwd()
|
|
37
|
+
self.prompt = ""
|
|
38
|
+
self.include_dirs: List[str] = []
|
|
39
|
+
self.approval_mode: Optional[str] = None
|
|
40
|
+
self.yolo: bool = False
|
|
41
|
+
self.debug = False
|
|
42
|
+
self.verbose = False
|
|
43
|
+
|
|
44
|
+
def expand_model_shorthand(self, model: str) -> str:
|
|
45
|
+
"""Expand shorthand model names (colon-prefixed) to full identifiers."""
|
|
46
|
+
if model.startswith(":"):
|
|
47
|
+
return self.MODEL_SHORTHANDS.get(model, model)
|
|
48
|
+
return model
|
|
49
|
+
|
|
50
|
+
def check_gemini_installed(self) -> bool:
|
|
51
|
+
"""Check if gemini CLI is installed and available."""
|
|
52
|
+
try:
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
["which", "gemini"],
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
check=False,
|
|
58
|
+
)
|
|
59
|
+
return result.returncode == 0
|
|
60
|
+
except Exception:
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
def ensure_api_key_present(self) -> bool:
|
|
64
|
+
"""Validate that GEMINI_API_KEY is set for headless execution."""
|
|
65
|
+
api_key = os.environ.get("GEMINI_API_KEY", "")
|
|
66
|
+
if isinstance(api_key, str) and api_key.strip():
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
print(
|
|
70
|
+
"Error: GEMINI_API_KEY is not set. Export GEMINI_API_KEY before running gemini headless CLI.",
|
|
71
|
+
file=sys.stderr,
|
|
72
|
+
)
|
|
73
|
+
print("Example: export GEMINI_API_KEY=\"your-api-key\"", file=sys.stderr)
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def parse_arguments(self) -> argparse.Namespace:
|
|
77
|
+
"""Parse command line arguments for the Gemini service."""
|
|
78
|
+
parser = argparse.ArgumentParser(
|
|
79
|
+
description="Gemini Service - Wrapper for Gemini CLI headless mode",
|
|
80
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
81
|
+
epilog="""
|
|
82
|
+
Examples:
|
|
83
|
+
%(prog)s -p "Quick summary of README" --output-format stream-json
|
|
84
|
+
%(prog)s -pp prompt.txt --model :pro-3 --yolo
|
|
85
|
+
%(prog)s -p "Refactor module" --include-directories src,docs
|
|
86
|
+
%(prog)s -p "Audit code" --approval-mode auto_edit --debug
|
|
87
|
+
""",
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
prompt_group = parser.add_mutually_exclusive_group(required=False)
|
|
91
|
+
prompt_group.add_argument("-p", "--prompt", type=str, help="Prompt text to send to Gemini")
|
|
92
|
+
prompt_group.add_argument("-pp", "--prompt-file", type=str, help="Path to file containing the prompt")
|
|
93
|
+
|
|
94
|
+
parser.add_argument(
|
|
95
|
+
"--cd",
|
|
96
|
+
type=str,
|
|
97
|
+
default=os.environ.get("GEMINI_PROJECT_PATH", os.getcwd()),
|
|
98
|
+
help="Project path (absolute). Default: current directory (env: GEMINI_PROJECT_PATH)",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
"-m",
|
|
103
|
+
"--model",
|
|
104
|
+
type=str,
|
|
105
|
+
default=os.environ.get("GEMINI_MODEL", self.DEFAULT_MODEL),
|
|
106
|
+
help=(
|
|
107
|
+
"Gemini model. Supports shorthands (:pro, :flash, :pro-3, :flash-3, :pro-2.5, :flash-2.5) "
|
|
108
|
+
f"or full IDs. Default: {self.DEFAULT_MODEL} (env: GEMINI_MODEL)"
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
parser.add_argument(
|
|
113
|
+
"--output-format",
|
|
114
|
+
type=str,
|
|
115
|
+
choices=self.VALID_OUTPUT_FORMATS,
|
|
116
|
+
default=os.environ.get("GEMINI_OUTPUT_FORMAT", self.DEFAULT_OUTPUT_FORMAT),
|
|
117
|
+
help="Gemini output format (stream-json/json/text). Default: stream-json (env: GEMINI_OUTPUT_FORMAT)",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
parser.add_argument(
|
|
121
|
+
"--include-directories",
|
|
122
|
+
type=str,
|
|
123
|
+
help="Comma-separated list of directories to include for Gemini context (forwarded to CLI).",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
parser.add_argument(
|
|
127
|
+
"--approval-mode",
|
|
128
|
+
type=str,
|
|
129
|
+
help="Set approval mode (e.g., auto_edit). Defaults to --yolo for headless mode when not provided.",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
parser.add_argument(
|
|
133
|
+
"--yolo",
|
|
134
|
+
action="store_true",
|
|
135
|
+
help="Auto-approve all actions (non-interactive). Enabled by default when no approval mode is supplied.",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
parser.add_argument(
|
|
139
|
+
"--debug",
|
|
140
|
+
action="store_true",
|
|
141
|
+
help="Enable Gemini CLI debug output.",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"--verbose",
|
|
146
|
+
action="store_true",
|
|
147
|
+
help="Print the constructed command before execution.",
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return parser.parse_args()
|
|
151
|
+
|
|
152
|
+
def _first_nonempty_str(self, *values: Optional[str]) -> str:
|
|
153
|
+
"""Return the first non-empty string from provided values."""
|
|
154
|
+
for val in values:
|
|
155
|
+
if isinstance(val, str) and val.strip() != "":
|
|
156
|
+
return val
|
|
157
|
+
return ""
|
|
158
|
+
|
|
159
|
+
def _extract_content_text(self, payload: dict) -> str:
|
|
160
|
+
"""Extract human-readable content from various Gemini event payload shapes."""
|
|
161
|
+
if not isinstance(payload, dict):
|
|
162
|
+
return ""
|
|
163
|
+
|
|
164
|
+
content_val = payload.get("content")
|
|
165
|
+
if isinstance(content_val, list):
|
|
166
|
+
parts: List[str] = []
|
|
167
|
+
for entry in content_val:
|
|
168
|
+
if isinstance(entry, dict):
|
|
169
|
+
text_val = entry.get("text") or entry.get("response") or entry.get("output")
|
|
170
|
+
if isinstance(text_val, str) and text_val.strip():
|
|
171
|
+
parts.append(text_val)
|
|
172
|
+
elif isinstance(entry, str) and entry.strip():
|
|
173
|
+
parts.append(entry)
|
|
174
|
+
if parts:
|
|
175
|
+
return "\n".join(parts)
|
|
176
|
+
elif isinstance(content_val, str):
|
|
177
|
+
return content_val
|
|
178
|
+
|
|
179
|
+
# Fall back to common fields
|
|
180
|
+
return self._first_nonempty_str(
|
|
181
|
+
payload.get("response"),
|
|
182
|
+
payload.get("message"),
|
|
183
|
+
payload.get("output"),
|
|
184
|
+
payload.get("result") if isinstance(payload.get("result"), str) else "",
|
|
185
|
+
payload.get("text"),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def _format_event_pretty(self, payload: dict) -> str:
|
|
189
|
+
"""
|
|
190
|
+
Normalize Gemini CLI JSON output into a compact JSON header plus optional multi-line block.
|
|
191
|
+
Ensures a `type` and `content` field exist so shell-backend can stream progress events.
|
|
192
|
+
"""
|
|
193
|
+
try:
|
|
194
|
+
raw_type = payload.get("type") or payload.get("event") or "message"
|
|
195
|
+
msg_type = str(raw_type).strip() or "message"
|
|
196
|
+
now = datetime.now().strftime("%I:%M:%S %p")
|
|
197
|
+
|
|
198
|
+
content = self._extract_content_text(payload)
|
|
199
|
+
# If still empty, serialize result/output objects as JSON to keep content non-undefined
|
|
200
|
+
header = {
|
|
201
|
+
"type": msg_type,
|
|
202
|
+
"datetime": now,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def copy_if_present(key: str, dest: Optional[str] = None):
|
|
206
|
+
val = payload.get(key)
|
|
207
|
+
if val not in (None, ""):
|
|
208
|
+
header[dest or key] = val
|
|
209
|
+
|
|
210
|
+
copy_if_present("role")
|
|
211
|
+
copy_if_present("status")
|
|
212
|
+
copy_if_present("tool_name", "tool")
|
|
213
|
+
copy_if_present("tool_id", "tool_id")
|
|
214
|
+
copy_if_present("timestamp")
|
|
215
|
+
copy_if_present("session_id")
|
|
216
|
+
copy_if_present("model")
|
|
217
|
+
if payload.get("delta"):
|
|
218
|
+
header["delta"] = True
|
|
219
|
+
|
|
220
|
+
if msg_type == "tool_use" and not content:
|
|
221
|
+
tool_params = payload.get("parameters") or payload.get("tool_use") or payload.get("input")
|
|
222
|
+
if isinstance(tool_params, (dict, list)):
|
|
223
|
+
header["parameters"] = tool_params
|
|
224
|
+
content = json.dumps(tool_params, ensure_ascii=False)
|
|
225
|
+
elif tool_params:
|
|
226
|
+
content = str(tool_params)
|
|
227
|
+
|
|
228
|
+
if msg_type == "tool_result" and not content:
|
|
229
|
+
tool_output = self._first_nonempty_str(payload.get("output"), payload.get("result"))
|
|
230
|
+
if tool_output:
|
|
231
|
+
content = tool_output
|
|
232
|
+
|
|
233
|
+
if msg_type == "init" and not content:
|
|
234
|
+
init_summary = {k: payload.get(k) for k in ["session_id", "model"] if payload.get(k)}
|
|
235
|
+
if init_summary:
|
|
236
|
+
content = json.dumps(init_summary, ensure_ascii=False)
|
|
237
|
+
|
|
238
|
+
if msg_type == "result" and not content:
|
|
239
|
+
if isinstance(payload.get("stats"), (dict, list)):
|
|
240
|
+
content = json.dumps(payload.get("stats"), ensure_ascii=False)
|
|
241
|
+
|
|
242
|
+
if content and "\n" in content:
|
|
243
|
+
return json.dumps(header, ensure_ascii=False) + "\ncontent:\n" + content
|
|
244
|
+
|
|
245
|
+
if content != "":
|
|
246
|
+
header["content"] = content if content is not None else ""
|
|
247
|
+
|
|
248
|
+
return json.dumps(header, ensure_ascii=False)
|
|
249
|
+
except Exception:
|
|
250
|
+
return json.dumps(payload, ensure_ascii=False)
|
|
251
|
+
|
|
252
|
+
def _split_json_stream(self, text: str) -> Tuple[List[str], str]:
|
|
253
|
+
"""
|
|
254
|
+
Split a stream of concatenated JSON objects based on top-level brace balance.
|
|
255
|
+
Returns (complete_objects, remainder).
|
|
256
|
+
"""
|
|
257
|
+
objs: List[str] = []
|
|
258
|
+
buf: List[str] = []
|
|
259
|
+
depth = 0
|
|
260
|
+
in_str = False
|
|
261
|
+
esc = False
|
|
262
|
+
started = False
|
|
263
|
+
|
|
264
|
+
for ch in text:
|
|
265
|
+
if in_str:
|
|
266
|
+
buf.append(ch)
|
|
267
|
+
if esc:
|
|
268
|
+
esc = False
|
|
269
|
+
elif ch == "\\":
|
|
270
|
+
esc = True
|
|
271
|
+
elif ch == '"':
|
|
272
|
+
in_str = False
|
|
273
|
+
continue
|
|
274
|
+
|
|
275
|
+
if ch == '"':
|
|
276
|
+
in_str = True
|
|
277
|
+
buf.append(ch)
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
if ch == "{":
|
|
281
|
+
depth += 1
|
|
282
|
+
started = True
|
|
283
|
+
buf.append(ch)
|
|
284
|
+
continue
|
|
285
|
+
|
|
286
|
+
if ch == "}":
|
|
287
|
+
depth -= 1
|
|
288
|
+
buf.append(ch)
|
|
289
|
+
if started and depth == 0:
|
|
290
|
+
candidate = "".join(buf).strip().strip("'\"")
|
|
291
|
+
if candidate:
|
|
292
|
+
objs.append(candidate)
|
|
293
|
+
buf = []
|
|
294
|
+
started = False
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
if started:
|
|
298
|
+
buf.append(ch)
|
|
299
|
+
else:
|
|
300
|
+
# Treat delimiter separators (e.g., ASCII 0x7f) as whitespace
|
|
301
|
+
if ch == "\x7f":
|
|
302
|
+
continue
|
|
303
|
+
buf.append(ch)
|
|
304
|
+
|
|
305
|
+
remainder = "".join(buf) if buf else ""
|
|
306
|
+
return objs, remainder
|
|
307
|
+
|
|
308
|
+
def read_prompt_file(self, file_path: str) -> str:
|
|
309
|
+
"""Read prompt content from a file."""
|
|
310
|
+
try:
|
|
311
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
312
|
+
return f.read().strip()
|
|
313
|
+
except FileNotFoundError:
|
|
314
|
+
print(f"Error: Prompt file not found: {file_path}", file=sys.stderr)
|
|
315
|
+
sys.exit(1)
|
|
316
|
+
except Exception as e:
|
|
317
|
+
print(f"Error reading prompt file: {e}", file=sys.stderr)
|
|
318
|
+
sys.exit(1)
|
|
319
|
+
|
|
320
|
+
def build_gemini_command(self, args: argparse.Namespace) -> List[str]:
|
|
321
|
+
"""Construct the Gemini CLI command for headless execution."""
|
|
322
|
+
cmd = ["gemini"]
|
|
323
|
+
|
|
324
|
+
if self.prompt:
|
|
325
|
+
cmd.extend(["--prompt", self.prompt])
|
|
326
|
+
|
|
327
|
+
cmd.extend(["--output-format", self.output_format])
|
|
328
|
+
cmd.extend(["--model", self.model_name])
|
|
329
|
+
|
|
330
|
+
include_dirs = []
|
|
331
|
+
if args.include_directories:
|
|
332
|
+
include_dirs = [part.strip() for part in args.include_directories.split(",") if part.strip()]
|
|
333
|
+
self.include_dirs = include_dirs
|
|
334
|
+
if include_dirs:
|
|
335
|
+
cmd.extend(["--include-directories", ",".join(include_dirs)])
|
|
336
|
+
|
|
337
|
+
if args.approval_mode:
|
|
338
|
+
cmd.extend(["--approval-mode", args.approval_mode])
|
|
339
|
+
else:
|
|
340
|
+
# Default to yolo for headless automation when approval mode is not provided
|
|
341
|
+
cmd.append("--yolo")
|
|
342
|
+
|
|
343
|
+
if args.yolo:
|
|
344
|
+
if "--yolo" not in cmd:
|
|
345
|
+
cmd.append("--yolo")
|
|
346
|
+
|
|
347
|
+
if args.debug:
|
|
348
|
+
cmd.append("--debug")
|
|
349
|
+
|
|
350
|
+
return cmd
|
|
351
|
+
|
|
352
|
+
def run_gemini(self, cmd: List[str], verbose: bool = False) -> int:
|
|
353
|
+
"""Execute the Gemini CLI and normalize streaming output for shell-backend consumption."""
|
|
354
|
+
if verbose:
|
|
355
|
+
print(f"Executing: {' '.join(cmd)}", file=sys.stderr)
|
|
356
|
+
print("-" * 80, file=sys.stderr)
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
process = subprocess.Popen(
|
|
360
|
+
cmd,
|
|
361
|
+
stdout=subprocess.PIPE,
|
|
362
|
+
stderr=subprocess.PIPE,
|
|
363
|
+
text=True,
|
|
364
|
+
bufsize=1,
|
|
365
|
+
universal_newlines=True,
|
|
366
|
+
cwd=self.project_path,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
pending = ""
|
|
370
|
+
|
|
371
|
+
if process.stdout:
|
|
372
|
+
for raw_line in process.stdout:
|
|
373
|
+
combined = (pending + raw_line).replace("\x7f", "\n")
|
|
374
|
+
pending = ""
|
|
375
|
+
|
|
376
|
+
if not combined.strip():
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
parts, pending = self._split_json_stream(combined)
|
|
380
|
+
|
|
381
|
+
if parts:
|
|
382
|
+
for part in parts:
|
|
383
|
+
try:
|
|
384
|
+
parsed = json.loads(part)
|
|
385
|
+
formatted = self._format_event_pretty(parsed)
|
|
386
|
+
print(formatted, flush=True)
|
|
387
|
+
except Exception:
|
|
388
|
+
print(part, flush=True)
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
# Fallback for non-JSON lines or partial content
|
|
392
|
+
if pending:
|
|
393
|
+
continue
|
|
394
|
+
|
|
395
|
+
print(combined, end="" if combined.endswith("\n") else "\n", flush=True)
|
|
396
|
+
|
|
397
|
+
if pending.strip():
|
|
398
|
+
try:
|
|
399
|
+
parsed_tail = json.loads(pending)
|
|
400
|
+
print(self._format_event_pretty(parsed_tail), flush=True)
|
|
401
|
+
except Exception:
|
|
402
|
+
print(pending, flush=True)
|
|
403
|
+
|
|
404
|
+
process.wait()
|
|
405
|
+
|
|
406
|
+
if process.stderr and process.returncode != 0:
|
|
407
|
+
stderr_output = process.stderr.read()
|
|
408
|
+
if stderr_output:
|
|
409
|
+
print(stderr_output, file=sys.stderr)
|
|
410
|
+
|
|
411
|
+
return process.returncode
|
|
412
|
+
|
|
413
|
+
except KeyboardInterrupt:
|
|
414
|
+
print("\nInterrupted by user", file=sys.stderr)
|
|
415
|
+
try:
|
|
416
|
+
process.terminate()
|
|
417
|
+
process.wait()
|
|
418
|
+
except Exception:
|
|
419
|
+
pass
|
|
420
|
+
return 130
|
|
421
|
+
except Exception as e:
|
|
422
|
+
print(f"Error executing gemini: {e}", file=sys.stderr)
|
|
423
|
+
return 1
|
|
424
|
+
|
|
425
|
+
def run(self) -> int:
|
|
426
|
+
"""Main execution flow."""
|
|
427
|
+
args = self.parse_arguments()
|
|
428
|
+
|
|
429
|
+
# Prompt handling: allow env override for shell backend compatibility
|
|
430
|
+
prompt_value = args.prompt or os.environ.get("JUNO_INSTRUCTION")
|
|
431
|
+
if not prompt_value and not args.prompt_file:
|
|
432
|
+
print("Error: Either -p/--prompt or -pp/--prompt-file is required.", file=sys.stderr)
|
|
433
|
+
print("\nRun 'gemini.py --help' for usage information.", file=sys.stderr)
|
|
434
|
+
return 1
|
|
435
|
+
|
|
436
|
+
if not self.check_gemini_installed():
|
|
437
|
+
print("Error: Gemini CLI is not available. Please install it: https://geminicli.com/docs/get-started/installation/", file=sys.stderr)
|
|
438
|
+
return 1
|
|
439
|
+
|
|
440
|
+
if not self.ensure_api_key_present():
|
|
441
|
+
return 1
|
|
442
|
+
|
|
443
|
+
self.project_path = os.path.abspath(args.cd)
|
|
444
|
+
if not os.path.isdir(self.project_path):
|
|
445
|
+
print(f"Error: Project path does not exist: {self.project_path}", file=sys.stderr)
|
|
446
|
+
return 1
|
|
447
|
+
|
|
448
|
+
self.model_name = self.expand_model_shorthand(args.model)
|
|
449
|
+
self.output_format = args.output_format or self.DEFAULT_OUTPUT_FORMAT
|
|
450
|
+
if self.output_format not in self.VALID_OUTPUT_FORMATS:
|
|
451
|
+
print(f"Warning: Unsupported output format '{self.output_format}'. Falling back to {self.DEFAULT_OUTPUT_FORMAT}.", file=sys.stderr)
|
|
452
|
+
self.output_format = self.DEFAULT_OUTPUT_FORMAT
|
|
453
|
+
self.debug = args.debug
|
|
454
|
+
self.verbose = args.verbose
|
|
455
|
+
self.approval_mode = args.approval_mode
|
|
456
|
+
self.yolo = args.yolo
|
|
457
|
+
|
|
458
|
+
if args.prompt_file:
|
|
459
|
+
self.prompt = self.read_prompt_file(args.prompt_file)
|
|
460
|
+
else:
|
|
461
|
+
self.prompt = prompt_value
|
|
462
|
+
|
|
463
|
+
cmd = self.build_gemini_command(args)
|
|
464
|
+
return self.run_gemini(cmd, verbose=args.verbose)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def main():
|
|
468
|
+
service = GeminiService()
|
|
469
|
+
sys.exit(service.run())
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
if __name__ == "__main__":
|
|
473
|
+
main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "juno-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "TypeScript CLI tool for AI subagent orchestration with code automation",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"scripts": {
|
|
37
37
|
"dev": "tsx src/bin/cli.ts",
|
|
38
38
|
"build": "tsup && npm run build:copy-templates && npm run build:copy-services && npm run build:copy-wrapper",
|
|
39
|
-
"build:copy-templates": "node -e \"require('fs-extra').copySync('src/templates/scripts', 'dist/templates/scripts', { recursive: true })
|
|
40
|
-
"build:copy-services": "node -e \"const fs = require('fs-extra');
|
|
39
|
+
"build:copy-templates": "node -e \"const fs = require('fs-extra'); const path = require('path'); fs.copySync('src/templates/scripts', 'dist/templates/scripts', { recursive: true }); fs.readdirSync('dist/templates/scripts').filter(f => f.endsWith('.sh')).forEach(f => fs.chmodSync(path.join('dist/templates/scripts', f), 0o755));\"",
|
|
40
|
+
"build:copy-services": "node -e \"const fs = require('fs-extra'); const path = require('path'); const src = 'src/templates/services'; const dest = 'dist/templates/services'; fs.copySync(src, dest, { recursive: true }); const required = ['codex.py','claude.py','gemini.py']; const missing = required.filter(file => !fs.existsSync(path.join(dest, file))); if (missing.length) { throw new Error('Missing required service scripts in dist: ' + missing.join(', ')); } required.forEach(file => fs.chmodSync(path.join(dest, file), 0o755));\"",
|
|
41
41
|
"build:copy-wrapper": "node -e \"const fs = require('fs-extra'); fs.copySync('src/bin/juno-code.sh', 'dist/bin/juno-code.sh'); fs.chmodSync('dist/bin/juno-code.sh', 0o755);\"",
|
|
42
42
|
"build:watch": "tsup --watch",
|
|
43
43
|
"test": "vitest",
|