balladeer 0.0.3
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 +7 -0
- package/README.md +77 -0
- package/bin/balladeer.js +116 -0
- package/package.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026 Balladeer
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This package reserves the "balladeer" package name and points at the official
|
|
6
|
+
installer. It grants no rights to Balladeer's software or services. Use of the
|
|
7
|
+
Balladeer service is governed by Balladeer's Terms of Service.
|
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Balladeer
|
|
2
|
+
|
|
3
|
+
Balladeer makes your AI agents work like they have been on your team for years. It is
|
|
4
|
+
decision memory for engineering teams and their agents: it captures the decisions your
|
|
5
|
+
team makes while building (the question, the call, the why, the rejected options) and
|
|
6
|
+
returns them at the moment they matter: before an agent builds, when a choice comes up,
|
|
7
|
+
and when a new plan contradicts a settled call.
|
|
8
|
+
|
|
9
|
+
A real serving from Balladeer's own brain (2026-07-14, trimmed). The agent asked before
|
|
10
|
+
touching a webhook receiver, and the team's settled call came back with its certainty
|
|
11
|
+
and its why:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
⟢ balladeer · 2 decisions for "Add a rate limiter to the webhook receiver…"
|
|
15
|
+
|
|
16
|
+
decision decision_1a4cdf8b-f92: The remote-MCP per-org RPM cap is enforced FLEET-WIDE
|
|
17
|
+
via a shared DB counter (mcp_rate_counters: one row per (org, minute bucket), single
|
|
18
|
+
atomic INSERT ... ON CONFLICT ... SET hits=hits+1 RETURNING), replacing the per-process
|
|
19
|
+
Map that would have enforced N x the limit across N machines. […]
|
|
20
|
+
certainty: confirmed by a human
|
|
21
|
+
question it settles: How should the remote-MCP rate limit be enforced across multiple
|
|
22
|
+
Fly machines, and which parts of the guard should stay per-process?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
When the agent then makes a new call of its own, the decision is recorded back
|
|
26
|
+
(unconfirmed until a human confirms it) and surfaces to every future session that
|
|
27
|
+
touches it.
|
|
28
|
+
|
|
29
|
+
## What it does mechanically
|
|
30
|
+
|
|
31
|
+
- Your agent gains three habits over MCP: `get_context` before non-trivial work,
|
|
32
|
+
`consult` at a choice, `propose_decision` when a call gets made. Every decision
|
|
33
|
+
carries its certainty (human-confirmed vs machine-captured) and its receipts.
|
|
34
|
+
- Your team's decisions live on a shared central server; the `balladeer` CLI and the
|
|
35
|
+
hosted MCP connector read from and write to it directly, with no local database to
|
|
36
|
+
manage.
|
|
37
|
+
- Humans stay in charge: `balladeer recent` and the status line show what accumulated;
|
|
38
|
+
confirm or dismiss from the terminal. `balladeer uninstall` removes everything the
|
|
39
|
+
setup installed (your data directory is backed up, not deleted).
|
|
40
|
+
- Decisions cluster into initiatives: cite a decision id in the PR body that delivers
|
|
41
|
+
it and its definition of done checks off on merge, so initiative progress is derived
|
|
42
|
+
from merged work, not status updates.
|
|
43
|
+
|
|
44
|
+
## Install
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
npm install -g balladeer
|
|
48
|
+
balladeer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This package is a thin installer. On your consent (one prompt; `--yes` to skip), it
|
|
52
|
+
downloads the current Balladeer client from `get.balladeer.ai`, verifies the published
|
|
53
|
+
sha256 checksum, and installs the verified file globally, replacing this installer with
|
|
54
|
+
the real client (the everyday `balladeer` CLI).
|
|
55
|
+
Equivalent, without npm:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
curl -fsSL https://get.balladeer.ai | sh
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Then pair the machine to your team:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
balladeer login
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
You need a Balladeer workspace to pair with; create one at
|
|
68
|
+
[app.balladeer.ai](https://app.balladeer.ai).
|
|
69
|
+
|
|
70
|
+
Flags: `--yes` installs without prompting; `--download-only` fetches and verifies the
|
|
71
|
+
client tarball and prints its path without installing.
|
|
72
|
+
|
|
73
|
+
The client is served from Balladeer's own infrastructure so every install gets the
|
|
74
|
+
current build; the client package will publish here directly once self-serve install
|
|
75
|
+
stabilizes.
|
|
76
|
+
|
|
77
|
+
Copyright (c) 2026 Balladeer. All rights reserved.
|
package/bin/balladeer.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Bootstrapper for the Balladeer client (npm name: balladeer).
|
|
4
|
+
*
|
|
5
|
+
* This package is not the client; it installs it. On consent it downloads the
|
|
6
|
+
* current client tarball from Balladeer's central installer endpoints, verifies
|
|
7
|
+
* the published sha256, and `npm install -g`s the verified file, which replaces
|
|
8
|
+
* this package in place (the client ships under the same package name).
|
|
9
|
+
*
|
|
10
|
+
* Flags: --yes/-y skip the prompt; --download-only fetch + verify, print the path.
|
|
11
|
+
*/
|
|
12
|
+
const { createHash } = require("node:crypto");
|
|
13
|
+
const { mkdtempSync, writeFileSync, rmSync } = require("node:fs");
|
|
14
|
+
const { tmpdir } = require("node:os");
|
|
15
|
+
const { join } = require("node:path");
|
|
16
|
+
const { spawnSync } = require("node:child_process");
|
|
17
|
+
const readline = require("node:readline");
|
|
18
|
+
|
|
19
|
+
const BASE = process.env.BALLADEER_INSTALL_BASE ?? "https://get.balladeer.ai";
|
|
20
|
+
const TGZ_URL = `${BASE}/install/client.tgz`;
|
|
21
|
+
const SHA_URL = `${BASE}/install/client.tgz.sha256`;
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const yes = args.includes("--yes") || args.includes("-y");
|
|
25
|
+
const downloadOnly = args.includes("--download-only");
|
|
26
|
+
|
|
27
|
+
function fail(msg, code = 1) {
|
|
28
|
+
console.error(msg);
|
|
29
|
+
process.exit(code);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function fetchBytes(url) {
|
|
33
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
fail(
|
|
36
|
+
`error: ${url} answered ${res.status}` +
|
|
37
|
+
(res.status === 503 ? " (the client artifact is not available on the server yet)" : ""),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return Buffer.from(await res.arrayBuffer());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
console.error(
|
|
45
|
+
`balladeer: this npm package is the installer, not the client.\n` +
|
|
46
|
+
`It downloads the current Balladeer client from ${BASE}, verifies its\n` +
|
|
47
|
+
`sha256 checksum, and installs it globally (replacing this package).\n`,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
if (!yes && !downloadOnly) {
|
|
51
|
+
if (!process.stdin.isTTY) {
|
|
52
|
+
fail(
|
|
53
|
+
`Non-interactive shell: re-run with --yes to install, or use the script installer:\n\n` +
|
|
54
|
+
` curl -fsSL ${BASE} | sh\n`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
58
|
+
const answer = await new Promise((res) => rl.question("Install the Balladeer client now? [Y/n] ", res));
|
|
59
|
+
rl.close();
|
|
60
|
+
if (answer.trim() !== "" && !/^y(es)?$/i.test(answer.trim())) fail("Nothing installed.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.error(`downloading ${TGZ_URL} ...`);
|
|
64
|
+
const [tgz, shaFile] = await Promise.all([fetchBytes(TGZ_URL), fetchBytes(SHA_URL)]);
|
|
65
|
+
const expected = shaFile.toString("utf8").trim().split(/\s+/)[0];
|
|
66
|
+
const actual = createHash("sha256").update(tgz).digest("hex");
|
|
67
|
+
if (!/^[0-9a-f]{64}$/.test(expected) || expected !== actual) {
|
|
68
|
+
fail(
|
|
69
|
+
`error: checksum mismatch; refusing to install a tampered download.\n` +
|
|
70
|
+
` expected ${expected}\n actual ${actual}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
console.error(`checksum OK (sha256 ${actual.slice(0, 16)}...)`);
|
|
74
|
+
|
|
75
|
+
// A private mkdtemp dir, not a predictable name in the shared tmpdir: the same posture as
|
|
76
|
+
// the script installer's `mktemp -d`, so nothing can pre-place a file/symlink at the path.
|
|
77
|
+
const dir = mkdtempSync(join(tmpdir(), "balladeer-"));
|
|
78
|
+
const file = join(dir, "balladeer-client.tgz");
|
|
79
|
+
writeFileSync(file, tgz);
|
|
80
|
+
if (downloadOnly) {
|
|
81
|
+
console.log(file);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --ignore-scripts: the client has no install hooks, and this keeps a hostile tarball from
|
|
86
|
+
// running code at install time (the same flag the script installer passes).
|
|
87
|
+
const r = spawnSync("npm", ["install", "-g", "--ignore-scripts", file], {
|
|
88
|
+
stdio: "inherit",
|
|
89
|
+
shell: process.platform === "win32",
|
|
90
|
+
});
|
|
91
|
+
try {
|
|
92
|
+
rmSync(dir, { recursive: true, force: true });
|
|
93
|
+
} catch {}
|
|
94
|
+
if (r.status !== 0) {
|
|
95
|
+
fail(`error: npm install -g failed (exit ${r.status ?? "?"}). Fix the npm error above and re-run \`balladeer\`.`, r.status ?? 1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Plain `balladeer login` (the client defaults to hosted central). BASE is the INSTALL host
|
|
99
|
+
// (get.balladeer.ai), an alias that exists only for downloading — teaching it as --server
|
|
100
|
+
// would store it as the machine's permanent server URL. A custom BALLADEER_INSTALL_BASE
|
|
101
|
+
// means a self-hosted deploy whose central URL this bootstrapper cannot derive, so point at
|
|
102
|
+
// the flag rather than silently teaching the hosted default.
|
|
103
|
+
const pairHint =
|
|
104
|
+
BASE === "https://get.balladeer.ai"
|
|
105
|
+
? "balladeer login"
|
|
106
|
+
: "balladeer login --server <your central server URL>";
|
|
107
|
+
console.error(
|
|
108
|
+
`\n✓ Balladeer client installed.\n\n` +
|
|
109
|
+
` next ${pairHint} # approve in your browser; no token to copy\n` +
|
|
110
|
+
` then your coding agent gains get_context / consult / propose_decision\n` +
|
|
111
|
+
` agent or let your agent finish the wiring: paste into it -> Read ${BASE}/agent and follow it.\n\n` +
|
|
112
|
+
` no workspace yet? create one at https://app.balladeer.ai`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main().catch((err) => fail(`error: ${err?.message ?? err}`));
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "balladeer",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Installer for the Balladeer client (decision memory for engineering teams and their AI agents): downloads the sha256-verified client from get.balladeer.ai and installs it globally.",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
6
|
+
"homepage": "https://balladeer.ai",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"balladeer": "bin/balladeer.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
}
|
|
21
|
+
}
|