@maind-dev/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/build/auth.js +76 -0
- package/build/auth.js.map +1 -0
- package/build/context.js +157 -0
- package/build/context.js.map +1 -0
- package/build/index.js +63 -0
- package/build/index.js.map +1 -0
- package/build/mcp-client.js +221 -0
- package/build/mcp-client.js.map +1 -0
- package/build/render.js +81 -0
- package/build/render.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# @maind-dev/cli
|
|
2
|
+
|
|
3
|
+
Headless [maind](https://maind.dev) CLI for CI runners. Pulls stack-relevant,
|
|
4
|
+
vetted lessons and conventions into a build before an agent runs, and emits a
|
|
5
|
+
**context manifest** that records exactly which lessons were served — the
|
|
6
|
+
attribution backbone for maind's CI feedback loop.
|
|
7
|
+
|
|
8
|
+
Unlike [`@maind-dev/mcp-bridge`](../mcp-bridge) (which proxies a live stdio MCP
|
|
9
|
+
client through a device-bound session), this CLI is a one-shot request/response
|
|
10
|
+
tool: authenticate from an environment variable, call a few read-only maind
|
|
11
|
+
tools, write files.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i -g @maind-dev/cli # or: npx @maind-dev/cli context ...
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Auth
|
|
20
|
+
|
|
21
|
+
Set `MAIND_API_KEY` to a **read-scoped CI key** from <https://maind.dev/keys>.
|
|
22
|
+
CI keys are device-unbound, so they work in a runner with no browser and no
|
|
23
|
+
stable device. On a developer machine that already paired the bridge, the CLI
|
|
24
|
+
falls back to the on-disk bridge key automatically.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
export MAIND_API_KEY=mnd_xxxxxxxx_...
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
maind context --stack nextjs,supabase --languages typescript --tools claude-code
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Writes two artifacts (defaults shown):
|
|
37
|
+
|
|
38
|
+
| File | Purpose |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `.maind/context.md` | Lesson bodies + convention checklist — load this into the agent's context |
|
|
41
|
+
| `.maind/manifest.json` | `served` lesson IDs + versions + tiers — attribution record for the run |
|
|
42
|
+
|
|
43
|
+
### Options
|
|
44
|
+
|
|
45
|
+
| Flag | Default | Meaning |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `--stack <a,b>` | — | Stack hint; seeds the platforms filter |
|
|
48
|
+
| `--platforms <a,b>` | from `--stack` | Explicit platforms filter |
|
|
49
|
+
| `--tools <a,b>` | — | Tool filter (e.g. `claude-code`) |
|
|
50
|
+
| `--languages <a,b>` | — | Language filter |
|
|
51
|
+
| `--query <text>` | stack terms | Search query |
|
|
52
|
+
| `--budget <n>` | 15 | Max lessons (1–50; server caps search at 10) |
|
|
53
|
+
| `--out <path>` | `.maind/context.md` | Context markdown output |
|
|
54
|
+
| `--manifest <path>` | `.maind/manifest.json` | Manifest JSON output |
|
|
55
|
+
| `--run-ref <id>` | `$GITHUB_RUN_ID` | Build identifier stored in the manifest |
|
|
56
|
+
| `--mcp-url <url>` | `$MAIND_MCP_URL` or prod | MCP endpoint override |
|
|
57
|
+
|
|
58
|
+
## Fail-open contract
|
|
59
|
+
|
|
60
|
+
A context pull must never fail a build. On any error (missing key, network,
|
|
61
|
+
auth) the CLI still writes explanatory artifacts (the manifest carries an
|
|
62
|
+
`error` field) and exits `0`. The agent simply runs without extra context.
|
|
63
|
+
|
|
64
|
+
## Example: GitHub Actions
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: Pull maind context
|
|
68
|
+
run: npx @maind-dev/cli context --stack nextjs,supabase --languages typescript
|
|
69
|
+
env:
|
|
70
|
+
MAIND_API_KEY: ${{ secrets.MAIND_API_KEY }}
|
|
71
|
+
|
|
72
|
+
- name: Upload manifest (for outcome attribution)
|
|
73
|
+
uses: actions/upload-artifact@v4
|
|
74
|
+
with:
|
|
75
|
+
name: maind-manifest
|
|
76
|
+
path: .maind/manifest.json
|
|
77
|
+
```
|
package/build/auth.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// API-key + device-id resolution for the headless CLI.
|
|
2
|
+
//
|
|
3
|
+
// CI-first: the key comes from the MAIND_API_KEY environment variable. For local
|
|
4
|
+
// dev convenience we also fall back to the same on-disk key the mcp-bridge
|
|
5
|
+
// persists (~/Library/Application Support/maind/api-key etc.), so a developer who
|
|
6
|
+
// already paired the bridge can run `maind context` without re-exporting anything.
|
|
7
|
+
//
|
|
8
|
+
// We deliberately do NOT run a device-authorization flow here. A CI runner has no
|
|
9
|
+
// browser and no stable device, so an interactive flow would just hang. The
|
|
10
|
+
// intended key for CI is a device-unbound "ci"-kind key minted in the dashboard
|
|
11
|
+
// (read-scoped). Until that key class ships, a free-tier / pre-cutoff key works
|
|
12
|
+
// because those are not device-bound either.
|
|
13
|
+
//
|
|
14
|
+
// The device-id is sent ONLY when an existing device-id file is found on disk
|
|
15
|
+
// (i.e. this machine already paired the bridge). In CI no such file exists, so the
|
|
16
|
+
// header is omitted and the server treats the request as device-less.
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
18
|
+
import { homedir, platform } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
/** XDG-style config directory for maind — must match apps/mcp-bridge/src/device-id.ts. */
|
|
21
|
+
function configDir() {
|
|
22
|
+
const home = homedir();
|
|
23
|
+
const env = process.env;
|
|
24
|
+
if (platform() === "darwin") {
|
|
25
|
+
return join(home, "Library", "Application Support", "maind");
|
|
26
|
+
}
|
|
27
|
+
if (platform() === "win32") {
|
|
28
|
+
const appdata = env.APPDATA && env.APPDATA.length > 0 ? env.APPDATA : join(home, "AppData", "Roaming");
|
|
29
|
+
return join(appdata, "maind");
|
|
30
|
+
}
|
|
31
|
+
const xdg = env.XDG_CONFIG_HOME && env.XDG_CONFIG_HOME.length > 0
|
|
32
|
+
? env.XDG_CONFIG_HOME
|
|
33
|
+
: join(home, ".config");
|
|
34
|
+
return join(xdg, "maind");
|
|
35
|
+
}
|
|
36
|
+
function readFileTrimmed(path) {
|
|
37
|
+
if (!existsSync(path))
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
const raw = readFileSync(path, "utf8").trim();
|
|
41
|
+
return raw.length > 0 ? raw : null;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export class MissingApiKeyError extends Error {
|
|
48
|
+
constructor() {
|
|
49
|
+
super("No API key found. Set MAIND_API_KEY (a read-scoped CI key from https://maind.dev/keys) " +
|
|
50
|
+
"or pair the maind bridge on this machine first.");
|
|
51
|
+
this.name = "MissingApiKeyError";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolves the API key (env-first, then the bridge's on-disk key) and an optional
|
|
56
|
+
* device-id. Throws MissingApiKeyError when neither source yields a key.
|
|
57
|
+
*/
|
|
58
|
+
export function resolveAuth() {
|
|
59
|
+
const fromEnv = process.env.MAIND_API_KEY?.trim();
|
|
60
|
+
const apiKey = fromEnv && fromEnv.length > 0 ? fromEnv : readFileTrimmed(join(configDir(), "api-key"));
|
|
61
|
+
if (!apiKey)
|
|
62
|
+
throw new MissingApiKeyError();
|
|
63
|
+
const deviceId = readFileTrimmed(join(configDir(), "device-id"));
|
|
64
|
+
return { apiKey, deviceId, keyPrefix: keyPrefixOf(apiKey) };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts the non-secret identifying prefix from a maind key (`mnd_<8hex>_<secret>`).
|
|
68
|
+
* Returns the `mnd_<8hex>` portion, or a coarse first-12-chars fallback for other shapes.
|
|
69
|
+
*/
|
|
70
|
+
export function keyPrefixOf(apiKey) {
|
|
71
|
+
const m = /^(mnd_[0-9a-f]{8})_/i.exec(apiKey);
|
|
72
|
+
if (m)
|
|
73
|
+
return m[1];
|
|
74
|
+
return apiKey.slice(0, 12);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/build/context.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// `maind context` — pull stack-relevant lessons + conventions into a CI runner.
|
|
2
|
+
//
|
|
3
|
+
// Fail-open by contract: a CI job must never fail because the context pull failed.
|
|
4
|
+
// Any error is logged to stderr, an explanatory empty context.md + an error-tagged
|
|
5
|
+
// manifest are still written, and the process exits 0. The caller decides whether
|
|
6
|
+
// to proceed (it always can — the agent just runs without extra context).
|
|
7
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { dirname } from "node:path";
|
|
9
|
+
import { MissingApiKeyError, resolveAuth } from "./auth.js";
|
|
10
|
+
import { McpClient } from "./mcp-client.js";
|
|
11
|
+
import { buildManifest, renderContextMarkdown, } from "./render.js";
|
|
12
|
+
function csvSplit(s) {
|
|
13
|
+
return s
|
|
14
|
+
.split(",")
|
|
15
|
+
.map((x) => x.trim())
|
|
16
|
+
.filter((x) => x.length > 0);
|
|
17
|
+
}
|
|
18
|
+
/** Parses the `context` subcommand flags. Unknown flags are ignored (forward-compat). */
|
|
19
|
+
export function parseContextArgs(argv) {
|
|
20
|
+
const get = (name) => {
|
|
21
|
+
const i = argv.indexOf(name);
|
|
22
|
+
if (i === -1 || i + 1 >= argv.length)
|
|
23
|
+
return undefined;
|
|
24
|
+
return argv[i + 1];
|
|
25
|
+
};
|
|
26
|
+
const stack = csvSplit(get("--stack") ?? "");
|
|
27
|
+
const platforms = csvSplit(get("--platforms") ?? "");
|
|
28
|
+
const tools = csvSplit(get("--tools") ?? "");
|
|
29
|
+
const languages = csvSplit(get("--languages") ?? "");
|
|
30
|
+
const budgetRaw = get("--budget");
|
|
31
|
+
const budgetParsed = budgetRaw ? Number.parseInt(budgetRaw, 10) : NaN;
|
|
32
|
+
const budget = Number.isFinite(budgetParsed) && budgetParsed > 0 ? Math.min(budgetParsed, 50) : 15;
|
|
33
|
+
return {
|
|
34
|
+
stack,
|
|
35
|
+
languages,
|
|
36
|
+
// --stack is a convenience that seeds the platforms filter; explicit --platforms wins/merges.
|
|
37
|
+
platforms: platforms.length > 0 ? platforms : stack,
|
|
38
|
+
tools,
|
|
39
|
+
query: get("--query") ?? null,
|
|
40
|
+
out: get("--out") ?? ".maind/context.md",
|
|
41
|
+
manifest: get("--manifest") ?? ".maind/manifest.json",
|
|
42
|
+
budget,
|
|
43
|
+
runRef: get("--run-ref") ?? process.env.GITHUB_RUN_ID ?? null,
|
|
44
|
+
mcpUrl: get("--mcp-url") ?? null,
|
|
45
|
+
clientVersion: "0.1.0",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function nowIso() {
|
|
49
|
+
// Real Node runtime — Date is available (the Date-ban only applies to Workflow scripts).
|
|
50
|
+
return new Date().toISOString();
|
|
51
|
+
}
|
|
52
|
+
function writeArtifact(path, content) {
|
|
53
|
+
const dir = dirname(path);
|
|
54
|
+
if (dir && dir !== ".")
|
|
55
|
+
mkdirSync(dir, { recursive: true });
|
|
56
|
+
writeFileSync(path, content, "utf8");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Runs the context pull. Returns the process exit code. Always 0 on the
|
|
60
|
+
* fail-open path (artifacts still written); non-zero is reserved for truly
|
|
61
|
+
* unwritable output paths where we can't even emit the empty artifacts.
|
|
62
|
+
*/
|
|
63
|
+
export async function runContext(opts) {
|
|
64
|
+
const query = opts.query ?? deriveQuery(opts);
|
|
65
|
+
const meta = {
|
|
66
|
+
stack: opts.stack,
|
|
67
|
+
languages: opts.languages,
|
|
68
|
+
platforms: opts.platforms,
|
|
69
|
+
tools: opts.tools,
|
|
70
|
+
query,
|
|
71
|
+
budget: opts.budget,
|
|
72
|
+
generatedAt: nowIso(),
|
|
73
|
+
keyPrefix: "unknown",
|
|
74
|
+
runRef: opts.runRef,
|
|
75
|
+
mcpUrl: opts.mcpUrl ?? process.env.MAIND_MCP_URL ?? "https://mcp.maind.dev/mcp",
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const auth = resolveAuth();
|
|
79
|
+
meta.keyPrefix = auth.keyPrefix;
|
|
80
|
+
const client = new McpClient({
|
|
81
|
+
apiKey: auth.apiKey,
|
|
82
|
+
deviceId: auth.deviceId,
|
|
83
|
+
clientVersion: opts.clientVersion,
|
|
84
|
+
mcpUrl: opts.mcpUrl ?? undefined,
|
|
85
|
+
});
|
|
86
|
+
// Two calls: search_lessons gives full lesson bodies; get_session_briefing
|
|
87
|
+
// gives the always-on convention awareness index. Conventions are best-effort —
|
|
88
|
+
// a failure there must not lose the lessons.
|
|
89
|
+
const search = await client.callTool("search_lessons", {
|
|
90
|
+
query,
|
|
91
|
+
platforms: opts.platforms,
|
|
92
|
+
languages: opts.languages,
|
|
93
|
+
tools: opts.tools,
|
|
94
|
+
limit: Math.min(opts.budget, 10), // search_lessons caps limit at 10 server-side
|
|
95
|
+
});
|
|
96
|
+
const lessons = Array.isArray(search.lessons) ? search.lessons : [];
|
|
97
|
+
let conventions = [];
|
|
98
|
+
try {
|
|
99
|
+
const briefing = await client.callTool("get_session_briefing", {
|
|
100
|
+
tools: opts.tools.length > 0 ? opts.tools : ["claude-code"],
|
|
101
|
+
languages: opts.languages,
|
|
102
|
+
platforms: opts.platforms,
|
|
103
|
+
});
|
|
104
|
+
if (Array.isArray(briefing.convention_index))
|
|
105
|
+
conventions = briefing.convention_index;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
warn(`conventions unavailable (continuing with lessons only): ${msg(err)}`);
|
|
109
|
+
}
|
|
110
|
+
writeArtifact(opts.out, renderContextMarkdown(lessons, conventions, meta));
|
|
111
|
+
writeArtifact(opts.manifest, JSON.stringify(buildManifest(lessons, conventions, meta), null, 2) + "\n");
|
|
112
|
+
process.stderr.write(`maind context: wrote ${lessons.length} lessons + ${conventions.length} conventions ` +
|
|
113
|
+
`→ ${opts.out} (manifest: ${opts.manifest})\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
// Fail-open: emit explanatory empty artifacts so the manifest path always exists,
|
|
118
|
+
// then exit 0 so CI proceeds without maind context.
|
|
119
|
+
const reason = err instanceof MissingApiKeyError ? err.message : msg(err);
|
|
120
|
+
warn(`context pull failed, continuing without maind context: ${reason}`);
|
|
121
|
+
const emptyManifest = buildManifest([], [], meta);
|
|
122
|
+
emptyManifest.error = reason;
|
|
123
|
+
try {
|
|
124
|
+
writeArtifact(opts.out, failOpenMarkdown(meta, reason));
|
|
125
|
+
writeArtifact(opts.manifest, JSON.stringify(emptyManifest, null, 2) + "\n");
|
|
126
|
+
}
|
|
127
|
+
catch (writeErr) {
|
|
128
|
+
warn(`could not write fail-open artifacts: ${msg(writeErr)}`);
|
|
129
|
+
return 1; // truly unwritable output path — signal it, but this is rare
|
|
130
|
+
}
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function deriveQuery(opts) {
|
|
135
|
+
const parts = [...opts.stack, ...opts.tools, ...opts.languages];
|
|
136
|
+
return parts.length > 0 ? parts.join(" ") : "common build and CI pitfalls";
|
|
137
|
+
}
|
|
138
|
+
function failOpenMarkdown(meta, reason) {
|
|
139
|
+
return [
|
|
140
|
+
"<!-- Generated by `maind context` (fail-open path). -->",
|
|
141
|
+
`<!-- generated_at: ${meta.generatedAt} -->`,
|
|
142
|
+
"",
|
|
143
|
+
"# maind context",
|
|
144
|
+
"",
|
|
145
|
+
`_maind context was unavailable for this run: ${reason}_`,
|
|
146
|
+
"",
|
|
147
|
+
"The build can proceed normally — this file is informational only.",
|
|
148
|
+
"",
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
function msg(err) {
|
|
152
|
+
return err instanceof Error ? err.message : String(err);
|
|
153
|
+
}
|
|
154
|
+
function warn(line) {
|
|
155
|
+
process.stderr.write(`maind context: ${line}\n`);
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// @maind-dev/cli — entry point.
|
|
4
|
+
//
|
|
5
|
+
// Headless companion to the maind MCP server, built for CI. Unlike the
|
|
6
|
+
// mcp-bridge (which proxies a live stdio MCP client through a device-bound
|
|
7
|
+
// session), this CLI is a one-shot request/response tool: it authenticates from
|
|
8
|
+
// MAIND_API_KEY, calls a few read-only maind tools, and writes files.
|
|
9
|
+
//
|
|
10
|
+
// STDOUT is reserved for command results that are meant to be piped; all
|
|
11
|
+
// diagnostics go to STDERR. Today only the `context` command exists.
|
|
12
|
+
import { parseContextArgs, runContext } from "./context.js";
|
|
13
|
+
const VERSION = "0.1.0";
|
|
14
|
+
const HELP = `maind ${VERSION} — headless maind CLI for CI
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
maind context [options] Pull stack-relevant lessons + conventions into the runner
|
|
18
|
+
maind --help Show this help
|
|
19
|
+
maind --version Print version
|
|
20
|
+
|
|
21
|
+
context options:
|
|
22
|
+
--stack <a,b> Stack hint (seeds the platforms filter), e.g. nextjs,supabase
|
|
23
|
+
--platforms <a,b> Explicit platforms filter (overrides --stack)
|
|
24
|
+
--tools <a,b> Tool filter, e.g. claude-code
|
|
25
|
+
--languages <a,b> Language filter, e.g. typescript
|
|
26
|
+
--query <text> Search query (defaults to the stack/tool/language terms)
|
|
27
|
+
--budget <n> Max lessons to pull (1-50, default 15; server caps search at 10)
|
|
28
|
+
--out <path> Context markdown output (default .maind/context.md)
|
|
29
|
+
--manifest <path> Manifest JSON output (default .maind/manifest.json)
|
|
30
|
+
--run-ref <id> Build identifier for the manifest (default \$GITHUB_RUN_ID)
|
|
31
|
+
--mcp-url <url> Override MCP endpoint (default \$MAIND_MCP_URL or production)
|
|
32
|
+
|
|
33
|
+
Auth:
|
|
34
|
+
Reads MAIND_API_KEY (a read-scoped CI key from https://maind.dev/keys), or falls
|
|
35
|
+
back to a paired bridge key on this machine. The context pull is fail-open: on any
|
|
36
|
+
error it still writes explanatory artifacts and exits 0 so the build proceeds.
|
|
37
|
+
`;
|
|
38
|
+
async function main() {
|
|
39
|
+
const argv = process.argv.slice(2);
|
|
40
|
+
const cmd = argv[0];
|
|
41
|
+
if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
42
|
+
process.stdout.write(HELP);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
if (cmd === "--version" || cmd === "-v") {
|
|
46
|
+
process.stdout.write(VERSION + "\n");
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
if (cmd === "context") {
|
|
50
|
+
return runContext(parseContextArgs(argv.slice(1)));
|
|
51
|
+
}
|
|
52
|
+
process.stderr.write(`maind: unknown command '${cmd}'. Run 'maind --help'.\n`);
|
|
53
|
+
return 2;
|
|
54
|
+
}
|
|
55
|
+
main()
|
|
56
|
+
.then((code) => process.exit(code))
|
|
57
|
+
.catch((err) => {
|
|
58
|
+
// Defensive backstop — runContext is already fail-open, so reaching here means
|
|
59
|
+
// an unexpected programming error. Surface it on stderr and exit non-zero.
|
|
60
|
+
process.stderr.write(`maind: fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,EAAE;AACF,gCAAgC;AAChC,EAAE;AACF,uEAAuE;AACvE,2EAA2E;AAC3E,gFAAgF;AAChF,sEAAsE;AACtE,EAAE;AACF,yEAAyE;AACzE,qEAAqE;AAErE,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,IAAI,GAAG,SAAS,OAAO;;;;;;;;;;;;;;;;;;;;;;;CAuB5B,CAAC;AAEF,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpB,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,GAAG,KAAK,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;QACrC,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,OAAO,UAAU,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,0BAA0B,CAAC,CAAC;IAC/E,OAAO,CAAC,CAAC;AACX,CAAC;AAED,IAAI,EAAE;KACH,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;KAClC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACb,+EAA+E;IAC/E,2EAA2E;IAC3E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACzG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Minimal Streamable-HTTP MCP client for the maind CLI.
|
|
2
|
+
//
|
|
3
|
+
// Intentionally thin — same philosophy as apps/mcp-bridge/src/forward.ts, but
|
|
4
|
+
// instead of piping JSON-RPC frames to stdout it performs a request/response
|
|
5
|
+
// round-trip and returns the parsed result. We don't pull in the full MCP SDK:
|
|
6
|
+
// the CLI only needs initialize → notifications/initialized → tools/call.
|
|
7
|
+
//
|
|
8
|
+
// Streamable HTTP means each POST may answer with a one-shot JSON body OR a
|
|
9
|
+
// text/event-stream. Both are unpacked to a single JSON-RPC message here.
|
|
10
|
+
const DEFAULT_MCP_URL = "https://mcp.maind.dev/mcp";
|
|
11
|
+
const CLIENT_FAMILY = "maind-cli";
|
|
12
|
+
export class McpError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(message, code) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "McpError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class McpClient {
|
|
21
|
+
mcpUrl;
|
|
22
|
+
apiKey;
|
|
23
|
+
deviceId;
|
|
24
|
+
clientVersion;
|
|
25
|
+
sessionId = null;
|
|
26
|
+
nextId = 1;
|
|
27
|
+
initialized = false;
|
|
28
|
+
constructor(cfg) {
|
|
29
|
+
this.apiKey = cfg.apiKey;
|
|
30
|
+
this.deviceId = cfg.deviceId;
|
|
31
|
+
this.clientVersion = cfg.clientVersion;
|
|
32
|
+
this.mcpUrl = cfg.mcpUrl ?? process.env.MAIND_MCP_URL ?? DEFAULT_MCP_URL;
|
|
33
|
+
}
|
|
34
|
+
headers() {
|
|
35
|
+
const h = {
|
|
36
|
+
"content-type": "application/json",
|
|
37
|
+
accept: "application/json, text/event-stream",
|
|
38
|
+
authorization: `Bearer ${this.apiKey}`,
|
|
39
|
+
"x-maind-client-family": CLIENT_FAMILY,
|
|
40
|
+
"x-maind-client-version": this.clientVersion,
|
|
41
|
+
};
|
|
42
|
+
if (this.deviceId)
|
|
43
|
+
h["x-maind-device-id"] = this.deviceId;
|
|
44
|
+
if (this.sessionId)
|
|
45
|
+
h["mcp-session-id"] = this.sessionId;
|
|
46
|
+
return h;
|
|
47
|
+
}
|
|
48
|
+
/** Performs the MCP handshake: initialize, capture session-id, send initialized. */
|
|
49
|
+
async connect() {
|
|
50
|
+
if (this.initialized)
|
|
51
|
+
return;
|
|
52
|
+
const res = await this.post({
|
|
53
|
+
jsonrpc: "2.0",
|
|
54
|
+
id: this.nextId++,
|
|
55
|
+
method: "initialize",
|
|
56
|
+
params: {
|
|
57
|
+
protocolVersion: "2025-06-18",
|
|
58
|
+
capabilities: {},
|
|
59
|
+
clientInfo: { name: CLIENT_FAMILY, version: this.clientVersion },
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
if (res.message.error) {
|
|
63
|
+
throw new McpError(`initialize failed: ${res.message.error.message}`, res.message.error.code);
|
|
64
|
+
}
|
|
65
|
+
if (res.sessionId)
|
|
66
|
+
this.sessionId = res.sessionId;
|
|
67
|
+
// Per MCP spec the client must acknowledge with an initialized notification
|
|
68
|
+
// before issuing tool calls. Notifications carry no id and return 202.
|
|
69
|
+
await this.post({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
70
|
+
this.initialized = true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Calls a maind MCP tool and returns its decoded JSON payload. maind tools
|
|
74
|
+
* return their result as a JSON string inside content[0].text, so we parse it.
|
|
75
|
+
*/
|
|
76
|
+
async callTool(name, args) {
|
|
77
|
+
if (!this.initialized)
|
|
78
|
+
await this.connect();
|
|
79
|
+
const res = await this.post({
|
|
80
|
+
jsonrpc: "2.0",
|
|
81
|
+
id: this.nextId++,
|
|
82
|
+
method: "tools/call",
|
|
83
|
+
params: { name, arguments: args },
|
|
84
|
+
});
|
|
85
|
+
if (res.message.error) {
|
|
86
|
+
throw new McpError(`tool ${name} failed: ${res.message.error.message}`, res.message.error.code);
|
|
87
|
+
}
|
|
88
|
+
const result = res.message.result;
|
|
89
|
+
const text = result?.content?.find((c) => c.type === "text" && typeof c.text === "string")?.text;
|
|
90
|
+
if (typeof text !== "string") {
|
|
91
|
+
throw new McpError(`tool ${name} returned no text content`);
|
|
92
|
+
}
|
|
93
|
+
const parsed = parseJsonLoose(text);
|
|
94
|
+
if (parsed === undefined) {
|
|
95
|
+
throw new McpError(`tool ${name} returned non-JSON text`);
|
|
96
|
+
}
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
/** POSTs a JSON-RPC frame and unpacks the JSON-or-SSE response. */
|
|
100
|
+
async post(frame) {
|
|
101
|
+
let response;
|
|
102
|
+
try {
|
|
103
|
+
response = await fetch(this.mcpUrl, {
|
|
104
|
+
method: "POST",
|
|
105
|
+
headers: this.headers(),
|
|
106
|
+
body: JSON.stringify(frame),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
throw new McpError(`network error contacting ${this.mcpUrl}: ${stringifyErr(err)}`);
|
|
111
|
+
}
|
|
112
|
+
const sid = response.headers.get("mcp-session-id");
|
|
113
|
+
if (response.status === 401) {
|
|
114
|
+
const body = await response.text().catch(() => "");
|
|
115
|
+
throw new McpError(`auth rejected (401): ${body.slice(0, 200)}`, 401);
|
|
116
|
+
}
|
|
117
|
+
// 202 Accepted: a notification with no body (e.g. notifications/initialized).
|
|
118
|
+
if (response.status === 202) {
|
|
119
|
+
return { message: { jsonrpc: "2.0", result: null }, sessionId: sid };
|
|
120
|
+
}
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const body = await response.text().catch(() => "");
|
|
123
|
+
throw new McpError(`upstream HTTP ${response.status}: ${body.slice(0, 300)}`, response.status);
|
|
124
|
+
}
|
|
125
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
126
|
+
const raw = contentType.startsWith("text/event-stream")
|
|
127
|
+
? await readSseMessage(response)
|
|
128
|
+
: await response.text();
|
|
129
|
+
if (raw.trim().length === 0) {
|
|
130
|
+
return { message: { jsonrpc: "2.0", result: null }, sessionId: sid };
|
|
131
|
+
}
|
|
132
|
+
let message;
|
|
133
|
+
try {
|
|
134
|
+
message = JSON.parse(raw);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
throw new McpError(`could not parse JSON-RPC response: ${raw.slice(0, 200)}`);
|
|
138
|
+
}
|
|
139
|
+
return { message, sessionId: sid };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Reads an SSE body and returns the last complete `data:` payload — for a
|
|
144
|
+
* request/response round-trip that is the single JSON-RPC response frame.
|
|
145
|
+
*/
|
|
146
|
+
async function readSseMessage(response) {
|
|
147
|
+
if (!response.body)
|
|
148
|
+
return "";
|
|
149
|
+
const reader = response.body.getReader();
|
|
150
|
+
const decoder = new TextDecoder();
|
|
151
|
+
let buffer = "";
|
|
152
|
+
let dataLines = [];
|
|
153
|
+
let lastPayload = "";
|
|
154
|
+
const flush = () => {
|
|
155
|
+
if (dataLines.length === 0)
|
|
156
|
+
return;
|
|
157
|
+
const payload = dataLines.join("\n").trim();
|
|
158
|
+
dataLines = [];
|
|
159
|
+
if (payload.length > 0)
|
|
160
|
+
lastPayload = payload;
|
|
161
|
+
};
|
|
162
|
+
while (true) {
|
|
163
|
+
const { value, done } = await reader.read();
|
|
164
|
+
if (done)
|
|
165
|
+
break;
|
|
166
|
+
buffer += decoder.decode(value, { stream: true });
|
|
167
|
+
let nl;
|
|
168
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
169
|
+
const rawLine = buffer.slice(0, nl);
|
|
170
|
+
buffer = buffer.slice(nl + 1);
|
|
171
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
172
|
+
if (line === "") {
|
|
173
|
+
flush();
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (line.startsWith(":"))
|
|
177
|
+
continue; // keep-alive comment
|
|
178
|
+
if (line.startsWith("data:"))
|
|
179
|
+
dataLines.push(line.slice(5).replace(/^\s/, ""));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
flush();
|
|
183
|
+
return lastPayload;
|
|
184
|
+
}
|
|
185
|
+
function stringifyErr(err) {
|
|
186
|
+
return err instanceof Error ? err.message : String(err);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Parses a tool's text payload as JSON, tolerating a leading human-readable
|
|
190
|
+
* prefix line. search_lessons returns pure JSON, but get_session_briefing
|
|
191
|
+
* prefixes its JSON with "SESSION BRIEFING ...:\n\n". We first try a strict
|
|
192
|
+
* parse, then fall back to the largest brace/bracket-delimited substring.
|
|
193
|
+
* Returns undefined when no JSON can be recovered.
|
|
194
|
+
*/
|
|
195
|
+
function parseJsonLoose(text) {
|
|
196
|
+
const trimmed = text.trim();
|
|
197
|
+
try {
|
|
198
|
+
return JSON.parse(trimmed);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// fall through to substring extraction
|
|
202
|
+
}
|
|
203
|
+
const firstObj = trimmed.indexOf("{");
|
|
204
|
+
const firstArr = trimmed.indexOf("[");
|
|
205
|
+
const candidates = [firstObj, firstArr].filter((i) => i >= 0);
|
|
206
|
+
if (candidates.length === 0)
|
|
207
|
+
return undefined;
|
|
208
|
+
const start = Math.min(...candidates);
|
|
209
|
+
const open = trimmed[start];
|
|
210
|
+
const close = open === "{" ? "}" : "]";
|
|
211
|
+
const end = trimmed.lastIndexOf(close);
|
|
212
|
+
if (end <= start)
|
|
213
|
+
return undefined;
|
|
214
|
+
try {
|
|
215
|
+
return JSON.parse(trimmed.slice(start, end + 1));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=mcp-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-client.js","sourceRoot":"","sources":["../src/mcp-client.ts"],"names":[],"mappings":"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"}
|
package/build/render.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Rendering of the two CI artifacts:
|
|
2
|
+
// - context.md — lesson bodies + a convention checklist for the agent in the runner
|
|
3
|
+
// - manifest.json — the served lesson/convention IDs+versions (attribution backbone)
|
|
4
|
+
//
|
|
5
|
+
// The manifest is the load-bearing piece for later phases: a build-outcome signal
|
|
6
|
+
// is only attributable to a lesson if we recorded that the lesson was actually in
|
|
7
|
+
// the agent's context for that run. context.md is human/agent-facing; manifest.json
|
|
8
|
+
// is machine-facing.
|
|
9
|
+
export function buildManifest(lessons, conventions, meta) {
|
|
10
|
+
return {
|
|
11
|
+
schema: "maind.context.manifest/v1",
|
|
12
|
+
generated_at: meta.generatedAt,
|
|
13
|
+
key_prefix: meta.keyPrefix,
|
|
14
|
+
run_ref: meta.runRef,
|
|
15
|
+
stack: meta.stack,
|
|
16
|
+
query: meta.query,
|
|
17
|
+
served: lessons.map((l) => ({
|
|
18
|
+
id: l.id,
|
|
19
|
+
version: typeof l.version === "number" ? l.version : null,
|
|
20
|
+
tier: l.tier ?? null,
|
|
21
|
+
last_validated_at: l.last_validated_at ?? null,
|
|
22
|
+
})),
|
|
23
|
+
conventions: conventions.map((c) => c.id),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function renderContextMarkdown(lessons, conventions, meta) {
|
|
27
|
+
const lines = [];
|
|
28
|
+
lines.push("<!-- Generated by `maind context` — do not edit by hand. -->");
|
|
29
|
+
lines.push(`<!-- generated_at: ${meta.generatedAt} | key: ${meta.keyPrefix}${meta.runRef ? ` | run: ${meta.runRef}` : ""} -->`);
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push("# maind context");
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push("Vetted lessons and conventions for this stack, pulled from maind for the agent " +
|
|
34
|
+
"running in this CI job. Treat each lesson as reference material — weigh it against " +
|
|
35
|
+
"the actual diff, don't apply blindly.");
|
|
36
|
+
lines.push("");
|
|
37
|
+
const stackLabel = meta.stack.length > 0 ? meta.stack.join(", ") : "(none)";
|
|
38
|
+
lines.push(`**Stack:** ${stackLabel} · **Lessons:** ${lessons.length} · **Conventions:** ${conventions.length}`);
|
|
39
|
+
lines.push("");
|
|
40
|
+
if (conventions.length > 0) {
|
|
41
|
+
lines.push("## Always-on conventions");
|
|
42
|
+
lines.push("");
|
|
43
|
+
for (const c of conventions) {
|
|
44
|
+
lines.push(`- **${c.title}** \`${c.id}\``);
|
|
45
|
+
}
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
if (lessons.length === 0) {
|
|
49
|
+
lines.push("## Lessons");
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push("_No stack-relevant lessons were returned for this query._");
|
|
52
|
+
lines.push("");
|
|
53
|
+
return lines.join("\n");
|
|
54
|
+
}
|
|
55
|
+
lines.push("## Lessons");
|
|
56
|
+
lines.push("");
|
|
57
|
+
for (const l of lessons) {
|
|
58
|
+
lines.push(`### ${l.title}`);
|
|
59
|
+
const meta2 = [`\`${l.id}\``];
|
|
60
|
+
if (l.tier)
|
|
61
|
+
meta2.push(l.tier);
|
|
62
|
+
if (typeof l.version === "number")
|
|
63
|
+
meta2.push(`v${l.version}`);
|
|
64
|
+
if (l.last_validated_at)
|
|
65
|
+
meta2.push(`validated ${l.last_validated_at}`);
|
|
66
|
+
lines.push(meta2.join(" · "));
|
|
67
|
+
lines.push("");
|
|
68
|
+
if (l.summary) {
|
|
69
|
+
lines.push(`> ${l.summary}`);
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
if (l.body && l.body.trim().length > 0) {
|
|
73
|
+
lines.push(l.body.trim());
|
|
74
|
+
lines.push("");
|
|
75
|
+
}
|
|
76
|
+
lines.push("---");
|
|
77
|
+
lines.push("");
|
|
78
|
+
}
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maind-dev/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Headless maind CLI — pull stack-relevant lessons into CI runners and emit a context manifest for build-outcome attribution. No device flow; authenticates from MAIND_API_KEY.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"maind": "./build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && chmod +x build/index.js",
|
|
16
|
+
"prepublishOnly": "pnpm build",
|
|
17
|
+
"start": "node build/index.js",
|
|
18
|
+
"dev": "node --import tsx src/index.ts",
|
|
19
|
+
"test": "node --import tsx --test test/**/*.test.ts",
|
|
20
|
+
"type-check": "tsc --noEmit",
|
|
21
|
+
"typecheck": "tsc --noEmit"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.11.0",
|
|
25
|
+
"tsx": "^4.7.0",
|
|
26
|
+
"typescript": "^5.4.0"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"maind",
|
|
34
|
+
"ci",
|
|
35
|
+
"cli",
|
|
36
|
+
"context"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|