juno-code 1.0.47 → 1.0.50
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 +455 -205
- package/dist/bin/cli.d.mts +17 -0
- package/dist/bin/cli.d.ts +17 -0
- package/dist/bin/cli.js +6456 -17604
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +6443 -17589
- package/dist/bin/cli.mjs.map +1 -1
- package/dist/bin/feedback-collector.d.mts +2 -0
- package/dist/bin/feedback-collector.d.ts +2 -0
- package/dist/bin/feedback-collector.js.map +1 -1
- package/dist/bin/feedback-collector.mjs.map +1 -1
- package/dist/index.d.mts +2133 -0
- package/dist/index.d.ts +2133 -0
- package/dist/index.js +3916 -14711
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3914 -14516
- package/dist/index.mjs.map +1 -1
- package/dist/templates/extensions/pi/juno-skill-preprocessor.ts +239 -0
- package/dist/templates/scripts/__pycache__/github.cpython-313.pyc +0 -0
- package/dist/templates/scripts/__pycache__/parallel_runner.cpython-313.pyc +0 -0
- package/dist/templates/scripts/__pycache__/slack_respond.cpython-313.pyc +0 -0
- package/dist/templates/scripts/install_requirements.sh +41 -3
- package/dist/templates/scripts/kanban.sh +22 -4
- package/dist/templates/scripts/parallel_runner.sh +2242 -0
- package/dist/templates/services/README.md +61 -1
- package/dist/templates/services/__pycache__/claude.cpython-313.pyc +0 -0
- package/dist/templates/services/__pycache__/codex.cpython-313.pyc +0 -0
- package/dist/templates/services/__pycache__/pi.cpython-313.pyc +0 -0
- package/dist/templates/services/claude.py +132 -33
- package/dist/templates/services/codex.py +179 -66
- package/dist/templates/services/gemini.py +117 -27
- package/dist/templates/services/pi.py +2796 -0
- package/dist/templates/skills/claude/kanban-workflow/SKILL.md +138 -0
- package/dist/templates/skills/claude/plan-kanban-tasks/SKILL.md +15 -8
- package/dist/templates/skills/claude/ralph-loop/SKILL.md +18 -22
- package/dist/templates/skills/claude/ralph-loop/references/first_check.md +15 -14
- package/dist/templates/skills/claude/ralph-loop/references/implement.md +17 -17
- package/dist/templates/skills/claude/ralph-loop/scripts/kanban.sh +22 -4
- package/dist/templates/skills/claude/understand-project/SKILL.md +15 -8
- package/dist/templates/skills/codex/kanban-workflow/SKILL.md +139 -0
- package/dist/templates/skills/codex/plan-kanban-tasks/SKILL.md +32 -0
- package/dist/templates/skills/codex/ralph-loop/SKILL.md +18 -22
- package/dist/templates/skills/codex/ralph-loop/references/first_check.md +15 -14
- package/dist/templates/skills/codex/ralph-loop/references/implement.md +17 -17
- package/dist/templates/skills/codex/ralph-loop/scripts/kanban.sh +22 -4
- package/dist/templates/skills/codex/understand-project/SKILL.md +46 -0
- package/dist/templates/skills/pi/.gitkeep +0 -0
- package/dist/templates/skills/pi/kanban-workflow/SKILL.md +139 -0
- package/dist/templates/skills/pi/plan-kanban-tasks/SKILL.md +32 -0
- package/dist/templates/skills/pi/ralph-loop/SKILL.md +43 -0
- package/dist/templates/skills/pi/ralph-loop/references/first_check.md +21 -0
- package/dist/templates/skills/pi/ralph-loop/references/implement.md +99 -0
- package/dist/templates/skills/pi/understand-project/SKILL.md +46 -0
- package/package.json +26 -46
- package/dist/templates/scripts/__pycache__/attachment_downloader.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/github.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/slack_fetch.cpython-38.pyc +0 -0
- package/dist/templates/scripts/__pycache__/slack_state.cpython-38.pyc +0 -0
- package/dist/templates/services/__pycache__/claude.cpython-38.pyc +0 -0
- package/dist/templates/services/__pycache__/codex.cpython-38.pyc +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Juno Skill Preprocessor — Pi Extension
|
|
3
|
+
*
|
|
4
|
+
* Adds variable substitution ($1, $2, $ARGUMENTS, $@, ${@:N}, ${@:N:L})
|
|
5
|
+
* and shell directive execution (!`command`) to Pi skill invocations.
|
|
6
|
+
*
|
|
7
|
+
* This extension intercepts /skill: commands via the "input" event,
|
|
8
|
+
* BEFORE Pi's internal _expandSkillCommand() runs. It reads the skill
|
|
9
|
+
* file, processes variables and shell directives, wraps the result in
|
|
10
|
+
* <skill> tags, and returns the fully expanded text.
|
|
11
|
+
*
|
|
12
|
+
* Shell directives only execute when the skill's frontmatter contains:
|
|
13
|
+
* enable-shell-directives: true
|
|
14
|
+
*
|
|
15
|
+
* Variable substitution always runs when arguments are provided.
|
|
16
|
+
*
|
|
17
|
+
* Shipped via juno-code's SkillInstaller to .pi/extensions/.
|
|
18
|
+
*/
|
|
19
|
+
import type { ExtensionAPI, InputEvent } from "@mariozechner/pi-coding-agent";
|
|
20
|
+
import { execSync } from "child_process";
|
|
21
|
+
import { existsSync, readFileSync } from "fs";
|
|
22
|
+
import { dirname, join } from "path";
|
|
23
|
+
|
|
24
|
+
const SHELL_DIRECTIVE_REGEX = /!`([^`]+)`/g;
|
|
25
|
+
const DEFAULT_SHELL_TIMEOUT = 5000;
|
|
26
|
+
|
|
27
|
+
/** Directories to search for skill files, relative to project root. */
|
|
28
|
+
const SKILL_DIRS = [".pi/skills", ".claude/skills"];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Find a skill's SKILL.md file by name, searching known skill directories.
|
|
32
|
+
* Checks both {dir}/{name}/SKILL.md and {dir}/{name}.md patterns.
|
|
33
|
+
*/
|
|
34
|
+
function findSkillFile(skillName: string, cwd: string): string | null {
|
|
35
|
+
for (const dir of SKILL_DIRS) {
|
|
36
|
+
const candidates = [join(cwd, dir, skillName, "SKILL.md"), join(cwd, dir, `${skillName}.md`)];
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
if (existsSync(candidate)) return candidate;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse YAML-like frontmatter from a skill file.
|
|
46
|
+
* Returns frontmatter key-value pairs and the body text after the frontmatter block.
|
|
47
|
+
*/
|
|
48
|
+
function parseFrontmatter(content: string): { frontmatter: Record<string, string | boolean>; body: string } {
|
|
49
|
+
const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/);
|
|
50
|
+
if (!match) return { frontmatter: {}, body: content };
|
|
51
|
+
|
|
52
|
+
const yaml = match[1];
|
|
53
|
+
const body = match[2] ?? '';
|
|
54
|
+
const frontmatter: Record<string, string | boolean> = {};
|
|
55
|
+
|
|
56
|
+
for (const rawLine of yaml!.split("\n")) {
|
|
57
|
+
const colonIndex = rawLine.indexOf(":");
|
|
58
|
+
if (colonIndex === -1) continue;
|
|
59
|
+
const key = rawLine.slice(0, colonIndex).trim();
|
|
60
|
+
const value = rawLine.slice(colonIndex + 1).trim();
|
|
61
|
+
if (value === "true") {
|
|
62
|
+
frontmatter[key] = true;
|
|
63
|
+
} else if (value === "false") {
|
|
64
|
+
frontmatter[key] = false;
|
|
65
|
+
} else {
|
|
66
|
+
frontmatter[key] = value;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { frontmatter, body };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse command arguments respecting single and double quotes.
|
|
75
|
+
* Handles escape characters with backslash.
|
|
76
|
+
*
|
|
77
|
+
* Examples:
|
|
78
|
+
* 'hello world' → ["hello", "world"]
|
|
79
|
+
* '"hello world"' → ["hello world"]
|
|
80
|
+
* "'hello' \"world\"" → ["hello", "world"]
|
|
81
|
+
*/
|
|
82
|
+
function parseCommandArgs(input: string): string[] {
|
|
83
|
+
if (!input.trim()) return [];
|
|
84
|
+
|
|
85
|
+
const args: string[] = [];
|
|
86
|
+
let current = "";
|
|
87
|
+
let inSingle = false;
|
|
88
|
+
let inDouble = false;
|
|
89
|
+
let escape = false;
|
|
90
|
+
|
|
91
|
+
for (const char of input) {
|
|
92
|
+
if (escape) {
|
|
93
|
+
current += char;
|
|
94
|
+
escape = false;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (char === "\\") {
|
|
98
|
+
escape = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === '"' && !inSingle) {
|
|
102
|
+
inDouble = !inDouble;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (char === "'" && !inDouble) {
|
|
106
|
+
inSingle = !inSingle;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (char === " " && !inSingle && !inDouble) {
|
|
110
|
+
if (current) {
|
|
111
|
+
args.push(current);
|
|
112
|
+
current = "";
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
current += char;
|
|
117
|
+
}
|
|
118
|
+
if (current) args.push(current);
|
|
119
|
+
return args;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Substitute argument placeholders in content.
|
|
124
|
+
*
|
|
125
|
+
* Supports (1-indexed, aligned with bash and Pi's prompt-templates):
|
|
126
|
+
* $1, $2, ... — positional arguments
|
|
127
|
+
* $@ — all arguments joined by space
|
|
128
|
+
* $ARGUMENTS — all arguments joined by space (alias)
|
|
129
|
+
* ${@:N} — arguments from Nth position onwards
|
|
130
|
+
* ${@:N:L} — L arguments starting from position N
|
|
131
|
+
*/
|
|
132
|
+
function substituteArgs(content: string, args: string[]): string {
|
|
133
|
+
let result = content;
|
|
134
|
+
|
|
135
|
+
// Replace $1, $2, etc. FIRST (before wildcards to prevent re-substitution)
|
|
136
|
+
result = result.replace(/\$(\d+)/g, (_, num) => {
|
|
137
|
+
const index = parseInt(num, 10) - 1;
|
|
138
|
+
return args[index] ?? "";
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Replace ${@:start} or ${@:start:length} (bash-style, 1-indexed)
|
|
142
|
+
result = result.replace(/\$\{@:(\d+)(?::(\d+))?\}/g, (_, startStr, lengthStr) => {
|
|
143
|
+
let start = parseInt(startStr, 10) - 1;
|
|
144
|
+
if (start < 0) start = 0;
|
|
145
|
+
if (lengthStr) {
|
|
146
|
+
const length = parseInt(lengthStr, 10);
|
|
147
|
+
return args.slice(start, start + length).join(" ");
|
|
148
|
+
}
|
|
149
|
+
return args.slice(start).join(" ");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const allArgs = args.join(" ");
|
|
153
|
+
result = result.replace(/\$ARGUMENTS/g, allArgs);
|
|
154
|
+
result = result.replace(/\$@/g, allArgs);
|
|
155
|
+
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Process shell directives (!`command`) by executing them and inlining stdout.
|
|
161
|
+
* On error: replaces with [Error executing: command].
|
|
162
|
+
* Timeout: DEFAULT_SHELL_TIMEOUT ms (configurable).
|
|
163
|
+
*/
|
|
164
|
+
function processShellDirectives(content: string, cwd: string, timeout: number = DEFAULT_SHELL_TIMEOUT): string {
|
|
165
|
+
return content.replace(SHELL_DIRECTIVE_REGEX, (_, command: string) => {
|
|
166
|
+
try {
|
|
167
|
+
return execSync(command, {
|
|
168
|
+
cwd,
|
|
169
|
+
timeout,
|
|
170
|
+
encoding: "utf-8",
|
|
171
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
172
|
+
}).trim();
|
|
173
|
+
} catch {
|
|
174
|
+
return `[Error executing: ${command}]`;
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Juno Skill Preprocessor Extension
|
|
181
|
+
*
|
|
182
|
+
* Intercepts /skill: commands via the "input" event (before Pi's internal
|
|
183
|
+
* _expandSkillCommand runs), applies variable substitution and shell
|
|
184
|
+
* directive processing, then returns the fully expanded skill block.
|
|
185
|
+
*/
|
|
186
|
+
export default function junoSkillPreprocessor(pi: ExtensionAPI) {
|
|
187
|
+
pi.on("input", (event: InputEvent) => {
|
|
188
|
+
const text = typeof event.text === "string" ? event.text : "";
|
|
189
|
+
if (!text.startsWith("/skill:")) return { action: "continue" };
|
|
190
|
+
|
|
191
|
+
const cwd = process.cwd();
|
|
192
|
+
|
|
193
|
+
// Parse skill name and arguments from the command
|
|
194
|
+
const spaceIndex = text.indexOf(" ");
|
|
195
|
+
const skillName = spaceIndex === -1 ? text.slice(7) : text.slice(7, spaceIndex);
|
|
196
|
+
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
197
|
+
const args = parseCommandArgs(argsString);
|
|
198
|
+
|
|
199
|
+
// Find the skill file on disk
|
|
200
|
+
const skillPath = findSkillFile(skillName, cwd);
|
|
201
|
+
if (!skillPath) return { action: "continue" }; // Unknown skill — let Pi handle it
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const content = readFileSync(skillPath, "utf-8");
|
|
205
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
206
|
+
let processedBody = body.trim();
|
|
207
|
+
|
|
208
|
+
// Substitute variable placeholders with provided arguments
|
|
209
|
+
if (args.length > 0) {
|
|
210
|
+
processedBody = substituteArgs(processedBody, args);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Execute shell directives (only when explicitly opted in via frontmatter)
|
|
214
|
+
if (frontmatter["enable-shell-directives"] === true) {
|
|
215
|
+
processedBody = processShellDirectives(processedBody, cwd);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Build the <skill> block (matches Pi's _expandSkillCommand format)
|
|
219
|
+
const baseDir = dirname(skillPath);
|
|
220
|
+
const skillBlock = [
|
|
221
|
+
`<skill name="${skillName}" location="${skillPath}">`,
|
|
222
|
+
`References are relative to ${baseDir}.`,
|
|
223
|
+
"",
|
|
224
|
+
processedBody,
|
|
225
|
+
"</skill>",
|
|
226
|
+
].join("\n");
|
|
227
|
+
|
|
228
|
+
// Return transformed text — Pi's _expandSkillCommand will see this
|
|
229
|
+
// doesn't start with /skill: and pass it through unchanged.
|
|
230
|
+
return { action: "transform", text: skillBlock };
|
|
231
|
+
} catch {
|
|
232
|
+
// On error, let Pi's native expansion handle it
|
|
233
|
+
return { action: "continue" };
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Export internals for testing
|
|
239
|
+
export { findSkillFile, parseCommandArgs, parseFrontmatter, processShellDirectives, substituteArgs };
|
|
Binary file
|
|
Binary file
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# - If inside venv: installs into venv
|
|
14
14
|
# - If externally managed Python detected: uses pipx or creates temporary venv
|
|
15
15
|
# - If outside venv (non-managed): uses --system flag for system-wide installation
|
|
16
|
-
# 6. Installs required packages: juno-kanban
|
|
16
|
+
# 6. Installs required packages: juno-kanban
|
|
17
17
|
# 7. Reports if requirements are already satisfied
|
|
18
18
|
#
|
|
19
19
|
# Usage: ./install_requirements.sh
|
|
@@ -54,13 +54,13 @@ NC='\033[0m' # No Color
|
|
|
54
54
|
# Required packages
|
|
55
55
|
# Note: requests and python-dotenv are required by github.py
|
|
56
56
|
# slack_sdk is required by Slack integration scripts (slack_fetch.py, slack_respond.py)
|
|
57
|
-
REQUIRED_PACKAGES=("juno-kanban" "
|
|
57
|
+
REQUIRED_PACKAGES=("juno-kanban" "requests" "python-dotenv" "slack_sdk")
|
|
58
58
|
|
|
59
59
|
# Version check cache configuration
|
|
60
60
|
# This ensures we don't check PyPI on every run (performance optimization per Task RTafs5)
|
|
61
61
|
VERSION_CHECK_CACHE_DIR="${HOME}/.juno_code"
|
|
62
62
|
VERSION_CHECK_CACHE_FILE="${VERSION_CHECK_CACHE_DIR}/.version_check_cache"
|
|
63
|
-
VERSION_CHECK_INTERVAL_HOURS=24 # Check for updates once per day
|
|
63
|
+
VERSION_CHECK_INTERVAL_HOURS="${VERSION_CHECK_INTERVAL_HOURS:-24}" # Check for updates once per day (override via env var)
|
|
64
64
|
|
|
65
65
|
# Logging functions
|
|
66
66
|
log_info() {
|
|
@@ -402,6 +402,38 @@ is_externally_managed_python() {
|
|
|
402
402
|
return 1 # Not externally managed
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
# Function to upgrade pip to latest version inside the active venv
|
|
406
|
+
# Why: venv ships with the pip version bundled in the Python distribution,
|
|
407
|
+
# which can be months/years behind. Old pip may fail to resolve modern
|
|
408
|
+
# dependency metadata or miss security fixes. Upgrading pip is fast (<2s)
|
|
409
|
+
# and prevents hard-to-debug install failures downstream.
|
|
410
|
+
upgrade_pip_in_venv() {
|
|
411
|
+
if ! is_in_virtualenv; then
|
|
412
|
+
return 0 # Only upgrade pip inside a venv
|
|
413
|
+
fi
|
|
414
|
+
|
|
415
|
+
log_info "Upgrading pip to latest version in venv..."
|
|
416
|
+
|
|
417
|
+
# Prefer uv for speed, fall back to pip itself
|
|
418
|
+
if command -v uv &>/dev/null; then
|
|
419
|
+
if uv pip install --upgrade pip --quiet 2>/dev/null; then
|
|
420
|
+
local pip_ver
|
|
421
|
+
pip_ver=$(python3 -m pip --version 2>/dev/null | awk '{print $2}' || echo "unknown")
|
|
422
|
+
log_success "pip upgraded to v$pip_ver (via uv)"
|
|
423
|
+
return 0
|
|
424
|
+
fi
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
# Fall back to pip self-upgrade
|
|
428
|
+
if python3 -m pip install --upgrade pip --quiet 2>/dev/null; then
|
|
429
|
+
local pip_ver
|
|
430
|
+
pip_ver=$(python3 -m pip --version 2>/dev/null | awk '{print $2}' || echo "unknown")
|
|
431
|
+
log_success "pip upgraded to v$pip_ver"
|
|
432
|
+
else
|
|
433
|
+
log_warning "Could not upgrade pip (non-fatal, continuing with current version)"
|
|
434
|
+
fi
|
|
435
|
+
}
|
|
436
|
+
|
|
405
437
|
# Function to install packages using pipx
|
|
406
438
|
install_with_pipx() {
|
|
407
439
|
log_info "Installing packages using 'pipx' (recommended for Python applications)..."
|
|
@@ -492,6 +524,9 @@ install_with_uv() {
|
|
|
492
524
|
log_error "Virtual environment activation script not found"
|
|
493
525
|
return 1
|
|
494
526
|
fi
|
|
527
|
+
|
|
528
|
+
# Upgrade pip to latest version in venv
|
|
529
|
+
upgrade_pip_in_venv
|
|
495
530
|
fi
|
|
496
531
|
|
|
497
532
|
local failed_packages=()
|
|
@@ -562,6 +597,9 @@ install_with_pip() {
|
|
|
562
597
|
source "$venv_path/bin/activate"
|
|
563
598
|
log_success "Activated virtual environment"
|
|
564
599
|
python_cmd="python" # Use the venv's python
|
|
600
|
+
|
|
601
|
+
# Upgrade pip to latest version in venv
|
|
602
|
+
upgrade_pip_in_venv
|
|
565
603
|
fi
|
|
566
604
|
|
|
567
605
|
local failed_packages=()
|
|
@@ -170,12 +170,30 @@ ensure_python_environment() {
|
|
|
170
170
|
# Get the directory where this script is located
|
|
171
171
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
172
172
|
|
|
173
|
-
#
|
|
174
|
-
|
|
173
|
+
# Auto-discover project root by walking up the directory tree looking for .juno_task/
|
|
174
|
+
# This makes kanban.sh work from any installation depth (e.g. .juno_task/scripts/,
|
|
175
|
+
# .claude/skills/ralph-loop/scripts/, etc.) without a hardcoded relative path.
|
|
176
|
+
PROJECT_ROOT=""
|
|
177
|
+
_dir="$SCRIPT_DIR"
|
|
178
|
+
while [[ "$_dir" != "/" ]]; do
|
|
179
|
+
if [[ -d "$_dir/.juno_task" ]]; then
|
|
180
|
+
PROJECT_ROOT="$_dir"
|
|
181
|
+
break
|
|
182
|
+
fi
|
|
183
|
+
_dir="$( cd "$_dir/.." && pwd )"
|
|
184
|
+
done
|
|
185
|
+
if [[ -z "$PROJECT_ROOT" ]]; then
|
|
186
|
+
echo "ERROR: Could not find project root (no .juno_task/ directory found above $SCRIPT_DIR)" >&2
|
|
187
|
+
exit 1
|
|
188
|
+
fi
|
|
175
189
|
|
|
176
190
|
# Change to project root
|
|
177
191
|
cd "$PROJECT_ROOT"
|
|
178
192
|
|
|
193
|
+
# Export JUNO_TASK_ROOT so juno-kanban resolves .juno_task paths from project root,
|
|
194
|
+
# not from wherever the calling agent happens to be. Respects existing override.
|
|
195
|
+
export JUNO_TASK_ROOT="${JUNO_TASK_ROOT:-$PROJECT_ROOT}"
|
|
196
|
+
|
|
179
197
|
# Arrays to store normalized arguments (declared at script level for proper handling)
|
|
180
198
|
declare -a NORMALIZED_GLOBAL_FLAGS=()
|
|
181
199
|
declare -a NORMALIZED_COMMAND_ARGS=()
|
|
@@ -192,7 +210,7 @@ normalize_arguments() {
|
|
|
192
210
|
local found_command=false
|
|
193
211
|
|
|
194
212
|
# Known subcommands
|
|
195
|
-
local commands="create search get show update archive mark list merge"
|
|
213
|
+
local commands="create search get show update archive mark list merge ready deps order"
|
|
196
214
|
|
|
197
215
|
while [[ $# -gt 0 ]]; do
|
|
198
216
|
case $1 in
|
|
@@ -207,7 +225,7 @@ normalize_arguments() {
|
|
|
207
225
|
fi
|
|
208
226
|
;;
|
|
209
227
|
# Global flags that don't take a value
|
|
210
|
-
-p|--pretty|--raw|-v|--verbose
|
|
228
|
+
-p|--pretty|--raw|-v|--verbose|--version)
|
|
211
229
|
NORMALIZED_GLOBAL_FLAGS+=("$1")
|
|
212
230
|
shift
|
|
213
231
|
;;
|