@azad-73/cli 0.1.0 → 0.2.1
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/LICENSE +10 -0
- package/README.md +70 -0
- package/dist/azad.js +646 -45
- package/dist/core/workflows/bugfix.md +13 -0
- package/dist/core/workflows/feature.md +11 -0
- package/package.json +2 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2026 et-azad. All rights reserved.
|
|
2
|
+
|
|
3
|
+
This software and its source code are proprietary and confidential.
|
|
4
|
+
No permission is granted to use, copy, modify, merge, publish, distribute,
|
|
5
|
+
sublicense, or sell any part of this software, except under a separate
|
|
6
|
+
written agreement with the copyright holder.
|
|
7
|
+
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
10
|
+
FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT.
|
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Azad73 (`azad`)
|
|
2
|
+
|
|
3
|
+
Agentic AI platform built on the [Pi](https://pi.dev) SDK — a branded terminal agent with a curated set of default agents. Local-first and BYO-key: your API key and sessions stay on your machine.
|
|
4
|
+
|
|
5
|
+
> "Azad" means *free / freedom* — local-first, BYO-key, no lock-in.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Requires **Node ≥ 22.19**.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @azad-73/cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Authenticate
|
|
16
|
+
|
|
17
|
+
Launch the TUI and log in (recommended — supports API key or subscription/OAuth):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
azad
|
|
21
|
+
# then, inside the TUI:
|
|
22
|
+
/login
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Credentials are stored locally in `~/.azad/auth.json`. Alternatives:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
azad auth set # paste your Anthropic API key
|
|
29
|
+
# or use an environment variable:
|
|
30
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
azad # launch the branded interactive TUI
|
|
37
|
+
azad agents list # list available agents
|
|
38
|
+
azad agents show <name> # show an agent (prompt + tools)
|
|
39
|
+
azad agents new <name> # scaffold a new agent in ~/.azad/agents
|
|
40
|
+
azad agents edit <name> # edit an agent ($EDITOR)
|
|
41
|
+
azad auth status|set|remove # manage the stored credential
|
|
42
|
+
azad version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Default agents include `context-cartographer` (run first on a new repo — maps the
|
|
46
|
+
codebase and writes `AGENTS.md`), `feature-builder`, `feature-mr-reviewer`,
|
|
47
|
+
`bug-reproduction-helper`, `root-cause-investigator`, `bug-fixer`,
|
|
48
|
+
`bugfix-mr-reviewer`, and `ticket-doc-writer`.
|
|
49
|
+
|
|
50
|
+
## Configuration
|
|
51
|
+
|
|
52
|
+
Everything lives under `~/.azad/`:
|
|
53
|
+
|
|
54
|
+
| Path | Contents |
|
|
55
|
+
|------|----------|
|
|
56
|
+
| `~/.azad/auth.json` | provider credentials (chmod 600) |
|
|
57
|
+
| `~/.azad/agents/` | your custom / overridden agents |
|
|
58
|
+
| `~/.azad/sessions/` | session history |
|
|
59
|
+
|
|
60
|
+
Select the bundled `azad73` theme in-TUI via `/settings`.
|
|
61
|
+
|
|
62
|
+
## Notes
|
|
63
|
+
|
|
64
|
+
- Installing pulls one runtime dependency, the Pi SDK (`@earendil-works/pi-coding-agent`).
|
|
65
|
+
You may see `npm warn deprecated node-domexception@1.0.0` during install — it is a
|
|
66
|
+
harmless deprecation notice from a deep transitive dependency, not from Azad73.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
Proprietary — all rights reserved. See [LICENSE](LICENSE).
|
package/dist/azad.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
// src/azad.ts
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import fs4 from "node:fs";
|
|
6
|
+
import path6 from "node:path";
|
|
7
7
|
import readline from "node:readline";
|
|
8
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8
9
|
|
|
9
10
|
// ../engine/src/index.ts
|
|
10
11
|
import {
|
|
11
|
-
SessionManager as
|
|
12
|
-
createAgentSession
|
|
12
|
+
SessionManager as SessionManager3,
|
|
13
|
+
createAgentSession as createAgentSession2
|
|
13
14
|
} from "@earendil-works/pi-coding-agent";
|
|
14
15
|
|
|
15
16
|
// ../shared/src/index.ts
|
|
@@ -84,52 +85,342 @@ import { InteractiveMode } from "@earendil-works/pi-coding-agent";
|
|
|
84
85
|
// ../engine/src/runtime.ts
|
|
85
86
|
import {
|
|
86
87
|
SessionManager,
|
|
88
|
+
SettingsManager,
|
|
87
89
|
createAgentSessionFromServices,
|
|
88
90
|
createAgentSessionRuntime,
|
|
89
91
|
createAgentSessionServices
|
|
90
92
|
} from "@earendil-works/pi-coding-agent";
|
|
91
93
|
|
|
94
|
+
// ../engine/src/approval.ts
|
|
95
|
+
import "@earendil-works/pi-coding-agent";
|
|
96
|
+
var APPROVAL_MODES = [
|
|
97
|
+
{ mode: "ask", short: "ask", label: "Ask \u2014 confirm before mutating tools" },
|
|
98
|
+
{ mode: "auto", short: "auto-pilot", label: "Auto-pilot \u2014 run tools without confirmation" }
|
|
99
|
+
];
|
|
100
|
+
var state = { mode: "ask", allow: /* @__PURE__ */ new Set() };
|
|
101
|
+
function getApprovalMode() {
|
|
102
|
+
return state.mode;
|
|
103
|
+
}
|
|
104
|
+
function approvalModeLabel(mode = state.mode) {
|
|
105
|
+
return APPROVAL_MODES.find((m) => m.mode === mode)?.short ?? mode;
|
|
106
|
+
}
|
|
107
|
+
var READ_ONLY = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
|
|
108
|
+
function truncate(s, max) {
|
|
109
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
110
|
+
return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
|
|
111
|
+
}
|
|
112
|
+
function describe(toolName, input) {
|
|
113
|
+
if (toolName === "bash") return `bash: ${truncate(String(input.command ?? ""), 64)}`;
|
|
114
|
+
if (toolName === "write") return `write ${String(input.file_path ?? input.path ?? "")}`;
|
|
115
|
+
if (toolName === "edit") return `edit ${String(input.file_path ?? input.path ?? "")}`;
|
|
116
|
+
return toolName;
|
|
117
|
+
}
|
|
118
|
+
function bashProgram(command) {
|
|
119
|
+
const tokens = command.trim().split(/\s+/);
|
|
120
|
+
for (const tok of tokens) {
|
|
121
|
+
if (/^[A-Za-z_]\w*=/.test(tok)) continue;
|
|
122
|
+
return tok;
|
|
123
|
+
}
|
|
124
|
+
return tokens[0] ?? "";
|
|
125
|
+
}
|
|
126
|
+
function bashPrograms(command) {
|
|
127
|
+
if (/\$\(|`|<\(|>\(/.test(command)) return null;
|
|
128
|
+
const programs = /* @__PURE__ */ new Set();
|
|
129
|
+
for (const part of command.split(/&&|\|\||[|;\n]/)) {
|
|
130
|
+
const p = bashProgram(part);
|
|
131
|
+
if (p) programs.add(p);
|
|
132
|
+
}
|
|
133
|
+
return [...programs];
|
|
134
|
+
}
|
|
135
|
+
function createApprovalExtension() {
|
|
136
|
+
return (pi) => {
|
|
137
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
138
|
+
if (state.mode === "auto") return void 0;
|
|
139
|
+
if (READ_ONLY.has(event.toolName)) return void 0;
|
|
140
|
+
const input = event.input;
|
|
141
|
+
const isBash = event.toolName === "bash";
|
|
142
|
+
const programs = isBash ? bashPrograms(String(input.command ?? "")) : null;
|
|
143
|
+
if (!isBash) {
|
|
144
|
+
if (state.allow.has(event.toolName)) return void 0;
|
|
145
|
+
} else if (programs && programs.length > 0 && programs.every((p) => state.allow.has(`bash:${p}`))) {
|
|
146
|
+
return void 0;
|
|
147
|
+
}
|
|
148
|
+
if (!ctx.hasUI) return { block: true, reason: `Approval required for ${event.toolName} (no UI to confirm)` };
|
|
149
|
+
const options = ["Yes", "No"];
|
|
150
|
+
let alwaysLabel;
|
|
151
|
+
let alwaysKeys = [];
|
|
152
|
+
if (!isBash) {
|
|
153
|
+
alwaysLabel = `Always allow ${event.toolName}`;
|
|
154
|
+
alwaysKeys = [event.toolName];
|
|
155
|
+
} else if (programs && programs.length > 0) {
|
|
156
|
+
alwaysKeys = programs.map((p) => `bash:${p}`);
|
|
157
|
+
alwaysLabel = programs.length === 1 ? `Always allow "${programs.join("")}" commands` : `Always allow: ${programs.join(", ")}`;
|
|
158
|
+
}
|
|
159
|
+
if (alwaysLabel) options.push(alwaysLabel);
|
|
160
|
+
const choice = await ctx.ui.select(`Approve ${describe(event.toolName, input)}`, options);
|
|
161
|
+
if (choice === void 0 || choice === "No") return { block: true, reason: "Denied by user" };
|
|
162
|
+
if (alwaysLabel && choice === alwaysLabel) for (const k of alwaysKeys) state.allow.add(k);
|
|
163
|
+
return void 0;
|
|
164
|
+
});
|
|
165
|
+
const setMode = (ctx, mode) => {
|
|
166
|
+
state.mode = mode;
|
|
167
|
+
ctx.ui.notify(`Approval mode: ${approvalModeLabel(mode)}`, mode === "auto" ? "warning" : "info");
|
|
168
|
+
};
|
|
169
|
+
const chooseMode = async (ctx) => {
|
|
170
|
+
const choice = await ctx.ui.select(
|
|
171
|
+
`Approval mode (current: ${approvalModeLabel()})`,
|
|
172
|
+
APPROVAL_MODES.map((m) => m.label)
|
|
173
|
+
);
|
|
174
|
+
const picked = APPROVAL_MODES.find((m) => m.label === choice);
|
|
175
|
+
if (picked) setMode(ctx, picked.mode);
|
|
176
|
+
};
|
|
177
|
+
pi.registerCommand("mode", { description: "Choose approval mode", handler: async (_args, ctx) => chooseMode(ctx) });
|
|
178
|
+
pi.registerCommand("ask", {
|
|
179
|
+
description: "Approval mode: ask before mutating tools",
|
|
180
|
+
handler: async (_args, ctx) => setMode(ctx, "ask")
|
|
181
|
+
});
|
|
182
|
+
pi.registerCommand("auto", {
|
|
183
|
+
description: "Approval mode: auto-pilot (run tools without confirmation)",
|
|
184
|
+
handler: async (_args, ctx) => setMode(ctx, "auto")
|
|
185
|
+
});
|
|
186
|
+
pi.registerShortcut("alt+a", { description: "Choose approval mode", handler: (ctx) => chooseMode(ctx) });
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ../engine/src/branding.ts
|
|
191
|
+
import os2 from "node:os";
|
|
192
|
+
import "@earendil-works/pi-coding-agent";
|
|
193
|
+
|
|
194
|
+
// ../engine/src/update.ts
|
|
195
|
+
import fs2 from "node:fs";
|
|
196
|
+
import path4 from "node:path";
|
|
197
|
+
var CLI_PACKAGE = "@azad-73/cli";
|
|
198
|
+
var CACHE_FILE = path4.join(AZAD_DIR, ".update-check.json");
|
|
199
|
+
var TTL_MS = 24 * 60 * 60 * 1e3;
|
|
200
|
+
async function getLatestVersion(timeoutMs = 3e3) {
|
|
201
|
+
try {
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
204
|
+
const res = await fetch(`https://registry.npmjs.org/${CLI_PACKAGE}/latest`, { signal: controller.signal });
|
|
205
|
+
clearTimeout(timer);
|
|
206
|
+
if (!res.ok) return null;
|
|
207
|
+
const data = await res.json();
|
|
208
|
+
return data.version ?? null;
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function isNewerVersion(latest, current) {
|
|
214
|
+
const a = latest.split(".").map((n) => Number.parseInt(n, 10));
|
|
215
|
+
const b = current.split(".").map((n) => Number.parseInt(n, 10));
|
|
216
|
+
for (let i = 0; i < 3; i++) {
|
|
217
|
+
const x = a[i] ?? 0;
|
|
218
|
+
const y = b[i] ?? 0;
|
|
219
|
+
if (x !== y) return x > y;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
function readCache() {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(fs2.readFileSync(CACHE_FILE, "utf8"));
|
|
226
|
+
} catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function writeCache(cache) {
|
|
231
|
+
try {
|
|
232
|
+
fs2.mkdirSync(AZAD_DIR, { recursive: true });
|
|
233
|
+
fs2.writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
234
|
+
} catch {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
async function checkForUpdate(current) {
|
|
238
|
+
const cache = readCache();
|
|
239
|
+
let latest;
|
|
240
|
+
if (cache && Date.now() - cache.checkedAt < TTL_MS) {
|
|
241
|
+
latest = cache.latest;
|
|
242
|
+
} else {
|
|
243
|
+
latest = await getLatestVersion();
|
|
244
|
+
writeCache({ checkedAt: Date.now(), latest });
|
|
245
|
+
}
|
|
246
|
+
return latest && isNewerVersion(latest, current) ? latest : null;
|
|
247
|
+
}
|
|
248
|
+
function cachedUpdate(current) {
|
|
249
|
+
const latest = readCache()?.latest;
|
|
250
|
+
return latest && isNewerVersion(latest, current) ? latest : null;
|
|
251
|
+
}
|
|
252
|
+
|
|
92
253
|
// ../engine/src/branding.ts
|
|
93
|
-
import { VERSION } from "@earendil-works/pi-coding-agent";
|
|
94
254
|
function coreThemesDir() {
|
|
95
255
|
return resolveCoreDataDir("themes", import.meta.url);
|
|
96
256
|
}
|
|
97
|
-
|
|
257
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
258
|
+
function vlen(s) {
|
|
259
|
+
return s.replace(ANSI, "").length;
|
|
260
|
+
}
|
|
261
|
+
var LOGO = [
|
|
262
|
+
"\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
263
|
+
"\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2557",
|
|
264
|
+
" \u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
265
|
+
" \u2588\u2588\u2554\u255D \u255A\u2550\u2550\u2550\u2588\u2588\u2557",
|
|
266
|
+
" \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
|
|
267
|
+
" \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D"
|
|
268
|
+
];
|
|
269
|
+
var LOGO_W = 16;
|
|
270
|
+
var LOGO_GAP = 6;
|
|
271
|
+
var TIP_ITEMS = [
|
|
272
|
+
{ cmd: "/login", desc: "sign in (API key or subscription)" },
|
|
273
|
+
{ cmd: "/model", desc: "switch the active model" },
|
|
274
|
+
{ cmd: "/hotkeys", desc: "show all keyboard shortcuts" }
|
|
275
|
+
];
|
|
276
|
+
var HELP_LINES = [
|
|
277
|
+
"Slash commands (type here):",
|
|
278
|
+
" /login connect a provider (API key or subscription)",
|
|
279
|
+
" /model switch model",
|
|
280
|
+
" /mode choose approval mode (also alt+a)",
|
|
281
|
+
" /ask approval: ask before mutating tools",
|
|
282
|
+
" /auto approval: auto-pilot",
|
|
283
|
+
" /hotkeys keyboard shortcuts",
|
|
284
|
+
"Shell (run: azad <cmd>):",
|
|
285
|
+
" agents list|show|new|edit",
|
|
286
|
+
" workflow list|show|new|edit|run",
|
|
287
|
+
" auth status|set|remove \xB7 update \xB7 version"
|
|
288
|
+
];
|
|
289
|
+
var ANIM_STYLE = "wipe";
|
|
290
|
+
var ANIM_MS = 750;
|
|
291
|
+
function animateLogoCell(theme, rowText, progress) {
|
|
292
|
+
const accent = (s) => theme.fg("accent", s);
|
|
293
|
+
const glow = (s) => theme.fg("borderAccent", s);
|
|
294
|
+
const logoFull = rowText.padEnd(LOGO_W);
|
|
295
|
+
if (progress >= 1) return accent(logoFull);
|
|
296
|
+
if (ANIM_STYLE === "wipe") {
|
|
297
|
+
const visible = logoFull.slice(0, Math.round(progress * LOGO_W));
|
|
298
|
+
const body = visible.slice(0, Math.max(0, visible.length - 1));
|
|
299
|
+
const edge = visible.slice(Math.max(0, visible.length - 1));
|
|
300
|
+
return `${accent(body)}${glow(edge)}${" ".repeat(LOGO_W - visible.length)}`;
|
|
301
|
+
}
|
|
302
|
+
const band = 2;
|
|
303
|
+
const sweep = progress * (LOGO_W + 2 * band) - band;
|
|
304
|
+
const start = Math.max(0, Math.min(LOGO_W, Math.round(sweep - band)));
|
|
305
|
+
const end = Math.max(0, Math.min(LOGO_W, Math.round(sweep + band) + 1));
|
|
306
|
+
return `${accent(logoFull.slice(0, start))}${glow(logoFull.slice(start, end))}${accent(logoFull.slice(end))}`;
|
|
307
|
+
}
|
|
308
|
+
function boxedHeader(theme, version, width, progress) {
|
|
98
309
|
const accent = (s) => theme.fg("accent", s);
|
|
99
|
-
const muted = (s) => theme.fg("muted", s);
|
|
100
310
|
const dim = (s) => theme.fg("dim", s);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
311
|
+
const muted = (s) => theme.fg("muted", s);
|
|
312
|
+
const text = (s) => theme.fg("text", s);
|
|
313
|
+
const inner = Math.max(54, Math.min(width - 2, 72));
|
|
314
|
+
const verText = `v${version}`;
|
|
315
|
+
const titlePlain = ` Azad73 ${verText} `;
|
|
316
|
+
const titleColored = ` ${accent("Azad73")} ${dim(verText)} `;
|
|
317
|
+
const dashAfter = Math.max(2, inner - 1 - titlePlain.length);
|
|
318
|
+
const top = `${accent("\u256D\u2500")}${titleColored}${accent("\u2500".repeat(dashAfter))}${accent("\u256E")}`;
|
|
319
|
+
const bottom = accent(`\u2570${"\u2500".repeat(inner)}\u256F`);
|
|
320
|
+
const row = (colored, plainLen) => `${accent("\u2502")}${colored}${" ".repeat(Math.max(0, inner - plainLen))}${accent("\u2502")}`;
|
|
321
|
+
const tipBudget = inner - 2 - LOGO_W - LOGO_GAP;
|
|
322
|
+
const tips = [{ colored: muted("Tips"), plainLen: 4 }];
|
|
323
|
+
for (const t of TIP_ITEMS) {
|
|
324
|
+
const fixed = 2 + t.cmd.length + 1;
|
|
325
|
+
const desc = fixed + t.desc.length > tipBudget ? t.desc.slice(0, Math.max(0, tipBudget - fixed)) : t.desc;
|
|
326
|
+
tips.push({ colored: `${dim("\u2022")} ${accent(t.cmd)} ${text(desc)}`, plainLen: fixed + desc.length });
|
|
327
|
+
}
|
|
328
|
+
const blank = row("", 0);
|
|
329
|
+
const lines = [blank];
|
|
330
|
+
const rowCount = Math.max(LOGO.length, tips.length);
|
|
331
|
+
for (let i = 0; i < rowCount; i++) {
|
|
332
|
+
const cell = animateLogoCell(theme, LOGO[i] ?? "", progress);
|
|
333
|
+
const tip = tips[i] ?? { colored: "", plainLen: 0 };
|
|
334
|
+
lines.push(row(` ${cell}${" ".repeat(LOGO_GAP)}${tip.colored}`, 2 + LOGO_W + LOGO_GAP + tip.plainLen));
|
|
335
|
+
}
|
|
336
|
+
lines.push(blank);
|
|
337
|
+
return ["", top, ...lines, bottom, ""];
|
|
107
338
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return brandBanner(theme);
|
|
115
|
-
},
|
|
116
|
-
invalidate() {
|
|
339
|
+
function createBrandingExtension(opts) {
|
|
340
|
+
return (pi) => {
|
|
341
|
+
pi.registerCommand("help", {
|
|
342
|
+
description: "Show Azad73 commands",
|
|
343
|
+
handler: async (_args, ctx) => {
|
|
344
|
+
await ctx.ui.select("Azad73 commands (Esc to close)", [...HELP_LINES]);
|
|
117
345
|
}
|
|
118
|
-
})
|
|
119
|
-
|
|
120
|
-
|
|
346
|
+
});
|
|
347
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
348
|
+
if (ctx.mode !== "tui") return;
|
|
349
|
+
ctx.ui.setTitle("Azad73");
|
|
350
|
+
ctx.ui.setHeader((tui, theme) => {
|
|
351
|
+
const start = Date.now();
|
|
352
|
+
let timer;
|
|
353
|
+
const tick = () => {
|
|
354
|
+
if (Date.now() - start >= ANIM_MS && timer) {
|
|
355
|
+
clearInterval(timer);
|
|
356
|
+
timer = void 0;
|
|
357
|
+
}
|
|
358
|
+
tui.requestRender();
|
|
359
|
+
};
|
|
360
|
+
timer = setInterval(tick, 33);
|
|
361
|
+
return {
|
|
362
|
+
render(width) {
|
|
363
|
+
const progress = Math.min(1, (Date.now() - start) / ANIM_MS);
|
|
364
|
+
return boxedHeader(theme, opts.version, width, progress);
|
|
365
|
+
},
|
|
366
|
+
invalidate() {
|
|
367
|
+
},
|
|
368
|
+
dispose() {
|
|
369
|
+
if (timer) clearInterval(timer);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
|
+
ctx.ui.setFooter((_tui, theme) => ({
|
|
374
|
+
render(width) {
|
|
375
|
+
const home = os2.homedir();
|
|
376
|
+
const cwd = ctx.cwd.startsWith(home) ? `~${ctx.cwd.slice(home.length)}` : ctx.cwd;
|
|
377
|
+
let cost = 0;
|
|
378
|
+
const entries = ctx.sessionManager.getBranch();
|
|
379
|
+
for (const entry of entries) {
|
|
380
|
+
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
381
|
+
cost += entry.message.usage?.cost?.total ?? 0;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const model = ctx.model?.id ?? "no model";
|
|
385
|
+
const level = pi.getThinkingLevel();
|
|
386
|
+
const mode = getApprovalMode();
|
|
387
|
+
const modeText = approvalModeLabel(mode);
|
|
388
|
+
const left = theme.fg("dim", `${cwd} \xB7 $${cost.toFixed(2)} session`);
|
|
389
|
+
const modeColored = mode === "auto" ? theme.fg("warning", modeText) : theme.fg("dim", modeText);
|
|
390
|
+
const right = `${theme.fg("dim", `${model} \xB7 ${level} \xB7 `)}${modeColored}`;
|
|
391
|
+
if (vlen(left) + vlen(right) + 1 > width) {
|
|
392
|
+
return [`${theme.fg("dim", `${model} \xB7 ${level} \xB7 `)}${modeColored}`];
|
|
393
|
+
}
|
|
394
|
+
const gap = Math.max(1, width - vlen(left) - vlen(right));
|
|
395
|
+
return [`${left}${" ".repeat(gap)}${right}`];
|
|
396
|
+
},
|
|
397
|
+
invalidate() {
|
|
398
|
+
}
|
|
399
|
+
}));
|
|
400
|
+
void checkForUpdate(opts.version).then((latest) => {
|
|
401
|
+
if (latest) {
|
|
402
|
+
ctx.ui.notify(`Azad73 update available: ${opts.version} \u2192 ${latest} \xB7 run \`azad update\``, "info");
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
}
|
|
121
408
|
|
|
122
409
|
// ../engine/src/runtime.ts
|
|
123
|
-
async function buildRuntime(workspace = defaultWorkspace(), sessionManager = SessionManager.create(workspace.rootDir)) {
|
|
410
|
+
async function buildRuntime(workspace = defaultWorkspace(), sessionManager = SessionManager.create(workspace.rootDir), options = {}) {
|
|
124
411
|
const { authStorage, modelRegistry } = createAuth(workspace);
|
|
412
|
+
const branding = createBrandingExtension({ version: options.version ?? "0.0.0" });
|
|
125
413
|
const createRuntime = async ({ cwd, sessionManager: sm, sessionStartEvent }) => {
|
|
414
|
+
const settingsManager = SettingsManager.create(cwd, workspace.agentDir);
|
|
415
|
+
if (!settingsManager.getQuietStartup()) settingsManager.setQuietStartup(true);
|
|
126
416
|
const services = await createAgentSessionServices({
|
|
127
417
|
cwd,
|
|
128
418
|
agentDir: workspace.agentDir,
|
|
129
419
|
authStorage,
|
|
130
420
|
modelRegistry,
|
|
421
|
+
settingsManager,
|
|
131
422
|
resourceLoaderOptions: {
|
|
132
|
-
extensionFactories: [
|
|
423
|
+
extensionFactories: [branding, createApprovalExtension()],
|
|
133
424
|
additionalThemePaths: [coreThemesDir()]
|
|
134
425
|
}
|
|
135
426
|
});
|
|
@@ -147,24 +438,103 @@ async function buildRuntime(workspace = defaultWorkspace(), sessionManager = Ses
|
|
|
147
438
|
}
|
|
148
439
|
|
|
149
440
|
// ../engine/src/tui.ts
|
|
150
|
-
async function runInteractive(
|
|
151
|
-
const
|
|
441
|
+
async function runInteractive(options = {}) {
|
|
442
|
+
const workspace = options.workspace ?? defaultWorkspace();
|
|
443
|
+
const runtime = await buildRuntime(workspace, void 0, { version: options.version });
|
|
152
444
|
const mode = new InteractiveMode(runtime);
|
|
153
445
|
await mode.run();
|
|
154
446
|
}
|
|
155
447
|
|
|
448
|
+
// ../engine/src/workflows.ts
|
|
449
|
+
import fs3 from "node:fs";
|
|
450
|
+
import path5 from "node:path";
|
|
451
|
+
import { parseFrontmatter as parseFrontmatter2 } from "@earendil-works/pi-coding-agent";
|
|
452
|
+
function discoverWorkflows(dirs) {
|
|
453
|
+
const byName = /* @__PURE__ */ new Map();
|
|
454
|
+
for (const dir of dirs) {
|
|
455
|
+
if (!fs3.existsSync(dir)) continue;
|
|
456
|
+
for (const entry of fs3.readdirSync(dir)) {
|
|
457
|
+
if (!entry.endsWith(".md")) continue;
|
|
458
|
+
const filePath = path5.join(dir, entry);
|
|
459
|
+
let content;
|
|
460
|
+
try {
|
|
461
|
+
content = fs3.readFileSync(filePath, "utf8");
|
|
462
|
+
} catch {
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
const { frontmatter } = parseFrontmatter2(content);
|
|
466
|
+
const name = frontmatter.name?.trim();
|
|
467
|
+
if (!name) continue;
|
|
468
|
+
const steps = (Array.isArray(frontmatter.steps) ? frontmatter.steps : []).map((s) => ({ agent: String(s?.agent ?? "").trim(), task: s?.task })).filter((s) => s.agent.length > 0);
|
|
469
|
+
byName.set(name, {
|
|
470
|
+
name,
|
|
471
|
+
description: frontmatter.description?.trim() ?? "",
|
|
472
|
+
approval: frontmatter.approval === "off" ? "off" : "between-steps",
|
|
473
|
+
steps,
|
|
474
|
+
filePath
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// ../engine/src/workflow-run.ts
|
|
482
|
+
import { DefaultResourceLoader, SessionManager as SessionManager2, createAgentSession } from "@earendil-works/pi-coding-agent";
|
|
483
|
+
async function runAgentStep(agent, input, options = {}) {
|
|
484
|
+
const workspace = options.workspace ?? defaultWorkspace();
|
|
485
|
+
const { authStorage, modelRegistry } = createAuth(workspace);
|
|
486
|
+
const model = options.modelId ? modelRegistry.find("anthropic", options.modelId) ?? void 0 : void 0;
|
|
487
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
488
|
+
cwd: workspace.rootDir,
|
|
489
|
+
agentDir: workspace.agentDir,
|
|
490
|
+
systemPromptOverride: () => agent.systemPrompt,
|
|
491
|
+
noExtensions: true
|
|
492
|
+
});
|
|
493
|
+
await resourceLoader.reload();
|
|
494
|
+
const { session } = await createAgentSession({
|
|
495
|
+
cwd: workspace.rootDir,
|
|
496
|
+
agentDir: workspace.agentDir,
|
|
497
|
+
resourceLoader,
|
|
498
|
+
authStorage,
|
|
499
|
+
modelRegistry,
|
|
500
|
+
model,
|
|
501
|
+
tools: agent.tools,
|
|
502
|
+
sessionManager: SessionManager2.inMemory(workspace.rootDir)
|
|
503
|
+
});
|
|
504
|
+
let output = "";
|
|
505
|
+
const unsubscribe = session.subscribe((event) => {
|
|
506
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
507
|
+
output += event.assistantMessageEvent.delta;
|
|
508
|
+
options.onText?.(event.assistantMessageEvent.delta);
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
await session.prompt(input);
|
|
512
|
+
unsubscribe();
|
|
513
|
+
session.dispose();
|
|
514
|
+
return output.trim();
|
|
515
|
+
}
|
|
516
|
+
|
|
156
517
|
// src/azad.ts
|
|
157
518
|
import { VERSION as PI_VERSION } from "@earendil-works/pi-coding-agent";
|
|
158
519
|
var PROVIDER = "anthropic";
|
|
159
520
|
function coreAgentsDir() {
|
|
160
521
|
return resolveCoreDataDir("agents", import.meta.url);
|
|
161
522
|
}
|
|
523
|
+
function azadVersion() {
|
|
524
|
+
try {
|
|
525
|
+
const pkgPath = path6.join(path6.dirname(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
526
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
|
|
527
|
+
return pkg.version ?? "0.0.0";
|
|
528
|
+
} catch {
|
|
529
|
+
return "0.0.0";
|
|
530
|
+
}
|
|
531
|
+
}
|
|
162
532
|
function agentDirs() {
|
|
163
533
|
const ws = defaultWorkspace();
|
|
164
|
-
return [coreAgentsDir(), workspaceAgentsDir(),
|
|
534
|
+
return [coreAgentsDir(), workspaceAgentsDir(), path6.join(ws.rootDir, ".azad", "agents")];
|
|
165
535
|
}
|
|
166
536
|
function workspaceAgentsDir() {
|
|
167
|
-
return
|
|
537
|
+
return path6.join(defaultWorkspace().agentDir, "agents");
|
|
168
538
|
}
|
|
169
539
|
var AGENT_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
170
540
|
function newAgent(name) {
|
|
@@ -177,12 +547,12 @@ function newAgent(name) {
|
|
|
177
547
|
process.exit(1);
|
|
178
548
|
}
|
|
179
549
|
const dir = workspaceAgentsDir();
|
|
180
|
-
const file =
|
|
181
|
-
if (
|
|
550
|
+
const file = path6.join(dir, `${name}.md`);
|
|
551
|
+
if (fs4.existsSync(file)) {
|
|
182
552
|
console.error(`agent already exists: ${file}`);
|
|
183
553
|
process.exit(1);
|
|
184
554
|
}
|
|
185
|
-
|
|
555
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
186
556
|
const template = [
|
|
187
557
|
"---",
|
|
188
558
|
`name: ${name}`,
|
|
@@ -195,7 +565,7 @@ function newAgent(name) {
|
|
|
195
565
|
"TODO: write the system prompt.",
|
|
196
566
|
""
|
|
197
567
|
].join("\n");
|
|
198
|
-
|
|
568
|
+
fs4.writeFileSync(file, template);
|
|
199
569
|
console.log(`created ${file}`);
|
|
200
570
|
console.log(`edit with: azad agents edit ${name}`);
|
|
201
571
|
}
|
|
@@ -204,16 +574,20 @@ async function editAgent(name) {
|
|
|
204
574
|
console.error("usage: azad agents edit <name>");
|
|
205
575
|
process.exit(1);
|
|
206
576
|
}
|
|
577
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
578
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens (e.g. my-agent)");
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
207
581
|
const dir = workspaceAgentsDir();
|
|
208
|
-
const target =
|
|
209
|
-
if (!
|
|
582
|
+
const target = path6.join(dir, `${name}.md`);
|
|
583
|
+
if (!fs4.existsSync(target)) {
|
|
210
584
|
const found = discoverAgents(agentDirs()).find((a) => a.name === name);
|
|
211
585
|
if (!found) {
|
|
212
586
|
console.error(`agent not found: ${name}`);
|
|
213
587
|
process.exit(1);
|
|
214
588
|
}
|
|
215
|
-
|
|
216
|
-
|
|
589
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
590
|
+
fs4.copyFileSync(found.filePath, target);
|
|
217
591
|
console.log(`copied default '${name}' into the workspace for editing: ${target}`);
|
|
218
592
|
}
|
|
219
593
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
@@ -265,7 +639,7 @@ function promptLine(question) {
|
|
|
265
639
|
}
|
|
266
640
|
async function authCmd(sub) {
|
|
267
641
|
const ws = defaultWorkspace();
|
|
268
|
-
const authPath =
|
|
642
|
+
const authPath = path6.join(ws.agentDir, "auth.json");
|
|
269
643
|
const { authStorage } = createAuth(ws);
|
|
270
644
|
if (sub === "status" || sub === void 0) {
|
|
271
645
|
const status = authStorage.getAuthStatus(PROVIDER);
|
|
@@ -280,7 +654,7 @@ async function authCmd(sub) {
|
|
|
280
654
|
console.error("no key provided");
|
|
281
655
|
process.exit(1);
|
|
282
656
|
}
|
|
283
|
-
|
|
657
|
+
fs4.mkdirSync(ws.agentDir, { recursive: true });
|
|
284
658
|
authStorage.set(PROVIDER, { type: "api_key", key });
|
|
285
659
|
console.log(`stored ${PROVIDER} key in ${authPath}`);
|
|
286
660
|
return;
|
|
@@ -293,6 +667,186 @@ async function authCmd(sub) {
|
|
|
293
667
|
console.error("usage: azad auth [status|set|remove]");
|
|
294
668
|
process.exit(1);
|
|
295
669
|
}
|
|
670
|
+
function workflowDirs() {
|
|
671
|
+
const ws = defaultWorkspace();
|
|
672
|
+
return [
|
|
673
|
+
resolveCoreDataDir("workflows", import.meta.url),
|
|
674
|
+
path6.join(ws.agentDir, "workflows"),
|
|
675
|
+
path6.join(ws.rootDir, ".azad", "workflows")
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
function workspaceWorkflowsDir() {
|
|
679
|
+
return path6.join(defaultWorkspace().agentDir, "workflows");
|
|
680
|
+
}
|
|
681
|
+
function listWorkflows(workflows) {
|
|
682
|
+
console.log(`Azad73 workflows (${workflows.length}):
|
|
683
|
+
`);
|
|
684
|
+
for (const w of workflows) {
|
|
685
|
+
console.log(` ${w.name.padEnd(14)} ${ellipsis(w.description, 60)}`);
|
|
686
|
+
console.log(` ${" ".repeat(14)} ${w.steps.map((s) => s.agent).join(" \u2192 ")}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function showWorkflow(workflows, name) {
|
|
690
|
+
if (!name) {
|
|
691
|
+
console.error("usage: azad workflow show <name>");
|
|
692
|
+
process.exit(1);
|
|
693
|
+
}
|
|
694
|
+
const workflow = workflows.find((w) => w.name === name);
|
|
695
|
+
if (!workflow) {
|
|
696
|
+
console.error(`workflow not found: ${name}`);
|
|
697
|
+
process.exit(1);
|
|
698
|
+
}
|
|
699
|
+
const known = new Set(discoverAgents(agentDirs()).map((a) => a.name));
|
|
700
|
+
console.log(`# ${workflow.name}`);
|
|
701
|
+
if (workflow.description) console.log(workflow.description);
|
|
702
|
+
console.log(`approval: ${workflow.approval}`);
|
|
703
|
+
console.log(`file: ${workflow.filePath}
|
|
704
|
+
`);
|
|
705
|
+
workflow.steps.forEach((step, i) => {
|
|
706
|
+
const missing = known.has(step.agent) ? "" : " (agent not found)";
|
|
707
|
+
console.log(` ${i + 1}. ${step.agent}${step.task ? ` \u2014 ${step.task}` : ""}${missing}`);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
function newWorkflow(name) {
|
|
711
|
+
if (!name) {
|
|
712
|
+
console.error("usage: azad workflow new <name>");
|
|
713
|
+
process.exit(1);
|
|
714
|
+
}
|
|
715
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
716
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens (e.g. my-flow)");
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
const dir = workspaceWorkflowsDir();
|
|
720
|
+
const file = path6.join(dir, `${name}.md`);
|
|
721
|
+
if (fs4.existsSync(file)) {
|
|
722
|
+
console.error(`workflow already exists: ${file}`);
|
|
723
|
+
process.exit(1);
|
|
724
|
+
}
|
|
725
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
726
|
+
const template = [
|
|
727
|
+
"---",
|
|
728
|
+
`name: ${name}`,
|
|
729
|
+
"description: TODO \u2014 what this workflow does.",
|
|
730
|
+
"approval: between-steps",
|
|
731
|
+
"steps:",
|
|
732
|
+
" - agent: context-cartographer",
|
|
733
|
+
"---",
|
|
734
|
+
"",
|
|
735
|
+
"TODO: describe the workflow. Add or remove steps under `steps:` (each is `- agent: <name>`).",
|
|
736
|
+
""
|
|
737
|
+
].join("\n");
|
|
738
|
+
fs4.writeFileSync(file, template);
|
|
739
|
+
console.log(`created ${file}`);
|
|
740
|
+
console.log(`edit with: azad workflow edit ${name}`);
|
|
741
|
+
}
|
|
742
|
+
async function editWorkflow(name) {
|
|
743
|
+
if (!name) {
|
|
744
|
+
console.error("usage: azad workflow edit <name>");
|
|
745
|
+
process.exit(1);
|
|
746
|
+
}
|
|
747
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
748
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens");
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
const dir = workspaceWorkflowsDir();
|
|
752
|
+
const target = path6.join(dir, `${name}.md`);
|
|
753
|
+
if (!fs4.existsSync(target)) {
|
|
754
|
+
const found = discoverWorkflows(workflowDirs()).find((w) => w.name === name);
|
|
755
|
+
if (!found) {
|
|
756
|
+
console.error(`workflow not found: ${name}`);
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
760
|
+
fs4.copyFileSync(found.filePath, target);
|
|
761
|
+
console.log(`copied default '${name}' into the workspace for editing: ${target}`);
|
|
762
|
+
}
|
|
763
|
+
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
764
|
+
await new Promise((resolve, reject) => {
|
|
765
|
+
const child = spawn(editor, [target], { stdio: "inherit" });
|
|
766
|
+
child.on("exit", () => resolve());
|
|
767
|
+
child.on("error", reject);
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
function buildStepInput(step, goal, previous, index) {
|
|
771
|
+
let task = step.task ?? "";
|
|
772
|
+
if (task.includes("{previous}")) task = task.replace(/\{previous\}/g, previous);
|
|
773
|
+
if (index === 0) return task || goal || "Begin.";
|
|
774
|
+
const parts = [];
|
|
775
|
+
if (task) parts.push(task);
|
|
776
|
+
if (goal) parts.push(`Overall goal: ${goal}`);
|
|
777
|
+
parts.push(`Output from the previous step:
|
|
778
|
+
|
|
779
|
+
${previous}`);
|
|
780
|
+
return parts.join("\n\n");
|
|
781
|
+
}
|
|
782
|
+
async function promptYesNo(question) {
|
|
783
|
+
const answer = (await promptLine(question)).trim().toLowerCase();
|
|
784
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
785
|
+
}
|
|
786
|
+
async function runWorkflow(name, goal, modelId) {
|
|
787
|
+
if (!name) {
|
|
788
|
+
console.error("usage: azad workflow run <name> [goal] [--model <id>]");
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
const workflow = discoverWorkflows(workflowDirs()).find((w) => w.name === name);
|
|
792
|
+
if (!workflow) {
|
|
793
|
+
console.error(`workflow not found: ${name}`);
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
const agentMap = new Map(discoverAgents(agentDirs()).map((a) => [a.name, a]));
|
|
797
|
+
const missing = [...new Set(workflow.steps.filter((s) => !agentMap.has(s.agent)).map((s) => s.agent))];
|
|
798
|
+
if (missing.length > 0) {
|
|
799
|
+
console.error(`workflow '${name}' references unknown agents: ${missing.join(", ")}`);
|
|
800
|
+
process.exit(1);
|
|
801
|
+
}
|
|
802
|
+
console.log(
|
|
803
|
+
`
|
|
804
|
+
Workflow: ${workflow.name} \xB7 ${workflow.steps.length} steps \xB7 approval: ${workflow.approval}${modelId ? ` \xB7 model: ${modelId}` : ""}
|
|
805
|
+
`
|
|
806
|
+
);
|
|
807
|
+
let previous = "";
|
|
808
|
+
for (let i = 0; i < workflow.steps.length; i++) {
|
|
809
|
+
const step = workflow.steps[i];
|
|
810
|
+
const agent = step ? agentMap.get(step.agent) : void 0;
|
|
811
|
+
if (!step || !agent) continue;
|
|
812
|
+
if (i > 0 && workflow.approval === "between-steps") {
|
|
813
|
+
const ok = await promptYesNo(`Proceed to step ${i + 1}/${workflow.steps.length} \xB7 ${step.agent}? [Y/n] `);
|
|
814
|
+
if (!ok) {
|
|
815
|
+
console.log("\nWorkflow stopped.");
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
console.log(`
|
|
820
|
+
\u2500\u2500\u2500 Step ${i + 1}/${workflow.steps.length} \xB7 ${step.agent} \u2500\u2500\u2500
|
|
821
|
+
`);
|
|
822
|
+
previous = await runAgentStep(agent, buildStepInput(step, goal, previous, i), {
|
|
823
|
+
modelId,
|
|
824
|
+
onText: (t) => process.stdout.write(t)
|
|
825
|
+
});
|
|
826
|
+
process.stdout.write("\n");
|
|
827
|
+
}
|
|
828
|
+
console.log(`
|
|
829
|
+
\u2713 Workflow '${workflow.name}' complete.
|
|
830
|
+
`);
|
|
831
|
+
}
|
|
832
|
+
async function update() {
|
|
833
|
+
const current = azadVersion();
|
|
834
|
+
process.stdout.write("Checking npm for the latest version\u2026 ");
|
|
835
|
+
const latest = await getLatestVersion();
|
|
836
|
+
if (!latest) {
|
|
837
|
+
console.log("could not reach the registry.");
|
|
838
|
+
process.exit(1);
|
|
839
|
+
}
|
|
840
|
+
if (!isNewerVersion(latest, current)) {
|
|
841
|
+
console.log(`already up to date (v${current}).`);
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
console.log(`updating ${current} \u2192 ${latest}`);
|
|
845
|
+
const child = spawn("npm", ["install", "-g", `${CLI_PACKAGE}@latest`], { stdio: "inherit" });
|
|
846
|
+
const code = await new Promise((resolve) => child.on("exit", (c) => resolve(c ?? 0)));
|
|
847
|
+
if (code === 0) console.log(`Updated to v${latest}.`);
|
|
848
|
+
process.exit(code);
|
|
849
|
+
}
|
|
296
850
|
function printHelp() {
|
|
297
851
|
console.log(
|
|
298
852
|
[
|
|
@@ -304,8 +858,18 @@ function printHelp() {
|
|
|
304
858
|
" azad agents show <name> show one agent",
|
|
305
859
|
" azad agents new <name> scaffold a new agent in ~/.azad/agents",
|
|
306
860
|
" azad agents edit <name> edit an agent ($EDITOR; copies a default into the workspace)",
|
|
861
|
+
" azad workflow list list workflows",
|
|
862
|
+
" azad workflow show <name> show a workflow's steps",
|
|
863
|
+
" azad workflow new <name> scaffold a workflow in ~/.azad/workflows",
|
|
864
|
+
" azad workflow edit <name> edit a workflow (add/remove agents under steps:)",
|
|
865
|
+
" azad workflow run <name> run a workflow (--model <id> to override the model)",
|
|
307
866
|
" azad auth status|set|remove manage the Anthropic credential (~/.azad/auth.json)",
|
|
308
|
-
" azad version print version"
|
|
867
|
+
" azad version print version",
|
|
868
|
+
" azad update update to the latest published version",
|
|
869
|
+
"",
|
|
870
|
+
"inside the TUI (type in azad):",
|
|
871
|
+
" /login connect a provider /model switch model /hotkeys shortcuts",
|
|
872
|
+
" /mode approval mode (alt+a) /ask \xB7 /auto /help commands"
|
|
309
873
|
].join("\n")
|
|
310
874
|
);
|
|
311
875
|
}
|
|
@@ -313,12 +877,18 @@ async function main() {
|
|
|
313
877
|
const [cmd, sub, arg] = process.argv.slice(2);
|
|
314
878
|
switch (cmd) {
|
|
315
879
|
case void 0:
|
|
316
|
-
await runInteractive();
|
|
880
|
+
await runInteractive({ version: azadVersion() });
|
|
317
881
|
return;
|
|
318
882
|
case "version":
|
|
319
883
|
case "--version":
|
|
320
|
-
case "-v":
|
|
321
|
-
console.log(`Azad73 (
|
|
884
|
+
case "-v": {
|
|
885
|
+
console.log(`Azad73 v${azadVersion()} (Pi SDK ${PI_VERSION})`);
|
|
886
|
+
const latest = cachedUpdate(azadVersion());
|
|
887
|
+
if (latest) console.log(`Update available: ${azadVersion()} \u2192 ${latest} \xB7 run \`azad update\``);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
case "update":
|
|
891
|
+
await update();
|
|
322
892
|
return;
|
|
323
893
|
case "help":
|
|
324
894
|
case "--help":
|
|
@@ -339,6 +909,37 @@ async function main() {
|
|
|
339
909
|
else listAgents(agents);
|
|
340
910
|
return;
|
|
341
911
|
}
|
|
912
|
+
case "workflow":
|
|
913
|
+
case "workflows": {
|
|
914
|
+
if (sub === "new") {
|
|
915
|
+
newWorkflow(arg);
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
if (sub === "edit") {
|
|
919
|
+
await editWorkflow(arg);
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (sub === "run") {
|
|
923
|
+
const rest = process.argv.slice(5);
|
|
924
|
+
let modelId;
|
|
925
|
+
const goalParts = [];
|
|
926
|
+
for (let i = 0; i < rest.length; i++) {
|
|
927
|
+
if (rest[i] === "--model") {
|
|
928
|
+
modelId = rest[i + 1];
|
|
929
|
+
i++;
|
|
930
|
+
} else {
|
|
931
|
+
const part = rest[i];
|
|
932
|
+
if (part) goalParts.push(part);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
await runWorkflow(arg, goalParts.join(" "), modelId);
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
const workflows = discoverWorkflows(workflowDirs());
|
|
939
|
+
if (sub === "show") showWorkflow(workflows, arg);
|
|
940
|
+
else listWorkflows(workflows);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
342
943
|
case "auth":
|
|
343
944
|
await authCmd(sub);
|
|
344
945
|
return;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bugfix
|
|
3
|
+
description: Bug lifecycle — reproduce, find root cause, fix, review, document.
|
|
4
|
+
approval: between-steps
|
|
5
|
+
steps:
|
|
6
|
+
- agent: bug-reproduction-helper
|
|
7
|
+
- agent: root-cause-investigator
|
|
8
|
+
- agent: bug-fixer
|
|
9
|
+
- agent: bugfix-mr-reviewer
|
|
10
|
+
- agent: ticket-doc-writer
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
Run on a confirmed bug. Each step passes its output to the next.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: feature
|
|
3
|
+
description: Feature lifecycle — build, review, document.
|
|
4
|
+
approval: between-steps
|
|
5
|
+
steps:
|
|
6
|
+
- agent: feature-builder
|
|
7
|
+
- agent: feature-mr-reviewer
|
|
8
|
+
- agent: ticket-doc-writer
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Run from acceptance criteria. Each step passes its output to the next.
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@azad-73/cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Azad73 — agentic AI platform CLI (branded TUI + agents) built on the Pi SDK",
|
|
5
|
-
"license": "
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"azad": "./dist/azad.js"
|