pi-laya-mlx 0.1.0
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 +154 -0
- package/bin/laya.mjs +362 -0
- package/daemon/anchors.json +111 -0
- package/daemon/requirements.txt +4 -0
- package/daemon/server.py +239 -0
- package/daemon/start.sh +67 -0
- package/extensions/laya-router.ts +440 -0
- package/package.json +43 -0
- package/skills/laya-tune/SKILL.md +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# pi-laya-mlx ⚡
|
|
2
|
+
|
|
3
|
+
> **Fast, local semantic router & model optimizer for Pi coding agent using Apple Silicon MLX.**
|
|
4
|
+
|
|
5
|
+
`pi-laya-mlx` classifies developer prompt intent locally in **sub-15ms** using dense multilingual sentence embeddings, then dynamically routes each prompt to the optimal model and thinking tier (e.g., lightweight models for simple chat/file exploration, high-reasoning frontier models for deep architecture and hard coding tasks).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 💻 Hardware & Platform Support
|
|
10
|
+
|
|
11
|
+
| Platform | Hardware Target | Acceleration | Expected Latency |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **macOS** | Apple Silicon (M1 / M2 / M3 / M4) | **Metal GPU Native (Zero-Copy)** | **~5–15 ms** |
|
|
14
|
+
| **Linux (x86_64 / ARM64 / Asahi)** | Generic CPU / Apple CPU | **CPU Fallback (`mlx[cpu]`)** | **~40–120 ms** |
|
|
15
|
+
|
|
16
|
+
> ⚠️ **Note on Linux / Asahi Linux:** Apple MLX hardware acceleration depends on Apple's proprietary Metal framework (macOS only). On Linux, it runs in **CPU-only mode**, which is functional but slower than macOS Metal execution.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 📦 Installation
|
|
21
|
+
|
|
22
|
+
### 1. Install as Pi Package
|
|
23
|
+
|
|
24
|
+
Install directly from GitHub or npm:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# From Git repository:
|
|
28
|
+
pi install git:github.com/<your-username>/pi-laya-mlx
|
|
29
|
+
|
|
30
|
+
# Or from npm:
|
|
31
|
+
pi install npm:pi-laya-mlx
|
|
32
|
+
|
|
33
|
+
# Or locally during development:
|
|
34
|
+
pi install ./pi-laya-mlx
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Setup Daemon Environment
|
|
38
|
+
|
|
39
|
+
In the package `daemon` directory (or globally), install the Python dependencies:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd daemon
|
|
43
|
+
|
|
44
|
+
# Recommended: using uv (ultra fast)
|
|
45
|
+
uv venv
|
|
46
|
+
uv pip install -r requirements.txt
|
|
47
|
+
|
|
48
|
+
# Or using standard pip:
|
|
49
|
+
python3 -m venv .venv
|
|
50
|
+
source .venv/bin/activate
|
|
51
|
+
pip install -r requirements.txt
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 🚀 CLI Usage (`laya`)
|
|
57
|
+
|
|
58
|
+
`pi-laya-mlx` includes a convenient CLI binary:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Check daemon health, active model, and anchor counts
|
|
62
|
+
laya status
|
|
63
|
+
|
|
64
|
+
# Start daemon in background (port 4141)
|
|
65
|
+
laya start
|
|
66
|
+
|
|
67
|
+
# Stop the running daemon
|
|
68
|
+
laya stop
|
|
69
|
+
|
|
70
|
+
# Restart the daemon
|
|
71
|
+
laya restart
|
|
72
|
+
|
|
73
|
+
# Hot-reload anchor embeddings after editing anchors.json (<50ms)
|
|
74
|
+
laya reload
|
|
75
|
+
|
|
76
|
+
# View recent prediction logs & confidence scores
|
|
77
|
+
laya logs -n 20
|
|
78
|
+
|
|
79
|
+
# Record a misclassification flag for tuning
|
|
80
|
+
laya flag CODING_STANDARD "tolong implementasikan fungsi ini"
|
|
81
|
+
|
|
82
|
+
# Initialize default ~/.pi/agent/laya-router.json config
|
|
83
|
+
laya init
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ⚡ Pi Extension Commands & Shortcuts
|
|
89
|
+
|
|
90
|
+
Inside your Pi session:
|
|
91
|
+
|
|
92
|
+
- **Toggle router on/off**: `/laya` or press `Ctrl+Shift+J` / `Alt+J`
|
|
93
|
+
- **Check status**: `/laya status`
|
|
94
|
+
- **View prediction logs**: `/laya logs`
|
|
95
|
+
- **Flag misclassification**: `/laya flag [chat|coding|review|explore|arch]`
|
|
96
|
+
- **Tune anchors**: `/skill:laya-tune`
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## ⚙️ Configuration (`~/.pi/agent/laya-router.json`)
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"enabled": true,
|
|
105
|
+
"endpoint": "http://127.0.0.1:4141/predict",
|
|
106
|
+
"threshold": 0.25,
|
|
107
|
+
"routes": {
|
|
108
|
+
"CHAT_OR_TRIVIAL": {
|
|
109
|
+
"provider": "anthropic",
|
|
110
|
+
"model": "claude-haiku-4-5",
|
|
111
|
+
"thinking": "off",
|
|
112
|
+
"description": "General casual chat, short responses, confirmations, trivial queries"
|
|
113
|
+
},
|
|
114
|
+
"RESEARCH_AND_EXPLORE": {
|
|
115
|
+
"provider": "anthropic",
|
|
116
|
+
"model": "claude-haiku-4-5",
|
|
117
|
+
"thinking": "low",
|
|
118
|
+
"description": "File search, code exploration, grep lookups, error investigations"
|
|
119
|
+
},
|
|
120
|
+
"CODING_STANDARD": {
|
|
121
|
+
"provider": "anthropic",
|
|
122
|
+
"model": "claude-sonnet-4-6",
|
|
123
|
+
"thinking": "low",
|
|
124
|
+
"description": "Standard coding tasks, bug fixes, feature implementation, refactoring"
|
|
125
|
+
},
|
|
126
|
+
"CODE_REVIEW": {
|
|
127
|
+
"provider": "anthropic",
|
|
128
|
+
"model": "claude-sonnet-4-6",
|
|
129
|
+
"thinking": "medium",
|
|
130
|
+
"description": "Code reviews, diff analysis, security audits, pre-push checks"
|
|
131
|
+
},
|
|
132
|
+
"HARD_ARCHITECTURE": {
|
|
133
|
+
"provider": "anthropic",
|
|
134
|
+
"model": "claude-opus-4-6",
|
|
135
|
+
"thinking": "high",
|
|
136
|
+
"description": "Complex system architecture, tricky debugging, hard distributed systems"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🎯 Continuous Tuning
|
|
145
|
+
|
|
146
|
+
When Laya misclassifies a prompt:
|
|
147
|
+
1. Run `/laya flag <category>` (or `laya flag` in CLI) to log the prompt.
|
|
148
|
+
2. Run `/skill:laya-tune` in Pi. An isolated background agent will analyze the flagged entries, add new anchor exemplars to `anchors.json`, and hot-reload the daemon instantly via `/reload-anchors` without restarting the server.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 📄 License
|
|
153
|
+
|
|
154
|
+
MIT
|
package/bin/laya.mjs
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn, execSync } from "node:child_process";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
|
+
import * as path from "node:path";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
12
|
+
const DAEMON_DIR = path.join(PKG_ROOT, "daemon");
|
|
13
|
+
|
|
14
|
+
const PORT = process.env.LAYA_PORT || "4141";
|
|
15
|
+
const BASE_URL = `http://127.0.0.1:${PORT}`;
|
|
16
|
+
const USER_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "laya-router.json");
|
|
17
|
+
|
|
18
|
+
const c = {
|
|
19
|
+
reset: "\x1b[0m",
|
|
20
|
+
bold: "\x1b[1m",
|
|
21
|
+
dim: "\x1b[2m",
|
|
22
|
+
green: "\x1b[32m",
|
|
23
|
+
yellow: "\x1b[33m",
|
|
24
|
+
red: "\x1b[31m",
|
|
25
|
+
cyan: "\x1b[36m",
|
|
26
|
+
magenta: "\x1b[35m",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
async function checkHealth(timeoutMs = 1200) {
|
|
30
|
+
try {
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
33
|
+
const res = await fetch(`${BASE_URL}/health`, { signal: controller.signal });
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
if (!res.ok) return { ok: false, status: res.status };
|
|
36
|
+
const data = await res.json();
|
|
37
|
+
return { ok: true, data };
|
|
38
|
+
} catch {
|
|
39
|
+
return { ok: false };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getDaemonPid() {
|
|
44
|
+
try {
|
|
45
|
+
const out = execSync(`lsof -ti:${PORT}`, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
46
|
+
const pids = out.trim().split("\n").filter(Boolean);
|
|
47
|
+
return pids.length > 0 ? pids[0] : null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function cmdStatus() {
|
|
54
|
+
console.log(`\n${c.bold}⚡ Laya Local Router Status${c.reset}`);
|
|
55
|
+
console.log(`${c.dim}Endpoint: ${BASE_URL}${c.reset}\n`);
|
|
56
|
+
|
|
57
|
+
const pid = getDaemonPid();
|
|
58
|
+
const health = await checkHealth();
|
|
59
|
+
|
|
60
|
+
if (health.ok) {
|
|
61
|
+
console.log(` State: ${c.green}● RUNNING${c.reset} ${pid ? `(PID: ${pid})` : ""}`);
|
|
62
|
+
console.log(` Model: ${c.cyan}${health.data?.model || "default"}${c.reset}`);
|
|
63
|
+
console.log(` Engine: ${health.data?.engine || "laya-mlx"}`);
|
|
64
|
+
|
|
65
|
+
if (health.data?.anchors_count) {
|
|
66
|
+
console.log(`\n ${c.bold}Active Anchor Exemplars:${c.reset}`);
|
|
67
|
+
for (const [cat, count] of Object.entries(health.data.anchors_count)) {
|
|
68
|
+
console.log(` • ${cat.padEnd(22)} : ${c.yellow}${count}${c.reset} exemplars`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
console.log(` State: ${c.red}○ STOPPED${c.reset}`);
|
|
73
|
+
console.log(` ${c.dim}Run 'laya start' to launch daemon.${c.reset}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Config check
|
|
77
|
+
console.log(`\n ${c.bold}Pi Extension Config:${c.reset}`);
|
|
78
|
+
if (fs.existsSync(USER_CONFIG_PATH)) {
|
|
79
|
+
try {
|
|
80
|
+
const cfg = JSON.parse(fs.readFileSync(USER_CONFIG_PATH, "utf8"));
|
|
81
|
+
console.log(` • File: ${USER_CONFIG_PATH}`);
|
|
82
|
+
console.log(` • Enabled: ${cfg.enabled ? `${c.green}YES${c.reset}` : `${c.yellow}NO${c.reset}`}`);
|
|
83
|
+
console.log(` • Threshold: ${cfg.threshold ?? 0.25}`);
|
|
84
|
+
console.log(` • Routes: ${Object.keys(cfg.routes || {}).join(", ")}`);
|
|
85
|
+
} catch {
|
|
86
|
+
console.log(` • Status: ${c.red}Invalid JSON in ${USER_CONFIG_PATH}${c.reset}`);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.log(` • Status: ${c.dim}Not configured yet. Run 'laya init'${c.reset}`);
|
|
90
|
+
}
|
|
91
|
+
console.log();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function cmdStart() {
|
|
95
|
+
const health = await checkHealth(500);
|
|
96
|
+
if (health.ok) {
|
|
97
|
+
console.log(`${c.yellow}[Laya] Daemon is already running on port ${PORT}.${c.reset}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`${c.cyan}[Laya] Starting daemon on port ${PORT}...${c.reset}`);
|
|
102
|
+
|
|
103
|
+
let runCmd = "uv";
|
|
104
|
+
let runArgs = ["run", "server.py"];
|
|
105
|
+
|
|
106
|
+
// Fallback to python3 if uv is not installed
|
|
107
|
+
try {
|
|
108
|
+
execSync("which uv", { stdio: "ignore" });
|
|
109
|
+
} catch {
|
|
110
|
+
runCmd = "python3";
|
|
111
|
+
runArgs = ["server.py"];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const logFile = path.join(DAEMON_DIR, "server.log");
|
|
115
|
+
const outStream = fs.openSync(logFile, "a");
|
|
116
|
+
const errStream = fs.openSync(logFile, "a");
|
|
117
|
+
|
|
118
|
+
const child = spawn(runCmd, runArgs, {
|
|
119
|
+
cwd: DAEMON_DIR,
|
|
120
|
+
detached: true,
|
|
121
|
+
stdio: ["ignore", outStream, errStream],
|
|
122
|
+
env: { ...process.env, LAYA_PORT: PORT },
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
child.unref();
|
|
126
|
+
|
|
127
|
+
process.stdout.write(`Waiting for model load (${runCmd})... `);
|
|
128
|
+
for (let i = 0; i < 35; i++) {
|
|
129
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
130
|
+
const h = await checkHealth(800);
|
|
131
|
+
if (h.ok) {
|
|
132
|
+
console.log(`${c.green}Ready!${c.reset}`);
|
|
133
|
+
console.log(`${c.dim}Logs written to: ${logFile}${c.reset}`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
process.stdout.write(".");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`\n${c.red}[Laya] Timeout waiting for daemon startup. Check ${logFile}${c.reset}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function cmdStop() {
|
|
144
|
+
const pid = getDaemonPid();
|
|
145
|
+
if (!pid) {
|
|
146
|
+
console.log(`${c.yellow}[Laya] No running daemon found on port ${PORT}.${c.reset}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log(`${c.cyan}[Laya] Stopping daemon (PID: ${pid})...${c.reset}`);
|
|
151
|
+
try {
|
|
152
|
+
process.kill(Number(pid), "SIGTERM");
|
|
153
|
+
} catch {
|
|
154
|
+
try {
|
|
155
|
+
execSync(`kill -9 ${pid}`, { stdio: "ignore" });
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
160
|
+
const pidAfter = getDaemonPid();
|
|
161
|
+
if (pidAfter) {
|
|
162
|
+
try {
|
|
163
|
+
execSync(`kill -9 ${pidAfter}`, { stdio: "ignore" });
|
|
164
|
+
} catch {}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(`${c.green}[Laya] Daemon stopped successfully.${c.reset}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function cmdReload() {
|
|
171
|
+
console.log(`${c.cyan}[Laya] Reloading anchor embeddings...${c.reset}`);
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch(`${BASE_URL}/reload-anchors`, { method: "POST" });
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
console.error(`${c.red}[Laya] Failed to reload anchors (HTTP ${res.status})${c.reset}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
console.log(`${c.green}✓ Anchors reloaded in ${data.reloaded_ms}ms!${c.reset}`);
|
|
180
|
+
if (data.counts) {
|
|
181
|
+
for (const [cat, count] of Object.entries(data.counts)) {
|
|
182
|
+
console.log(` • ${cat.padEnd(22)}: ${count} exemplars`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error(`${c.red}[Laya] Error connecting to daemon: ${err.message}${c.reset}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function cmdLogs(limit = 15) {
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetch(`${BASE_URL}/logs?limit=${limit}`);
|
|
193
|
+
if (!res.ok) {
|
|
194
|
+
console.error(`${c.red}[Laya] Failed to fetch logs (HTTP ${res.status})${c.reset}`);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const data = await res.json();
|
|
198
|
+
if (!data.logs || data.logs.length === 0) {
|
|
199
|
+
console.log(`${c.dim}[Laya] No prediction logs recorded yet.${c.reset}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
console.log(`\n${c.bold}⚡ Recent Laya Prediction Logs (Latest ${data.logs.length}):${c.reset}\n`);
|
|
204
|
+
for (const item of data.logs) {
|
|
205
|
+
const timeStr = item.timestamp?.split("T")[1]?.slice(0, 8) || "";
|
|
206
|
+
const conf = Math.round((item.confidence || 0) * 100);
|
|
207
|
+
const prompt = item.prompt?.length > 45 ? item.prompt.slice(0, 42) + "..." : item.prompt;
|
|
208
|
+
console.log(
|
|
209
|
+
` ${c.dim}[${timeStr}]${c.reset} ${c.green}${item.choice?.padEnd(20)}${c.reset} (${conf}% conf) [${item.latency_ms}ms] <- "${prompt}"`
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
console.log();
|
|
213
|
+
} catch (err) {
|
|
214
|
+
console.error(`${c.red}[Laya] Error fetching logs: ${err.message}${c.reset}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function cmdFlag(cat, prompt) {
|
|
219
|
+
if (!cat || !prompt) {
|
|
220
|
+
console.log(`Usage: laya flag <category> <prompt text>`);
|
|
221
|
+
console.log(`Categories: CHAT_OR_TRIVIAL, RESEARCH_AND_EXPLORE, CODING_STANDARD, CODE_REVIEW, HARD_ARCHITECTURE`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const res = await fetch(`${BASE_URL}/corrections`, {
|
|
227
|
+
method: "POST",
|
|
228
|
+
headers: { "Content-Type": "application/json" },
|
|
229
|
+
body: JSON.stringify({ prompt, target: cat }),
|
|
230
|
+
});
|
|
231
|
+
if (!res.ok) {
|
|
232
|
+
console.error(`${c.red}[Laya] Failed to record correction (HTTP ${res.status})${c.reset}`);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
console.log(`${c.green}✓ Flagged prompt saved to corrections.jsonl → ${cat}${c.reset}`);
|
|
236
|
+
console.log(`Run '/skill:laya-tune' inside Pi to optimize anchors.`);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(`${c.red}[Laya] Error recording correction: ${err.message}${c.reset}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function cmdInit() {
|
|
243
|
+
const targetDir = path.dirname(USER_CONFIG_PATH);
|
|
244
|
+
if (!fs.existsSync(targetDir)) {
|
|
245
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (fs.existsSync(USER_CONFIG_PATH)) {
|
|
249
|
+
console.log(`${c.yellow}[Laya] Config already exists at: ${USER_CONFIG_PATH}${c.reset}`);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const sampleConfig = {
|
|
254
|
+
enabled: true,
|
|
255
|
+
endpoint: "http://127.0.0.1:4141/predict",
|
|
256
|
+
threshold: 0.25,
|
|
257
|
+
routes: {
|
|
258
|
+
CHAT_OR_TRIVIAL: {
|
|
259
|
+
provider: "anthropic",
|
|
260
|
+
model: "claude-haiku-4-5",
|
|
261
|
+
thinking: "off",
|
|
262
|
+
description: "General casual chat, short responses, simple confirmations, trivial queries",
|
|
263
|
+
},
|
|
264
|
+
RESEARCH_AND_EXPLORE: {
|
|
265
|
+
provider: "anthropic",
|
|
266
|
+
model: "claude-haiku-4-5",
|
|
267
|
+
thinking: "low",
|
|
268
|
+
description: "File search, code exploration, grep lookups, error investigations, planning",
|
|
269
|
+
},
|
|
270
|
+
CODING_STANDARD: {
|
|
271
|
+
provider: "anthropic",
|
|
272
|
+
model: "claude-sonnet-4-6",
|
|
273
|
+
thinking: "low",
|
|
274
|
+
description: "Standard coding tasks, bug fixes, feature implementation, refactoring, unit tests",
|
|
275
|
+
},
|
|
276
|
+
CODE_REVIEW: {
|
|
277
|
+
provider: "anthropic",
|
|
278
|
+
model: "claude-sonnet-4-6",
|
|
279
|
+
thinking: "medium",
|
|
280
|
+
description: "Code reviews, diff analysis, security audits, pre-push checks",
|
|
281
|
+
},
|
|
282
|
+
HARD_ARCHITECTURE: {
|
|
283
|
+
provider: "anthropic",
|
|
284
|
+
model: "claude-opus-4-6",
|
|
285
|
+
thinking: "high",
|
|
286
|
+
description: "Complex system architecture, tricky debugging, hard distributed systems problems",
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
fs.writeFileSync(USER_CONFIG_PATH, JSON.stringify(sampleConfig, null, 2), "utf8");
|
|
292
|
+
console.log(`${c.green}✓ Created default config at: ${USER_CONFIG_PATH}${c.reset}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function printHelp() {
|
|
296
|
+
console.log(`
|
|
297
|
+
${c.bold}⚡ Laya MLX CLI${c.reset} — Local semantic router for Pi coding agent
|
|
298
|
+
|
|
299
|
+
${c.bold}USAGE:${c.reset}
|
|
300
|
+
laya <command> [options]
|
|
301
|
+
|
|
302
|
+
${c.bold}COMMANDS:${c.reset}
|
|
303
|
+
${c.cyan}status${c.reset} Check daemon health, model, active anchors, and config
|
|
304
|
+
${c.cyan}start${c.reset} Start the Laya daemon in background on port ${PORT}
|
|
305
|
+
${c.cyan}stop${c.reset} Stop the running Laya daemon
|
|
306
|
+
${c.cyan}restart${c.reset} Restart the Laya daemon
|
|
307
|
+
${c.cyan}reload${c.reset} Hot-reload anchors.json embeddings without restart
|
|
308
|
+
${c.cyan}logs${c.reset} [-n <limit>] Display recent semantic prediction logs
|
|
309
|
+
${c.cyan}flag${c.reset} <cat> <text> Record a misclassification correction for tuning
|
|
310
|
+
${c.cyan}init${c.reset} Create default ~/.pi/agent/laya-router.json config
|
|
311
|
+
${c.cyan}help${c.reset} Show this help message
|
|
312
|
+
|
|
313
|
+
${c.bold}ENVIRONMENT VARIABLES:${c.reset}
|
|
314
|
+
LAYA_PORT Port for Laya daemon (default: 4141)
|
|
315
|
+
LAYA_MODEL Hugging Face model ID (default: aac6fef/laya-multilingual-mlx)
|
|
316
|
+
`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const [,, cmd, ...args] = process.argv;
|
|
320
|
+
|
|
321
|
+
switch (cmd) {
|
|
322
|
+
case "status":
|
|
323
|
+
await cmdStatus();
|
|
324
|
+
break;
|
|
325
|
+
case "start":
|
|
326
|
+
await cmdStart();
|
|
327
|
+
break;
|
|
328
|
+
case "stop":
|
|
329
|
+
await cmdStop();
|
|
330
|
+
break;
|
|
331
|
+
case "restart":
|
|
332
|
+
await cmdStop();
|
|
333
|
+
await cmdStart();
|
|
334
|
+
break;
|
|
335
|
+
case "reload":
|
|
336
|
+
await cmdReload();
|
|
337
|
+
break;
|
|
338
|
+
case "logs":
|
|
339
|
+
case "log": {
|
|
340
|
+
const limitIdx = args.indexOf("-n");
|
|
341
|
+
const limit = limitIdx !== -1 && args[limitIdx + 1] ? Number(args[limitIdx + 1]) : 15;
|
|
342
|
+
await cmdLogs(limit);
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case "flag":
|
|
346
|
+
case "correct":
|
|
347
|
+
await cmdFlag(args[0], args.slice(1).join(" "));
|
|
348
|
+
break;
|
|
349
|
+
case "init":
|
|
350
|
+
cmdInit();
|
|
351
|
+
break;
|
|
352
|
+
case "help":
|
|
353
|
+
case "--help":
|
|
354
|
+
case "-h":
|
|
355
|
+
case undefined:
|
|
356
|
+
printHelp();
|
|
357
|
+
break;
|
|
358
|
+
default:
|
|
359
|
+
console.error(`${c.red}Unknown command: ${cmd}${c.reset}`);
|
|
360
|
+
printHelp();
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"CHAT_OR_TRIVIAL": [
|
|
3
|
+
"hello how are you today",
|
|
4
|
+
"thanks, looks great!",
|
|
5
|
+
"what do you think about this?",
|
|
6
|
+
"yes I agree, sounds good",
|
|
7
|
+
"ok roger that",
|
|
8
|
+
"halo bro gimana kabarmu hari ini",
|
|
9
|
+
"iye bener banget katamu, setuju",
|
|
10
|
+
"thanks bro mantap banget infonya",
|
|
11
|
+
"ah yang bener nih, ku coba lagi",
|
|
12
|
+
"oke aku lagi sambil kerja, ini ngga berat kan",
|
|
13
|
+
"test testing",
|
|
14
|
+
"ok siap",
|
|
15
|
+
"ngobrol santai aja",
|
|
16
|
+
"hai apa kabar",
|
|
17
|
+
"oiya ini udah berubah",
|
|
18
|
+
"udah bener ini",
|
|
19
|
+
"ini udah jalan kok",
|
|
20
|
+
"ternyata udah berubah ya",
|
|
21
|
+
"oh iya paham",
|
|
22
|
+
"sip udah bisa",
|
|
23
|
+
"oke udah berubah",
|
|
24
|
+
"nah ini baru bener",
|
|
25
|
+
"good banget",
|
|
26
|
+
"good",
|
|
27
|
+
"mantap",
|
|
28
|
+
"keren banget",
|
|
29
|
+
"bagus banget",
|
|
30
|
+
"nice",
|
|
31
|
+
"great",
|
|
32
|
+
"tanya dulu, butuh informasi apa dari aku sebelum mulai prosesnya, perlu konfirmasi dulu kan"
|
|
33
|
+
],
|
|
34
|
+
"RESEARCH_AND_EXPLORE": [
|
|
35
|
+
"where is handleAuth defined in the codebase?",
|
|
36
|
+
"search the repo for occurrences of JWT_SECRET",
|
|
37
|
+
"why did the build fail, here is the error log",
|
|
38
|
+
"explain how this function works and trace data flow",
|
|
39
|
+
"investigate why this 500 internal server error happens",
|
|
40
|
+
"cariin dimana fungsi handleAuth dipanggil di codebase",
|
|
41
|
+
"kenapa build gagal ya, ini log errornya tolong baca",
|
|
42
|
+
"baca file config laya-router.json dong",
|
|
43
|
+
"coba cek file server.py bagian predict dan telusuri kodenya",
|
|
44
|
+
"jelaskan cara kerja fungsi ini dan alur datanya",
|
|
45
|
+
"investigasi kenapa error 500 ini terjadi",
|
|
46
|
+
"tolong cari tahu kenapa error ini muncul di logs",
|
|
47
|
+
"kamu bisa carikan pengerjaan master itu ada di branch mana",
|
|
48
|
+
"carikan branch atau pengerjaan fitur ini",
|
|
49
|
+
"buatkan plan dan analisa dulu sebelum implementasi",
|
|
50
|
+
"analisa dulu kodenya dan buat planning",
|
|
51
|
+
"eksplorasi rencana implementasi fitur baru dan adaptasi arsitekturnya",
|
|
52
|
+
"riset dan planning apakah bisa ide atau fitur ini diimplementasikan",
|
|
53
|
+
"bagaimana cara implementasi fitur ini di pi coding agent, tolong cari tahu dan analisa"
|
|
54
|
+
],
|
|
55
|
+
"CODING_STANDARD": [
|
|
56
|
+
"write a python script to organize downloaded files",
|
|
57
|
+
"fix bug in JSON parser when handling empty array",
|
|
58
|
+
"add new endpoint /metrics in fastapi app",
|
|
59
|
+
"implement user authentication and session management",
|
|
60
|
+
"write unit tests for helper functions",
|
|
61
|
+
"refactor this code to be cleaner and more modular",
|
|
62
|
+
"bikinin script python buat rapihin folder download otomatis",
|
|
63
|
+
"fix bug di parser JSON itu biar ngga error saat parse array",
|
|
64
|
+
"tambahin endpoint baru /metrics di fastapi",
|
|
65
|
+
"buatkan unit test untuk auth helper function",
|
|
66
|
+
"refactor kode ini agar lebih clean dan modular",
|
|
67
|
+
"implementasi fungsi upload image ke S3",
|
|
68
|
+
"bikin fitur baru untuk auth login",
|
|
69
|
+
"tulis kode untuk validasi email",
|
|
70
|
+
"oke terapkan perubahan kode ini sekarang",
|
|
71
|
+
"terapkan",
|
|
72
|
+
"eksekusi implementasi kodenya",
|
|
73
|
+
"[TASK: SPRINT]: Lakukan eksekusi Sprint untuk proyek sesuai IMPLEMENTATION_PLAN.md dan ARCHITECTURE_SPEC.md",
|
|
74
|
+
"[TASK: SPRINT] eksekusi Sprint pengerjaan fitur sesuai implementation plan dan delivery checkpoints TDD",
|
|
75
|
+
"Lakukan eksekusi Sprint dan delivery checkpoints TDD untuk implementasi endpoint, hub, dan backend code",
|
|
76
|
+
"[TASK: SPRINT] Lakukan eksekusi Sprint [DELIVERY CHECKPOINTS (TDD)] implementasi internal/api/ws_hub.go dan test"
|
|
77
|
+
],
|
|
78
|
+
"CODE_REVIEW": [
|
|
79
|
+
"review my git diff before pushing to remote branch",
|
|
80
|
+
"check security and performance on this auth code",
|
|
81
|
+
"review PR #42 for potential bugs",
|
|
82
|
+
"audit uncommitted changes",
|
|
83
|
+
"cek review ini",
|
|
84
|
+
"tolong cek review ini dong",
|
|
85
|
+
"code review",
|
|
86
|
+
"review code",
|
|
87
|
+
"review",
|
|
88
|
+
"/review",
|
|
89
|
+
"tolong review code ini",
|
|
90
|
+
"tolong review diff aku sebelum push ke remote branch",
|
|
91
|
+
"review PR #42 dong tolong cek bug dan security",
|
|
92
|
+
"cek security di kode auth yang baru ini sebelum deploy",
|
|
93
|
+
"audit uncommitted changes sebelum commit ke git",
|
|
94
|
+
"lakukan code review komprehensif pada commit terakhir",
|
|
95
|
+
"tolong audit perubahan kode di PR ini",
|
|
96
|
+
"review diff ini",
|
|
97
|
+
"review pull request",
|
|
98
|
+
"tolong review code ini sebelum push"
|
|
99
|
+
],
|
|
100
|
+
"HARD_ARCHITECTURE": [
|
|
101
|
+
"design a scalable multi-region distributed system architecture",
|
|
102
|
+
"evaluate database migration strategy from postgresql to horizontal sharding",
|
|
103
|
+
"investigate severe memory leak and deadlocks across concurrent worker pools",
|
|
104
|
+
"design event-driven architecture handling 100k requests per second with kafka",
|
|
105
|
+
"bagaimana desain arsitektur multi-region yang scalable dan high availability",
|
|
106
|
+
"evaluasi rencana migrasi database postgresql ke horizontal sharding",
|
|
107
|
+
"ini kok memory leak parah di worker, susah banget trace-nya, gimana solusinya",
|
|
108
|
+
"desain event-driven architecture untuk handling 100k req/s dengan kafka",
|
|
109
|
+
"analisis tradeoff antara microservices vs modular monolith untuk skala besar"
|
|
110
|
+
]
|
|
111
|
+
}
|