@azad-73/cli 0.1.0 → 0.2.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/LICENSE +10 -0
- package/README.md +70 -0
- package/dist/azad.js +624 -46
- 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,323 @@ 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 ANIM_STYLE = "wipe";
|
|
277
|
+
var ANIM_MS = 750;
|
|
278
|
+
function animateLogoCell(theme, rowText, progress) {
|
|
279
|
+
const accent = (s) => theme.fg("accent", s);
|
|
280
|
+
const glow = (s) => theme.fg("borderAccent", s);
|
|
281
|
+
const logoFull = rowText.padEnd(LOGO_W);
|
|
282
|
+
if (progress >= 1) return accent(logoFull);
|
|
283
|
+
if (ANIM_STYLE === "wipe") {
|
|
284
|
+
const visible = logoFull.slice(0, Math.round(progress * LOGO_W));
|
|
285
|
+
const body = visible.slice(0, Math.max(0, visible.length - 1));
|
|
286
|
+
const edge = visible.slice(Math.max(0, visible.length - 1));
|
|
287
|
+
return `${accent(body)}${glow(edge)}${" ".repeat(LOGO_W - visible.length)}`;
|
|
288
|
+
}
|
|
289
|
+
const band = 2;
|
|
290
|
+
const sweep = progress * (LOGO_W + 2 * band) - band;
|
|
291
|
+
const start = Math.max(0, Math.min(LOGO_W, Math.round(sweep - band)));
|
|
292
|
+
const end = Math.max(0, Math.min(LOGO_W, Math.round(sweep + band) + 1));
|
|
293
|
+
return `${accent(logoFull.slice(0, start))}${glow(logoFull.slice(start, end))}${accent(logoFull.slice(end))}`;
|
|
294
|
+
}
|
|
295
|
+
function boxedHeader(theme, version, width, progress) {
|
|
98
296
|
const accent = (s) => theme.fg("accent", s);
|
|
99
|
-
const muted = (s) => theme.fg("muted", s);
|
|
100
297
|
const dim = (s) => theme.fg("dim", s);
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
298
|
+
const muted = (s) => theme.fg("muted", s);
|
|
299
|
+
const text = (s) => theme.fg("text", s);
|
|
300
|
+
const inner = Math.max(54, Math.min(width - 2, 72));
|
|
301
|
+
const verText = `v${version}`;
|
|
302
|
+
const titlePlain = ` Azad73 ${verText} `;
|
|
303
|
+
const titleColored = ` ${accent("Azad73")} ${dim(verText)} `;
|
|
304
|
+
const dashAfter = Math.max(2, inner - 1 - titlePlain.length);
|
|
305
|
+
const top = `${accent("\u256D\u2500")}${titleColored}${accent("\u2500".repeat(dashAfter))}${accent("\u256E")}`;
|
|
306
|
+
const bottom = accent(`\u2570${"\u2500".repeat(inner)}\u256F`);
|
|
307
|
+
const row = (colored, plainLen) => `${accent("\u2502")}${colored}${" ".repeat(Math.max(0, inner - plainLen))}${accent("\u2502")}`;
|
|
308
|
+
const tipBudget = inner - 2 - LOGO_W - LOGO_GAP;
|
|
309
|
+
const tips = [{ colored: muted("Tips"), plainLen: 4 }];
|
|
310
|
+
for (const t of TIP_ITEMS) {
|
|
311
|
+
const fixed = 2 + t.cmd.length + 1;
|
|
312
|
+
const desc = fixed + t.desc.length > tipBudget ? t.desc.slice(0, Math.max(0, tipBudget - fixed)) : t.desc;
|
|
313
|
+
tips.push({ colored: `${dim("\u2022")} ${accent(t.cmd)} ${text(desc)}`, plainLen: fixed + desc.length });
|
|
314
|
+
}
|
|
315
|
+
const blank = row("", 0);
|
|
316
|
+
const lines = [blank];
|
|
317
|
+
const rowCount = Math.max(LOGO.length, tips.length);
|
|
318
|
+
for (let i = 0; i < rowCount; i++) {
|
|
319
|
+
const cell = animateLogoCell(theme, LOGO[i] ?? "", progress);
|
|
320
|
+
const tip = tips[i] ?? { colored: "", plainLen: 0 };
|
|
321
|
+
lines.push(row(` ${cell}${" ".repeat(LOGO_GAP)}${tip.colored}`, 2 + LOGO_W + LOGO_GAP + tip.plainLen));
|
|
322
|
+
}
|
|
323
|
+
lines.push(blank);
|
|
324
|
+
return ["", top, ...lines, bottom, ""];
|
|
325
|
+
}
|
|
326
|
+
function createBrandingExtension(opts) {
|
|
327
|
+
return (pi) => {
|
|
328
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
329
|
+
if (ctx.mode !== "tui") return;
|
|
330
|
+
ctx.ui.setTitle("Azad73");
|
|
331
|
+
ctx.ui.setHeader((tui, theme) => {
|
|
332
|
+
const start = Date.now();
|
|
333
|
+
let timer;
|
|
334
|
+
const tick = () => {
|
|
335
|
+
if (Date.now() - start >= ANIM_MS && timer) {
|
|
336
|
+
clearInterval(timer);
|
|
337
|
+
timer = void 0;
|
|
338
|
+
}
|
|
339
|
+
tui.requestRender();
|
|
340
|
+
};
|
|
341
|
+
timer = setInterval(tick, 33);
|
|
342
|
+
return {
|
|
343
|
+
render(width) {
|
|
344
|
+
const progress = Math.min(1, (Date.now() - start) / ANIM_MS);
|
|
345
|
+
return boxedHeader(theme, opts.version, width, progress);
|
|
346
|
+
},
|
|
347
|
+
invalidate() {
|
|
348
|
+
},
|
|
349
|
+
dispose() {
|
|
350
|
+
if (timer) clearInterval(timer);
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
ctx.ui.setFooter((_tui, theme) => ({
|
|
355
|
+
render(width) {
|
|
356
|
+
const home = os2.homedir();
|
|
357
|
+
const cwd = ctx.cwd.startsWith(home) ? `~${ctx.cwd.slice(home.length)}` : ctx.cwd;
|
|
358
|
+
let cost = 0;
|
|
359
|
+
const entries = ctx.sessionManager.getBranch();
|
|
360
|
+
for (const entry of entries) {
|
|
361
|
+
if (entry.type === "message" && entry.message?.role === "assistant") {
|
|
362
|
+
cost += entry.message.usage?.cost?.total ?? 0;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const model = ctx.model?.id ?? "no model";
|
|
366
|
+
const level = pi.getThinkingLevel();
|
|
367
|
+
const mode = getApprovalMode();
|
|
368
|
+
const modeText = approvalModeLabel(mode);
|
|
369
|
+
const left = theme.fg("dim", `${cwd} \xB7 $${cost.toFixed(2)} session`);
|
|
370
|
+
const modeColored = mode === "auto" ? theme.fg("warning", modeText) : theme.fg("dim", modeText);
|
|
371
|
+
const right = `${theme.fg("dim", `${model} \xB7 ${level} \xB7 `)}${modeColored}`;
|
|
372
|
+
if (vlen(left) + vlen(right) + 1 > width) {
|
|
373
|
+
return [`${theme.fg("dim", `${model} \xB7 ${level} \xB7 `)}${modeColored}`];
|
|
374
|
+
}
|
|
375
|
+
const gap = Math.max(1, width - vlen(left) - vlen(right));
|
|
376
|
+
return [`${left}${" ".repeat(gap)}${right}`];
|
|
377
|
+
},
|
|
378
|
+
invalidate() {
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
void checkForUpdate(opts.version).then((latest) => {
|
|
382
|
+
if (latest) {
|
|
383
|
+
ctx.ui.notify(`Azad73 update available: ${opts.version} \u2192 ${latest} \xB7 run \`azad update\``, "info");
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
};
|
|
107
388
|
}
|
|
108
|
-
var brandingExtension = (pi) => {
|
|
109
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
110
|
-
if (ctx.mode !== "tui") return;
|
|
111
|
-
ctx.ui.setTitle("Azad73");
|
|
112
|
-
ctx.ui.setHeader((_tui, theme) => ({
|
|
113
|
-
render(_width) {
|
|
114
|
-
return brandBanner(theme);
|
|
115
|
-
},
|
|
116
|
-
invalidate() {
|
|
117
|
-
}
|
|
118
|
-
}));
|
|
119
|
-
});
|
|
120
|
-
};
|
|
121
389
|
|
|
122
390
|
// ../engine/src/runtime.ts
|
|
123
|
-
async function buildRuntime(workspace = defaultWorkspace(), sessionManager = SessionManager.create(workspace.rootDir)) {
|
|
391
|
+
async function buildRuntime(workspace = defaultWorkspace(), sessionManager = SessionManager.create(workspace.rootDir), options = {}) {
|
|
124
392
|
const { authStorage, modelRegistry } = createAuth(workspace);
|
|
393
|
+
const branding = createBrandingExtension({ version: options.version ?? "0.0.0" });
|
|
125
394
|
const createRuntime = async ({ cwd, sessionManager: sm, sessionStartEvent }) => {
|
|
395
|
+
const settingsManager = SettingsManager.create(cwd, workspace.agentDir);
|
|
396
|
+
if (!settingsManager.getQuietStartup()) settingsManager.setQuietStartup(true);
|
|
126
397
|
const services = await createAgentSessionServices({
|
|
127
398
|
cwd,
|
|
128
399
|
agentDir: workspace.agentDir,
|
|
129
400
|
authStorage,
|
|
130
401
|
modelRegistry,
|
|
402
|
+
settingsManager,
|
|
131
403
|
resourceLoaderOptions: {
|
|
132
|
-
extensionFactories: [
|
|
404
|
+
extensionFactories: [branding, createApprovalExtension()],
|
|
133
405
|
additionalThemePaths: [coreThemesDir()]
|
|
134
406
|
}
|
|
135
407
|
});
|
|
@@ -147,24 +419,103 @@ async function buildRuntime(workspace = defaultWorkspace(), sessionManager = Ses
|
|
|
147
419
|
}
|
|
148
420
|
|
|
149
421
|
// ../engine/src/tui.ts
|
|
150
|
-
async function runInteractive(
|
|
151
|
-
const
|
|
422
|
+
async function runInteractive(options = {}) {
|
|
423
|
+
const workspace = options.workspace ?? defaultWorkspace();
|
|
424
|
+
const runtime = await buildRuntime(workspace, void 0, { version: options.version });
|
|
152
425
|
const mode = new InteractiveMode(runtime);
|
|
153
426
|
await mode.run();
|
|
154
427
|
}
|
|
155
428
|
|
|
429
|
+
// ../engine/src/workflows.ts
|
|
430
|
+
import fs3 from "node:fs";
|
|
431
|
+
import path5 from "node:path";
|
|
432
|
+
import { parseFrontmatter as parseFrontmatter2 } from "@earendil-works/pi-coding-agent";
|
|
433
|
+
function discoverWorkflows(dirs) {
|
|
434
|
+
const byName = /* @__PURE__ */ new Map();
|
|
435
|
+
for (const dir of dirs) {
|
|
436
|
+
if (!fs3.existsSync(dir)) continue;
|
|
437
|
+
for (const entry of fs3.readdirSync(dir)) {
|
|
438
|
+
if (!entry.endsWith(".md")) continue;
|
|
439
|
+
const filePath = path5.join(dir, entry);
|
|
440
|
+
let content;
|
|
441
|
+
try {
|
|
442
|
+
content = fs3.readFileSync(filePath, "utf8");
|
|
443
|
+
} catch {
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
const { frontmatter } = parseFrontmatter2(content);
|
|
447
|
+
const name = frontmatter.name?.trim();
|
|
448
|
+
if (!name) continue;
|
|
449
|
+
const steps = (Array.isArray(frontmatter.steps) ? frontmatter.steps : []).map((s) => ({ agent: String(s?.agent ?? "").trim(), task: s?.task })).filter((s) => s.agent.length > 0);
|
|
450
|
+
byName.set(name, {
|
|
451
|
+
name,
|
|
452
|
+
description: frontmatter.description?.trim() ?? "",
|
|
453
|
+
approval: frontmatter.approval === "off" ? "off" : "between-steps",
|
|
454
|
+
steps,
|
|
455
|
+
filePath
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ../engine/src/workflow-run.ts
|
|
463
|
+
import { DefaultResourceLoader, SessionManager as SessionManager2, createAgentSession } from "@earendil-works/pi-coding-agent";
|
|
464
|
+
async function runAgentStep(agent, input, options = {}) {
|
|
465
|
+
const workspace = options.workspace ?? defaultWorkspace();
|
|
466
|
+
const { authStorage, modelRegistry } = createAuth(workspace);
|
|
467
|
+
const model = options.modelId ? modelRegistry.find("anthropic", options.modelId) ?? void 0 : void 0;
|
|
468
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
469
|
+
cwd: workspace.rootDir,
|
|
470
|
+
agentDir: workspace.agentDir,
|
|
471
|
+
systemPromptOverride: () => agent.systemPrompt,
|
|
472
|
+
noExtensions: true
|
|
473
|
+
});
|
|
474
|
+
await resourceLoader.reload();
|
|
475
|
+
const { session } = await createAgentSession({
|
|
476
|
+
cwd: workspace.rootDir,
|
|
477
|
+
agentDir: workspace.agentDir,
|
|
478
|
+
resourceLoader,
|
|
479
|
+
authStorage,
|
|
480
|
+
modelRegistry,
|
|
481
|
+
model,
|
|
482
|
+
tools: agent.tools,
|
|
483
|
+
sessionManager: SessionManager2.inMemory(workspace.rootDir)
|
|
484
|
+
});
|
|
485
|
+
let output = "";
|
|
486
|
+
const unsubscribe = session.subscribe((event) => {
|
|
487
|
+
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
488
|
+
output += event.assistantMessageEvent.delta;
|
|
489
|
+
options.onText?.(event.assistantMessageEvent.delta);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
await session.prompt(input);
|
|
493
|
+
unsubscribe();
|
|
494
|
+
session.dispose();
|
|
495
|
+
return output.trim();
|
|
496
|
+
}
|
|
497
|
+
|
|
156
498
|
// src/azad.ts
|
|
157
499
|
import { VERSION as PI_VERSION } from "@earendil-works/pi-coding-agent";
|
|
158
500
|
var PROVIDER = "anthropic";
|
|
159
501
|
function coreAgentsDir() {
|
|
160
502
|
return resolveCoreDataDir("agents", import.meta.url);
|
|
161
503
|
}
|
|
504
|
+
function azadVersion() {
|
|
505
|
+
try {
|
|
506
|
+
const pkgPath = path6.join(path6.dirname(fileURLToPath2(import.meta.url)), "..", "package.json");
|
|
507
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf8"));
|
|
508
|
+
return pkg.version ?? "0.0.0";
|
|
509
|
+
} catch {
|
|
510
|
+
return "0.0.0";
|
|
511
|
+
}
|
|
512
|
+
}
|
|
162
513
|
function agentDirs() {
|
|
163
514
|
const ws = defaultWorkspace();
|
|
164
|
-
return [coreAgentsDir(), workspaceAgentsDir(),
|
|
515
|
+
return [coreAgentsDir(), workspaceAgentsDir(), path6.join(ws.rootDir, ".azad", "agents")];
|
|
165
516
|
}
|
|
166
517
|
function workspaceAgentsDir() {
|
|
167
|
-
return
|
|
518
|
+
return path6.join(defaultWorkspace().agentDir, "agents");
|
|
168
519
|
}
|
|
169
520
|
var AGENT_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
170
521
|
function newAgent(name) {
|
|
@@ -177,12 +528,12 @@ function newAgent(name) {
|
|
|
177
528
|
process.exit(1);
|
|
178
529
|
}
|
|
179
530
|
const dir = workspaceAgentsDir();
|
|
180
|
-
const file =
|
|
181
|
-
if (
|
|
531
|
+
const file = path6.join(dir, `${name}.md`);
|
|
532
|
+
if (fs4.existsSync(file)) {
|
|
182
533
|
console.error(`agent already exists: ${file}`);
|
|
183
534
|
process.exit(1);
|
|
184
535
|
}
|
|
185
|
-
|
|
536
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
186
537
|
const template = [
|
|
187
538
|
"---",
|
|
188
539
|
`name: ${name}`,
|
|
@@ -195,7 +546,7 @@ function newAgent(name) {
|
|
|
195
546
|
"TODO: write the system prompt.",
|
|
196
547
|
""
|
|
197
548
|
].join("\n");
|
|
198
|
-
|
|
549
|
+
fs4.writeFileSync(file, template);
|
|
199
550
|
console.log(`created ${file}`);
|
|
200
551
|
console.log(`edit with: azad agents edit ${name}`);
|
|
201
552
|
}
|
|
@@ -204,16 +555,20 @@ async function editAgent(name) {
|
|
|
204
555
|
console.error("usage: azad agents edit <name>");
|
|
205
556
|
process.exit(1);
|
|
206
557
|
}
|
|
558
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
559
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens (e.g. my-agent)");
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
207
562
|
const dir = workspaceAgentsDir();
|
|
208
|
-
const target =
|
|
209
|
-
if (!
|
|
563
|
+
const target = path6.join(dir, `${name}.md`);
|
|
564
|
+
if (!fs4.existsSync(target)) {
|
|
210
565
|
const found = discoverAgents(agentDirs()).find((a) => a.name === name);
|
|
211
566
|
if (!found) {
|
|
212
567
|
console.error(`agent not found: ${name}`);
|
|
213
568
|
process.exit(1);
|
|
214
569
|
}
|
|
215
|
-
|
|
216
|
-
|
|
570
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
571
|
+
fs4.copyFileSync(found.filePath, target);
|
|
217
572
|
console.log(`copied default '${name}' into the workspace for editing: ${target}`);
|
|
218
573
|
}
|
|
219
574
|
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
@@ -265,7 +620,7 @@ function promptLine(question) {
|
|
|
265
620
|
}
|
|
266
621
|
async function authCmd(sub) {
|
|
267
622
|
const ws = defaultWorkspace();
|
|
268
|
-
const authPath =
|
|
623
|
+
const authPath = path6.join(ws.agentDir, "auth.json");
|
|
269
624
|
const { authStorage } = createAuth(ws);
|
|
270
625
|
if (sub === "status" || sub === void 0) {
|
|
271
626
|
const status = authStorage.getAuthStatus(PROVIDER);
|
|
@@ -280,7 +635,7 @@ async function authCmd(sub) {
|
|
|
280
635
|
console.error("no key provided");
|
|
281
636
|
process.exit(1);
|
|
282
637
|
}
|
|
283
|
-
|
|
638
|
+
fs4.mkdirSync(ws.agentDir, { recursive: true });
|
|
284
639
|
authStorage.set(PROVIDER, { type: "api_key", key });
|
|
285
640
|
console.log(`stored ${PROVIDER} key in ${authPath}`);
|
|
286
641
|
return;
|
|
@@ -293,6 +648,186 @@ async function authCmd(sub) {
|
|
|
293
648
|
console.error("usage: azad auth [status|set|remove]");
|
|
294
649
|
process.exit(1);
|
|
295
650
|
}
|
|
651
|
+
function workflowDirs() {
|
|
652
|
+
const ws = defaultWorkspace();
|
|
653
|
+
return [
|
|
654
|
+
resolveCoreDataDir("workflows", import.meta.url),
|
|
655
|
+
path6.join(ws.agentDir, "workflows"),
|
|
656
|
+
path6.join(ws.rootDir, ".azad", "workflows")
|
|
657
|
+
];
|
|
658
|
+
}
|
|
659
|
+
function workspaceWorkflowsDir() {
|
|
660
|
+
return path6.join(defaultWorkspace().agentDir, "workflows");
|
|
661
|
+
}
|
|
662
|
+
function listWorkflows(workflows) {
|
|
663
|
+
console.log(`Azad73 workflows (${workflows.length}):
|
|
664
|
+
`);
|
|
665
|
+
for (const w of workflows) {
|
|
666
|
+
console.log(` ${w.name.padEnd(14)} ${ellipsis(w.description, 60)}`);
|
|
667
|
+
console.log(` ${" ".repeat(14)} ${w.steps.map((s) => s.agent).join(" \u2192 ")}`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
function showWorkflow(workflows, name) {
|
|
671
|
+
if (!name) {
|
|
672
|
+
console.error("usage: azad workflow show <name>");
|
|
673
|
+
process.exit(1);
|
|
674
|
+
}
|
|
675
|
+
const workflow = workflows.find((w) => w.name === name);
|
|
676
|
+
if (!workflow) {
|
|
677
|
+
console.error(`workflow not found: ${name}`);
|
|
678
|
+
process.exit(1);
|
|
679
|
+
}
|
|
680
|
+
const known = new Set(discoverAgents(agentDirs()).map((a) => a.name));
|
|
681
|
+
console.log(`# ${workflow.name}`);
|
|
682
|
+
if (workflow.description) console.log(workflow.description);
|
|
683
|
+
console.log(`approval: ${workflow.approval}`);
|
|
684
|
+
console.log(`file: ${workflow.filePath}
|
|
685
|
+
`);
|
|
686
|
+
workflow.steps.forEach((step, i) => {
|
|
687
|
+
const missing = known.has(step.agent) ? "" : " (agent not found)";
|
|
688
|
+
console.log(` ${i + 1}. ${step.agent}${step.task ? ` \u2014 ${step.task}` : ""}${missing}`);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
function newWorkflow(name) {
|
|
692
|
+
if (!name) {
|
|
693
|
+
console.error("usage: azad workflow new <name>");
|
|
694
|
+
process.exit(1);
|
|
695
|
+
}
|
|
696
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
697
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens (e.g. my-flow)");
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
const dir = workspaceWorkflowsDir();
|
|
701
|
+
const file = path6.join(dir, `${name}.md`);
|
|
702
|
+
if (fs4.existsSync(file)) {
|
|
703
|
+
console.error(`workflow already exists: ${file}`);
|
|
704
|
+
process.exit(1);
|
|
705
|
+
}
|
|
706
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
707
|
+
const template = [
|
|
708
|
+
"---",
|
|
709
|
+
`name: ${name}`,
|
|
710
|
+
"description: TODO \u2014 what this workflow does.",
|
|
711
|
+
"approval: between-steps",
|
|
712
|
+
"steps:",
|
|
713
|
+
" - agent: context-cartographer",
|
|
714
|
+
"---",
|
|
715
|
+
"",
|
|
716
|
+
"TODO: describe the workflow. Add or remove steps under `steps:` (each is `- agent: <name>`).",
|
|
717
|
+
""
|
|
718
|
+
].join("\n");
|
|
719
|
+
fs4.writeFileSync(file, template);
|
|
720
|
+
console.log(`created ${file}`);
|
|
721
|
+
console.log(`edit with: azad workflow edit ${name}`);
|
|
722
|
+
}
|
|
723
|
+
async function editWorkflow(name) {
|
|
724
|
+
if (!name) {
|
|
725
|
+
console.error("usage: azad workflow edit <name>");
|
|
726
|
+
process.exit(1);
|
|
727
|
+
}
|
|
728
|
+
if (!AGENT_NAME_RE.test(name)) {
|
|
729
|
+
console.error("invalid name: use lowercase letters, digits, and single hyphens");
|
|
730
|
+
process.exit(1);
|
|
731
|
+
}
|
|
732
|
+
const dir = workspaceWorkflowsDir();
|
|
733
|
+
const target = path6.join(dir, `${name}.md`);
|
|
734
|
+
if (!fs4.existsSync(target)) {
|
|
735
|
+
const found = discoverWorkflows(workflowDirs()).find((w) => w.name === name);
|
|
736
|
+
if (!found) {
|
|
737
|
+
console.error(`workflow not found: ${name}`);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
741
|
+
fs4.copyFileSync(found.filePath, target);
|
|
742
|
+
console.log(`copied default '${name}' into the workspace for editing: ${target}`);
|
|
743
|
+
}
|
|
744
|
+
const editor = process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
745
|
+
await new Promise((resolve, reject) => {
|
|
746
|
+
const child = spawn(editor, [target], { stdio: "inherit" });
|
|
747
|
+
child.on("exit", () => resolve());
|
|
748
|
+
child.on("error", reject);
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
function buildStepInput(step, goal, previous, index) {
|
|
752
|
+
let task = step.task ?? "";
|
|
753
|
+
if (task.includes("{previous}")) task = task.replace(/\{previous\}/g, previous);
|
|
754
|
+
if (index === 0) return task || goal || "Begin.";
|
|
755
|
+
const parts = [];
|
|
756
|
+
if (task) parts.push(task);
|
|
757
|
+
if (goal) parts.push(`Overall goal: ${goal}`);
|
|
758
|
+
parts.push(`Output from the previous step:
|
|
759
|
+
|
|
760
|
+
${previous}`);
|
|
761
|
+
return parts.join("\n\n");
|
|
762
|
+
}
|
|
763
|
+
async function promptYesNo(question) {
|
|
764
|
+
const answer = (await promptLine(question)).trim().toLowerCase();
|
|
765
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
766
|
+
}
|
|
767
|
+
async function runWorkflow(name, goal, modelId) {
|
|
768
|
+
if (!name) {
|
|
769
|
+
console.error("usage: azad workflow run <name> [goal] [--model <id>]");
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
const workflow = discoverWorkflows(workflowDirs()).find((w) => w.name === name);
|
|
773
|
+
if (!workflow) {
|
|
774
|
+
console.error(`workflow not found: ${name}`);
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
const agentMap = new Map(discoverAgents(agentDirs()).map((a) => [a.name, a]));
|
|
778
|
+
const missing = [...new Set(workflow.steps.filter((s) => !agentMap.has(s.agent)).map((s) => s.agent))];
|
|
779
|
+
if (missing.length > 0) {
|
|
780
|
+
console.error(`workflow '${name}' references unknown agents: ${missing.join(", ")}`);
|
|
781
|
+
process.exit(1);
|
|
782
|
+
}
|
|
783
|
+
console.log(
|
|
784
|
+
`
|
|
785
|
+
Workflow: ${workflow.name} \xB7 ${workflow.steps.length} steps \xB7 approval: ${workflow.approval}${modelId ? ` \xB7 model: ${modelId}` : ""}
|
|
786
|
+
`
|
|
787
|
+
);
|
|
788
|
+
let previous = "";
|
|
789
|
+
for (let i = 0; i < workflow.steps.length; i++) {
|
|
790
|
+
const step = workflow.steps[i];
|
|
791
|
+
const agent = step ? agentMap.get(step.agent) : void 0;
|
|
792
|
+
if (!step || !agent) continue;
|
|
793
|
+
if (i > 0 && workflow.approval === "between-steps") {
|
|
794
|
+
const ok = await promptYesNo(`Proceed to step ${i + 1}/${workflow.steps.length} \xB7 ${step.agent}? [Y/n] `);
|
|
795
|
+
if (!ok) {
|
|
796
|
+
console.log("\nWorkflow stopped.");
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
console.log(`
|
|
801
|
+
\u2500\u2500\u2500 Step ${i + 1}/${workflow.steps.length} \xB7 ${step.agent} \u2500\u2500\u2500
|
|
802
|
+
`);
|
|
803
|
+
previous = await runAgentStep(agent, buildStepInput(step, goal, previous, i), {
|
|
804
|
+
modelId,
|
|
805
|
+
onText: (t) => process.stdout.write(t)
|
|
806
|
+
});
|
|
807
|
+
process.stdout.write("\n");
|
|
808
|
+
}
|
|
809
|
+
console.log(`
|
|
810
|
+
\u2713 Workflow '${workflow.name}' complete.
|
|
811
|
+
`);
|
|
812
|
+
}
|
|
813
|
+
async function update() {
|
|
814
|
+
const current = azadVersion();
|
|
815
|
+
process.stdout.write("Checking npm for the latest version\u2026 ");
|
|
816
|
+
const latest = await getLatestVersion();
|
|
817
|
+
if (!latest) {
|
|
818
|
+
console.log("could not reach the registry.");
|
|
819
|
+
process.exit(1);
|
|
820
|
+
}
|
|
821
|
+
if (!isNewerVersion(latest, current)) {
|
|
822
|
+
console.log(`already up to date (v${current}).`);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
console.log(`updating ${current} \u2192 ${latest}`);
|
|
826
|
+
const child = spawn("npm", ["install", "-g", `${CLI_PACKAGE}@latest`], { stdio: "inherit" });
|
|
827
|
+
const code = await new Promise((resolve) => child.on("exit", (c) => resolve(c ?? 0)));
|
|
828
|
+
if (code === 0) console.log(`Updated to v${latest}.`);
|
|
829
|
+
process.exit(code);
|
|
830
|
+
}
|
|
296
831
|
function printHelp() {
|
|
297
832
|
console.log(
|
|
298
833
|
[
|
|
@@ -304,8 +839,14 @@ function printHelp() {
|
|
|
304
839
|
" azad agents show <name> show one agent",
|
|
305
840
|
" azad agents new <name> scaffold a new agent in ~/.azad/agents",
|
|
306
841
|
" azad agents edit <name> edit an agent ($EDITOR; copies a default into the workspace)",
|
|
842
|
+
" azad workflow list list workflows",
|
|
843
|
+
" azad workflow show <name> show a workflow's steps",
|
|
844
|
+
" azad workflow new <name> scaffold a workflow in ~/.azad/workflows",
|
|
845
|
+
" azad workflow edit <name> edit a workflow (add/remove agents under steps:)",
|
|
846
|
+
" azad workflow run <name> run a workflow (--model <id> to override the model)",
|
|
307
847
|
" azad auth status|set|remove manage the Anthropic credential (~/.azad/auth.json)",
|
|
308
|
-
" azad version print version"
|
|
848
|
+
" azad version print version",
|
|
849
|
+
" azad update update to the latest published version"
|
|
309
850
|
].join("\n")
|
|
310
851
|
);
|
|
311
852
|
}
|
|
@@ -313,12 +854,18 @@ async function main() {
|
|
|
313
854
|
const [cmd, sub, arg] = process.argv.slice(2);
|
|
314
855
|
switch (cmd) {
|
|
315
856
|
case void 0:
|
|
316
|
-
await runInteractive();
|
|
857
|
+
await runInteractive({ version: azadVersion() });
|
|
317
858
|
return;
|
|
318
859
|
case "version":
|
|
319
860
|
case "--version":
|
|
320
|
-
case "-v":
|
|
321
|
-
console.log(`Azad73 (
|
|
861
|
+
case "-v": {
|
|
862
|
+
console.log(`Azad73 v${azadVersion()} (Pi SDK ${PI_VERSION})`);
|
|
863
|
+
const latest = cachedUpdate(azadVersion());
|
|
864
|
+
if (latest) console.log(`Update available: ${azadVersion()} \u2192 ${latest} \xB7 run \`azad update\``);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
case "update":
|
|
868
|
+
await update();
|
|
322
869
|
return;
|
|
323
870
|
case "help":
|
|
324
871
|
case "--help":
|
|
@@ -339,6 +886,37 @@ async function main() {
|
|
|
339
886
|
else listAgents(agents);
|
|
340
887
|
return;
|
|
341
888
|
}
|
|
889
|
+
case "workflow":
|
|
890
|
+
case "workflows": {
|
|
891
|
+
if (sub === "new") {
|
|
892
|
+
newWorkflow(arg);
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (sub === "edit") {
|
|
896
|
+
await editWorkflow(arg);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
if (sub === "run") {
|
|
900
|
+
const rest = process.argv.slice(5);
|
|
901
|
+
let modelId;
|
|
902
|
+
const goalParts = [];
|
|
903
|
+
for (let i = 0; i < rest.length; i++) {
|
|
904
|
+
if (rest[i] === "--model") {
|
|
905
|
+
modelId = rest[i + 1];
|
|
906
|
+
i++;
|
|
907
|
+
} else {
|
|
908
|
+
const part = rest[i];
|
|
909
|
+
if (part) goalParts.push(part);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
await runWorkflow(arg, goalParts.join(" "), modelId);
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
915
|
+
const workflows = discoverWorkflows(workflowDirs());
|
|
916
|
+
if (sub === "show") showWorkflow(workflows, arg);
|
|
917
|
+
else listWorkflows(workflows);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
342
920
|
case "auth":
|
|
343
921
|
await authCmd(sub);
|
|
344
922
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
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"
|