ai-whisper 0.1.2 → 0.1.4
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 +18 -0
- package/dist/bin/broker-daemon.js +27 -3
- package/dist/bin/companion-agent.js +27 -3
- package/dist/bin/relay-monitor.js +30 -4
- package/dist/bin/whisper.js +114 -6
- package/package.json +20 -4
package/README.md
CHANGED
|
@@ -50,6 +50,20 @@ It is **not** for:
|
|
|
50
50
|
- invisible background automation you never watch.
|
|
51
51
|
- people new to coding agents looking for a guided, hand-holding experience.
|
|
52
52
|
|
|
53
|
+
## Prerequisites
|
|
54
|
+
|
|
55
|
+
ai-whisper drives the *real* Claude and Codex CLIs, so you need both installed and authenticated first:
|
|
56
|
+
|
|
57
|
+
- **[Claude Code CLI](https://claude.com/claude-code)** — the `claude` command, signed in.
|
|
58
|
+
- **[Codex CLI](https://github.com/openai/codex)** — the `codex` command, signed in.
|
|
59
|
+
- **Node.js 22+**.
|
|
60
|
+
- **An LLM evaluator with credentials** — workflows are gated by it and refuse to start without it. See [Evaluator configuration](docs/evaluator-configuration.md).
|
|
61
|
+
- **tmux** *(optional)* — only for `whisper collab start`, which auto-arranges both agents into panes. The mount flow below does not need it.
|
|
62
|
+
|
|
63
|
+
## Safety & permissions
|
|
64
|
+
|
|
65
|
+
ai-whisper launches each agent in **full-autonomy mode** so the relay can drive it unattended — `claude --dangerously-skip-permissions` and `codex --dangerously-bypass-approvals-and-sandbox`. Inside the mounted workspace the agents read, write, and run commands without prompting. Point it at code you're willing to let two agents change autonomously, watch the run on the dashboard, and remember you are the final gatekeeper — review the result before you ship it. The deeper rationale is in [Concepts](docs/concepts.md).
|
|
66
|
+
|
|
53
67
|
## Quickstart
|
|
54
68
|
|
|
55
69
|
Install from npm:
|
|
@@ -90,6 +104,10 @@ whisper collab dashboard
|
|
|
90
104
|
|
|
91
105
|
> Running from a repo checkout instead of a packaged install? Build first (`pnpm build`) and invoke the CLI as `node packages/cli/dist/bin/whisper.js ...` wherever these examples say `whisper ...`.
|
|
92
106
|
|
|
107
|
+
## What happens if it fails?
|
|
108
|
+
|
|
109
|
+
A run that stops short usually **escalates** — it does not crash. When the evaluator can't resolve a phase (the round budget is spent, an agent reports it's blocked, or confidence stays too low), the loop halts and turn ownership returns to you. That's a designed exit, not a failure: run state is durable, so you read the dashboard, fix the spec or unblock the agent, and `whisper workflow resume <id>` to pick up where it left off. Escalation is the system asking for a human exactly when it should — seeing it is normal, not a sign something broke.
|
|
110
|
+
|
|
93
111
|
## Core concepts
|
|
94
112
|
|
|
95
113
|
ai-whisper is **not a swarm**. The agents never type at once — work moves by a single baton, one owner at a time. Mounted sessions are *real* Claude and Codex sessions in your terminal, and those sessions are the source of truth. Autonomy is supervised: every handoff, verdict, and round is inspectable, and runs are resumable rather than fire-and-forget. Work is organized as structured workflows — explicit loops and state transitions, not a free-form chat.
|
|
@@ -1702,7 +1702,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1702
1702
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1703
1703
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1704
1704
|
const out = [];
|
|
1705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1705
1706
|
for (const e of eligible) {
|
|
1707
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1708
|
+
seen.add(e.collabId);
|
|
1709
|
+
}
|
|
1710
|
+
const minResults = input.minResults ?? 3;
|
|
1711
|
+
if (out.length < minResults) {
|
|
1712
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1713
|
+
FROM workflows
|
|
1714
|
+
WHERE status IN ('done','halted','canceled')
|
|
1715
|
+
GROUP BY collab_id
|
|
1716
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1717
|
+
for (const f of finished) {
|
|
1718
|
+
if (out.length >= minResults)
|
|
1719
|
+
break;
|
|
1720
|
+
if (seen.has(f.collabId))
|
|
1721
|
+
continue;
|
|
1722
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1723
|
+
seen.add(f.collabId);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return out;
|
|
1727
|
+
}
|
|
1728
|
+
function buildCollabSummary(db, collabId2) {
|
|
1729
|
+
{
|
|
1730
|
+
const e = { collabId: collabId2 };
|
|
1706
1731
|
const collab2 = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1707
1732
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1708
1733
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1756,7 +1781,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1756
1781
|
FROM relay_handoff
|
|
1757
1782
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1758
1783
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1759
|
-
|
|
1784
|
+
return {
|
|
1760
1785
|
collabId: e.collabId,
|
|
1761
1786
|
label,
|
|
1762
1787
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1775,9 +1800,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1775
1800
|
},
|
|
1776
1801
|
sessions,
|
|
1777
1802
|
lastActivityAt: runLastAct
|
|
1778
|
-
}
|
|
1803
|
+
};
|
|
1779
1804
|
}
|
|
1780
|
-
return out;
|
|
1781
1805
|
}
|
|
1782
1806
|
function listWorkflowsForCollab(db, collabId2) {
|
|
1783
1807
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1735,7 +1735,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1735
1735
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1736
1736
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1737
1737
|
const out = [];
|
|
1738
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1738
1739
|
for (const e of eligible) {
|
|
1740
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1741
|
+
seen.add(e.collabId);
|
|
1742
|
+
}
|
|
1743
|
+
const minResults = input.minResults ?? 3;
|
|
1744
|
+
if (out.length < minResults) {
|
|
1745
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1746
|
+
FROM workflows
|
|
1747
|
+
WHERE status IN ('done','halted','canceled')
|
|
1748
|
+
GROUP BY collab_id
|
|
1749
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1750
|
+
for (const f of finished) {
|
|
1751
|
+
if (out.length >= minResults)
|
|
1752
|
+
break;
|
|
1753
|
+
if (seen.has(f.collabId))
|
|
1754
|
+
continue;
|
|
1755
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1756
|
+
seen.add(f.collabId);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return out;
|
|
1760
|
+
}
|
|
1761
|
+
function buildCollabSummary(db, collabId) {
|
|
1762
|
+
{
|
|
1763
|
+
const e = { collabId };
|
|
1739
1764
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1740
1765
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1741
1766
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1789,7 +1814,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1789
1814
|
FROM relay_handoff
|
|
1790
1815
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1791
1816
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1792
|
-
|
|
1817
|
+
return {
|
|
1793
1818
|
collabId: e.collabId,
|
|
1794
1819
|
label,
|
|
1795
1820
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1808,9 +1833,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1808
1833
|
},
|
|
1809
1834
|
sessions,
|
|
1810
1835
|
lastActivityAt: runLastAct
|
|
1811
|
-
}
|
|
1836
|
+
};
|
|
1812
1837
|
}
|
|
1813
|
-
return out;
|
|
1814
1838
|
}
|
|
1815
1839
|
function listWorkflowsForCollab(db, collabId) {
|
|
1816
1840
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1705,7 +1705,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1705
1705
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1706
1706
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1707
1707
|
const out = [];
|
|
1708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1708
1709
|
for (const e of eligible) {
|
|
1710
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1711
|
+
seen.add(e.collabId);
|
|
1712
|
+
}
|
|
1713
|
+
const minResults = input.minResults ?? 3;
|
|
1714
|
+
if (out.length < minResults) {
|
|
1715
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1716
|
+
FROM workflows
|
|
1717
|
+
WHERE status IN ('done','halted','canceled')
|
|
1718
|
+
GROUP BY collab_id
|
|
1719
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1720
|
+
for (const f of finished) {
|
|
1721
|
+
if (out.length >= minResults)
|
|
1722
|
+
break;
|
|
1723
|
+
if (seen.has(f.collabId))
|
|
1724
|
+
continue;
|
|
1725
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1726
|
+
seen.add(f.collabId);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return out;
|
|
1730
|
+
}
|
|
1731
|
+
function buildCollabSummary(db, collabId2) {
|
|
1732
|
+
{
|
|
1733
|
+
const e = { collabId: collabId2 };
|
|
1709
1734
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1710
1735
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1711
1736
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1759,7 +1784,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1759
1784
|
FROM relay_handoff
|
|
1760
1785
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1761
1786
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1762
|
-
|
|
1787
|
+
return {
|
|
1763
1788
|
collabId: e.collabId,
|
|
1764
1789
|
label,
|
|
1765
1790
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1778,9 +1803,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1778
1803
|
},
|
|
1779
1804
|
sessions,
|
|
1780
1805
|
lastActivityAt: runLastAct
|
|
1781
|
-
}
|
|
1806
|
+
};
|
|
1782
1807
|
}
|
|
1783
|
-
return out;
|
|
1784
1808
|
}
|
|
1785
1809
|
function listWorkflowsForCollab(db, collabId2) {
|
|
1786
1810
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -5076,7 +5100,9 @@ function computeLiveness(snap) {
|
|
|
5076
5100
|
let why = null;
|
|
5077
5101
|
let stuck = false;
|
|
5078
5102
|
let liveOverride = null;
|
|
5079
|
-
if (terminal === "
|
|
5103
|
+
if (terminal === "done") {
|
|
5104
|
+
return { stuck: false, why: null, liveText: "done" };
|
|
5105
|
+
} else if (terminal === "halted" || terminal === "canceled") {
|
|
5080
5106
|
stuck = true;
|
|
5081
5107
|
why = snap.workflow?.haltReason ? `${terminal}: ${snap.workflow.haltReason}` : `workflow ${terminal}`;
|
|
5082
5108
|
} else if (chainStatus === "escalated" || chainStatus === "abandoned") {
|
package/dist/bin/whisper.js
CHANGED
|
@@ -14,6 +14,7 @@ function loadDotEnv() {
|
|
|
14
14
|
|
|
15
15
|
// src/create-cli.ts
|
|
16
16
|
import { execSync as execSync4 } from "node:child_process";
|
|
17
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
17
18
|
import { Command, Option } from "commander";
|
|
18
19
|
|
|
19
20
|
// ../broker/dist/config.js
|
|
@@ -1775,7 +1776,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1775
1776
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1776
1777
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1777
1778
|
const out = [];
|
|
1779
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1778
1780
|
for (const e of eligible) {
|
|
1781
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1782
|
+
seen.add(e.collabId);
|
|
1783
|
+
}
|
|
1784
|
+
const minResults = input.minResults ?? 3;
|
|
1785
|
+
if (out.length < minResults) {
|
|
1786
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1787
|
+
FROM workflows
|
|
1788
|
+
WHERE status IN ('done','halted','canceled')
|
|
1789
|
+
GROUP BY collab_id
|
|
1790
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1791
|
+
for (const f of finished) {
|
|
1792
|
+
if (out.length >= minResults)
|
|
1793
|
+
break;
|
|
1794
|
+
if (seen.has(f.collabId))
|
|
1795
|
+
continue;
|
|
1796
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1797
|
+
seen.add(f.collabId);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return out;
|
|
1801
|
+
}
|
|
1802
|
+
function buildCollabSummary(db, collabId) {
|
|
1803
|
+
{
|
|
1804
|
+
const e = { collabId };
|
|
1779
1805
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1780
1806
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1781
1807
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1829,7 +1855,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1829
1855
|
FROM relay_handoff
|
|
1830
1856
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1831
1857
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1832
|
-
|
|
1858
|
+
return {
|
|
1833
1859
|
collabId: e.collabId,
|
|
1834
1860
|
label,
|
|
1835
1861
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1848,9 +1874,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1848
1874
|
},
|
|
1849
1875
|
sessions,
|
|
1850
1876
|
lastActivityAt: runLastAct
|
|
1851
|
-
}
|
|
1877
|
+
};
|
|
1852
1878
|
}
|
|
1853
|
-
return out;
|
|
1854
1879
|
}
|
|
1855
1880
|
function listWorkflowsForCollab(db, collabId) {
|
|
1856
1881
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -9165,7 +9190,9 @@ function computeLiveness(snap) {
|
|
|
9165
9190
|
let why = null;
|
|
9166
9191
|
let stuck = false;
|
|
9167
9192
|
let liveOverride = null;
|
|
9168
|
-
if (terminal === "
|
|
9193
|
+
if (terminal === "done") {
|
|
9194
|
+
return { stuck: false, why: null, liveText: "done" };
|
|
9195
|
+
} else if (terminal === "halted" || terminal === "canceled") {
|
|
9169
9196
|
stuck = true;
|
|
9170
9197
|
why = snap.workflow?.haltReason ? `${terminal}: ${snap.workflow.haltReason}` : `workflow ${terminal}`;
|
|
9171
9198
|
} else if (chainStatus === "escalated" || chainStatus === "abandoned") {
|
|
@@ -9946,6 +9973,8 @@ function createDashboardRuntime(input) {
|
|
|
9946
9973
|
let lastSig = null;
|
|
9947
9974
|
let renderCount = 0;
|
|
9948
9975
|
let recycles = 0;
|
|
9976
|
+
let lastRenderedMode = "wall";
|
|
9977
|
+
let clearCount = 0;
|
|
9949
9978
|
const recycleEvery = Math.max(1, input.__recycleEveryRenders ?? 750);
|
|
9950
9979
|
function toPhaseRuns(raw) {
|
|
9951
9980
|
return raw.map((p) => ({
|
|
@@ -10151,6 +10180,11 @@ function createDashboardRuntime(input) {
|
|
|
10151
10180
|
lastSig = pendingSig;
|
|
10152
10181
|
renderCount += 1;
|
|
10153
10182
|
const full = createElement2(DashboardApp, { node: el, onKey: handleKey });
|
|
10183
|
+
if (mode !== lastRenderedMode) {
|
|
10184
|
+
ink.clear();
|
|
10185
|
+
clearCount += 1;
|
|
10186
|
+
lastRenderedMode = mode;
|
|
10187
|
+
}
|
|
10154
10188
|
if (renderCount % recycleEvery === 0) {
|
|
10155
10189
|
ink.unmount();
|
|
10156
10190
|
ink = render2(full, inkOptions);
|
|
@@ -10260,7 +10294,8 @@ function createDashboardRuntime(input) {
|
|
|
10260
10294
|
lastError: lastPollError
|
|
10261
10295
|
}),
|
|
10262
10296
|
__renderCount: () => renderCount,
|
|
10263
|
-
__recycles: () => recycles
|
|
10297
|
+
__recycles: () => recycles,
|
|
10298
|
+
__clears: () => clearCount
|
|
10264
10299
|
};
|
|
10265
10300
|
}
|
|
10266
10301
|
|
|
@@ -11006,8 +11041,18 @@ async function connectToWorkspaceBroker(input) {
|
|
|
11006
11041
|
function collectArtifact(value, previous = []) {
|
|
11007
11042
|
return [...previous, value];
|
|
11008
11043
|
}
|
|
11044
|
+
function resolveCliVersion() {
|
|
11045
|
+
for (const rel of ["../../package.json", "../package.json"]) {
|
|
11046
|
+
try {
|
|
11047
|
+
const pkg = JSON.parse(readFileSync3(new URL(rel, import.meta.url), "utf8"));
|
|
11048
|
+
if (pkg.name === "ai-whisper" && typeof pkg.version === "string") return pkg.version;
|
|
11049
|
+
} catch {
|
|
11050
|
+
}
|
|
11051
|
+
}
|
|
11052
|
+
return "0.0.0-dev";
|
|
11053
|
+
}
|
|
11009
11054
|
function createCli() {
|
|
11010
|
-
const cli = new Command().name("whisper").description("ai-whisper CLI");
|
|
11055
|
+
const cli = new Command().name("whisper").description("ai-whisper CLI").version(resolveCliVersion(), "-v, --version", "output the version number");
|
|
11011
11056
|
const collab = cli.command("collab").description("Manage AI agent collaboration sessions");
|
|
11012
11057
|
collab.command("start").description("Start a new collaboration session").option("--workspace <path>", "Workspace root", process.cwd()).option("--no-tmux", "Disable tmux even if available").option("--no-launch", "Start broker only, do not launch agent terminals").option(
|
|
11013
11058
|
"--port <port>",
|
|
@@ -11310,6 +11355,69 @@ Not attached (stdin/stdout is not a TTY). To view the tmux session, run:
|
|
|
11310
11355
|
return cli;
|
|
11311
11356
|
}
|
|
11312
11357
|
|
|
11358
|
+
// src/runtime/version-check.ts
|
|
11359
|
+
var PKG = "ai-whisper";
|
|
11360
|
+
var REGISTRY_LATEST = `https://registry.npmjs.org/${PKG}/latest`;
|
|
11361
|
+
var DEFAULT_TIMEOUT_MS = 1500;
|
|
11362
|
+
function coreParts(v) {
|
|
11363
|
+
const core = v.split("-")[0] ?? "";
|
|
11364
|
+
const [a, b, c] = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
|
|
11365
|
+
return [a ?? 0, b ?? 0, c ?? 0];
|
|
11366
|
+
}
|
|
11367
|
+
function isNewerVersion(candidate, current) {
|
|
11368
|
+
const cand = coreParts(candidate);
|
|
11369
|
+
const cur = coreParts(current);
|
|
11370
|
+
for (let i = 0; i < 3; i++) {
|
|
11371
|
+
if (cand[i] > cur[i]) return true;
|
|
11372
|
+
if (cand[i] < cur[i]) return false;
|
|
11373
|
+
}
|
|
11374
|
+
const candPre = candidate.includes("-");
|
|
11375
|
+
const curPre = current.includes("-");
|
|
11376
|
+
if (candPre !== curPre) return !candPre;
|
|
11377
|
+
return false;
|
|
11378
|
+
}
|
|
11379
|
+
function formatVersionReport(current, latest) {
|
|
11380
|
+
if (latest && isNewerVersion(latest, current)) {
|
|
11381
|
+
return `${current}
|
|
11382
|
+
A newer version is available: ${current} \u2192 ${latest} \xB7 update: npm install -g ${PKG}@latest`;
|
|
11383
|
+
}
|
|
11384
|
+
return current;
|
|
11385
|
+
}
|
|
11386
|
+
async function fetchLatestVersion(opts) {
|
|
11387
|
+
const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
11388
|
+
const f = opts?.fetchImpl ?? fetch;
|
|
11389
|
+
const ac = new AbortController();
|
|
11390
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
11391
|
+
try {
|
|
11392
|
+
const res = await f(REGISTRY_LATEST, { signal: ac.signal });
|
|
11393
|
+
if (!res.ok) return null;
|
|
11394
|
+
const body = await res.json();
|
|
11395
|
+
return typeof body.version === "string" ? body.version : null;
|
|
11396
|
+
} catch {
|
|
11397
|
+
return null;
|
|
11398
|
+
} finally {
|
|
11399
|
+
clearTimeout(timer);
|
|
11400
|
+
}
|
|
11401
|
+
}
|
|
11402
|
+
async function reportVersion(input) {
|
|
11403
|
+
const write = input.write ?? ((line) => process.stdout.write(`${line}
|
|
11404
|
+
`));
|
|
11405
|
+
if (input.disabled) {
|
|
11406
|
+
write(input.current);
|
|
11407
|
+
return;
|
|
11408
|
+
}
|
|
11409
|
+
const latest = await (input.fetchLatest ? input.fetchLatest() : fetchLatestVersion());
|
|
11410
|
+
write(formatVersionReport(input.current, latest));
|
|
11411
|
+
}
|
|
11412
|
+
|
|
11313
11413
|
// src/bin/whisper.ts
|
|
11314
11414
|
loadDotEnv();
|
|
11415
|
+
var argv = process.argv.slice(2);
|
|
11416
|
+
if (argv.includes("-v") || argv.includes("--version")) {
|
|
11417
|
+
await reportVersion({
|
|
11418
|
+
current: resolveCliVersion(),
|
|
11419
|
+
disabled: process.env.AI_WHISPER_NO_UPDATE_CHECK === "1"
|
|
11420
|
+
});
|
|
11421
|
+
process.exit(0);
|
|
11422
|
+
}
|
|
11315
11423
|
await createCli().parseAsync(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-whisper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Terminal-first relay for paired AI coding agents (Claude + Codex), driven by structured workflows.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,22 @@
|
|
|
8
8
|
"url": "https://github.com/ai-creed/ai-whisper.git"
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://ai-creed.dev/projects/ai-whisper/",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"ai-agents",
|
|
13
|
+
"claude",
|
|
14
|
+
"codex",
|
|
15
|
+
"coding-agent",
|
|
16
|
+
"cli",
|
|
17
|
+
"developer-tools",
|
|
18
|
+
"tmux",
|
|
19
|
+
"spec-driven-development",
|
|
20
|
+
"agent-orchestration",
|
|
21
|
+
"pair-programming",
|
|
22
|
+
"typescript"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=22"
|
|
26
|
+
},
|
|
11
27
|
"type": "module",
|
|
12
28
|
"bin": {
|
|
13
29
|
"whisper": "./dist/bin/whisper.js",
|
|
@@ -30,10 +46,10 @@
|
|
|
30
46
|
"@types/react": "^19.2.14",
|
|
31
47
|
"ink-testing-library": "^4.0.0",
|
|
32
48
|
"@ai-whisper/adapter-claude": "0.0.0",
|
|
33
|
-
"@ai-whisper/
|
|
34
|
-
"@ai-whisper/companion-core": "0.0.0",
|
|
49
|
+
"@ai-whisper/adapter-codex": "0.0.0",
|
|
35
50
|
"@ai-whisper/shared": "0.0.0",
|
|
36
|
-
"@ai-whisper/
|
|
51
|
+
"@ai-whisper/companion-core": "0.0.0",
|
|
52
|
+
"@ai-whisper/broker": "0.0.0"
|
|
37
53
|
},
|
|
38
54
|
"files": [
|
|
39
55
|
"dist",
|