juno-code 1.0.46 → 1.0.49
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 +44 -8
- package/dist/bin/cli.d.mts +17 -0
- package/dist/bin/cli.d.ts +17 -0
- package/dist/bin/cli.js +5601 -17505
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/cli.mjs +5640 -17542
- 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 +2107 -0
- package/dist/index.d.ts +2107 -0
- package/dist/index.js +3760 -14728
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3761 -14536
- 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/kanban.sh +18 -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 +1753 -0
- package/dist/templates/skills/claude/plan-kanban-tasks/SKILL.md +14 -7
- 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 +18 -4
- package/dist/templates/skills/claude/understand-project/SKILL.md +14 -7
- 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 +18 -4
- package/dist/templates/skills/pi/.gitkeep +0 -0
- package/dist/templates/skills/pi/plan-kanban-tasks/SKILL.md +32 -0
- package/dist/templates/skills/pi/ralph-loop/SKILL.md +39 -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 +20 -42
- 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
|
|
@@ -170,8 +170,22 @@ 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"
|
|
@@ -192,7 +206,7 @@ normalize_arguments() {
|
|
|
192
206
|
local found_command=false
|
|
193
207
|
|
|
194
208
|
# Known subcommands
|
|
195
|
-
local commands="create search get show update archive mark list merge"
|
|
209
|
+
local commands="create search get show update archive mark list merge ready deps order"
|
|
196
210
|
|
|
197
211
|
while [[ $# -gt 0 ]]; do
|
|
198
212
|
case $1 in
|
|
@@ -207,7 +221,7 @@ normalize_arguments() {
|
|
|
207
221
|
fi
|
|
208
222
|
;;
|
|
209
223
|
# Global flags that don't take a value
|
|
210
|
-
-p|--pretty|--raw|-v|--verbose
|
|
224
|
+
-p|--pretty|--raw|-v|--verbose|--version)
|
|
211
225
|
NORMALIZED_GLOBAL_FLAGS+=("$1")
|
|
212
226
|
shift
|
|
213
227
|
;;
|