@mapled/cli 0.14.6 → 0.15.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 +23 -1
- package/dist/api.d.ts +5 -1
- package/dist/api.js +18 -8
- package/dist/args.js +1 -1
- package/dist/commands.d.ts +13 -0
- package/dist/commands.js +128 -3
- package/dist/doctor.js +9 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +9 -0
- package/dist/index.js +16 -3
- package/dist/oauth.d.ts +2 -0
- package/dist/oauth.js +1 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -16,6 +16,26 @@ Node 18 or newer. No dependencies. Prefer a global install? `npm install -g @map
|
|
|
16
16
|
|
|
17
17
|
Connecting a site without an AI agent? [Integrate by hand](https://www.npmjs.com/package/@mapled/next#integrate-by-hand) in the `@mapled/next` README walks through the whole integration — modeling the content, reading it, the publish webhook, preview, forms — and uses these commands along the way.
|
|
18
18
|
|
|
19
|
+
## For AI agents
|
|
20
|
+
|
|
21
|
+
Mapled is a hosted headless CMS for sites built with AI: the owner edits content in Mapled, the site reads published content through a small SDK, and you connect the two. Everything below runs from the shell of the session you are already in — no MCP server to register, no restart.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx @mapled/cli connect --project <id>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The browser opens Mapled on that project and the person clicks Authorize; nothing is copied or pasted. The command writes `mapled.json` and prints Mapled's guide for agents (also at https://api.mapled.io/agents.md). Run again in a connected repository — or in a subdirectory of one — it opens no browser. The guide is read from Mapled, or from the origin you name with `--api` on that run; an origin that only `mapled.json` names never serves it, and a first sign-in there asks for `--api` too. Without `--project` the person picks the project in the browser.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @mapled/cli tools
|
|
31
|
+
npx @mapled/cli tool get_schema
|
|
32
|
+
npx @mapled/cli tool propose_setup_plan --input plan.json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`tools` lists every tool with its input (`--json` for the schemas). `tool <name>` calls one — the same tools the [MCP server](https://www.npmjs.com/package/@mapled/mcp) offers, through the same connection: the input is a JSON object, inline or in a file with `--input`; the answer is printed as Mapled worded it, and a refusal exits 1.
|
|
36
|
+
|
|
37
|
+
Do the work yourself: the person approves on Mapled screens (the connection, the plan, a destructive change) and is never sent to a terminal or to GitHub. Never ask for a token or a key in the conversation.
|
|
38
|
+
|
|
19
39
|
## Sign in
|
|
20
40
|
|
|
21
41
|
```bash
|
|
@@ -271,7 +291,9 @@ Prints a Preview link — the one the **Preview** button in Mapled opens: the si
|
|
|
271
291
|
|
|
272
292
|
- `--api <origin>` — a Mapled API other than `https://api.mapled.io` (or set `MAPLED_API_URL`); `project link` remembers it in `mapled.json`
|
|
273
293
|
- `--no-browser` — print the sign-in link instead of opening a browser
|
|
274
|
-
- `--
|
|
294
|
+
- `--project <id>` — the project `connect` and `auth login` open the consent on, and the one `project link` links
|
|
295
|
+
- `--input <file>` — `tool` only: the tool's input as a JSON file
|
|
296
|
+
- `--json` — machine-readable output (`tools`, `doctor`, `integration report`, `schema diff`, `scan`, `manifest validate`, `forms list`, `preview`)
|
|
275
297
|
- `--load-config` — `doctor` only: let the site's installed Next load a next.config doctor can't read; runs the site's code, as `next build` does
|
|
276
298
|
- `MAPLED_CONFIG_DIR` — where credentials live (default: `$XDG_CONFIG_HOME/mapled` or `~/.config/mapled`)
|
|
277
299
|
|
package/dist/api.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export declare class ApiError extends Error {
|
|
|
7
7
|
constructor(status: number, code: string | null, message: string);
|
|
8
8
|
}
|
|
9
9
|
export type Method = "GET" | "POST" | "PATCH" | "DELETE";
|
|
10
|
-
export declare function apiRequest<T>(api: string, method: Method, path: string, token: string, body: unknown, f: Fetch): Promise<T>;
|
|
10
|
+
export declare function apiRequest<T>(api: string, method: Method, path: string, token: string, body: unknown, f: Fetch, accept?: string): Promise<T>;
|
|
11
11
|
export declare function apiGet<T>(api: string, path: string, token: string, f: Fetch): Promise<T>;
|
|
12
12
|
/** A signed-in connection with its store: reads refresh the access
|
|
13
13
|
token when it is about to expire or when the API says it did, and
|
|
@@ -20,6 +20,10 @@ export declare class Session {
|
|
|
20
20
|
constructor(store: CredentialsFile, file: string, conn: Connection, f: Fetch);
|
|
21
21
|
get<T>(path: string): Promise<T>;
|
|
22
22
|
send<T>(method: Method, path: string, body: unknown): Promise<T>;
|
|
23
|
+
/** One JSON-RPC call to the hosted MCP endpoint — the tools an agent
|
|
24
|
+
gets over MCP, from the shell (the endpoint is stateless: a call
|
|
25
|
+
needs no `initialize`). */
|
|
26
|
+
rpc<T>(method: string, params?: unknown): Promise<T>;
|
|
23
27
|
private request;
|
|
24
28
|
private refresh;
|
|
25
29
|
}
|
package/dist/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { upsertConnection, writeCredentials } from "./credentials.js";
|
|
2
|
-
import { CliError } from "./errors.js";
|
|
2
|
+
import { CliError, SignInNeeded } from "./errors.js";
|
|
3
3
|
import { refreshTokens } from "./oauth.js";
|
|
4
4
|
/** Calls to the agent API (/v1/agent/*) with a connection's bearer token. */
|
|
5
5
|
export class ApiError extends Error {
|
|
@@ -12,14 +12,14 @@ export class ApiError extends Error {
|
|
|
12
12
|
this.code = code;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
export async function apiRequest(api, method, path, token, body, f) {
|
|
15
|
+
export async function apiRequest(api, method, path, token, body, f, accept = "application/json") {
|
|
16
16
|
let res;
|
|
17
17
|
try {
|
|
18
18
|
res = await f(`${api}${path}`, {
|
|
19
19
|
method,
|
|
20
20
|
headers: {
|
|
21
21
|
authorization: `Bearer ${token}`,
|
|
22
|
-
accept
|
|
22
|
+
accept,
|
|
23
23
|
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
24
24
|
},
|
|
25
25
|
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
@@ -57,19 +57,29 @@ export class Session {
|
|
|
57
57
|
send(method, path, body) {
|
|
58
58
|
return this.request(method, path, body);
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
/** One JSON-RPC call to the hosted MCP endpoint — the tools an agent
|
|
61
|
+
gets over MCP, from the shell (the endpoint is stateless: a call
|
|
62
|
+
needs no `initialize`). */
|
|
63
|
+
async rpc(method, params) {
|
|
64
|
+
const answer = await this.request("POST", "/mcp", { jsonrpc: "2.0", id: 1, method, ...(params !== undefined ? { params } : {}) }, "application/json, text/event-stream");
|
|
65
|
+
if (answer?.error || answer?.result === undefined) {
|
|
66
|
+
throw new CliError(answer?.error?.message ?? "Mapled gave no answer. Try again.");
|
|
67
|
+
}
|
|
68
|
+
return answer.result;
|
|
69
|
+
}
|
|
70
|
+
async request(method, path, body, accept) {
|
|
61
71
|
if (new Date(this.conn.expiresAt).getTime() - Date.now() < 60_000)
|
|
62
72
|
await this.refresh();
|
|
63
73
|
try {
|
|
64
|
-
return await apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
|
|
74
|
+
return await apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f, accept);
|
|
65
75
|
}
|
|
66
76
|
catch (err) {
|
|
67
77
|
if (err instanceof ApiError && err.status === 401) {
|
|
68
78
|
if (err.code === "TOKEN_EXPIRED") {
|
|
69
79
|
await this.refresh();
|
|
70
|
-
return apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
|
|
80
|
+
return apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f, accept);
|
|
71
81
|
}
|
|
72
|
-
throw new
|
|
82
|
+
throw new SignInNeeded(`The connection to ${this.conn.projectName} was revoked. Run \`mapled auth login\` to sign in again.`);
|
|
73
83
|
}
|
|
74
84
|
throw err;
|
|
75
85
|
}
|
|
@@ -80,7 +90,7 @@ export class Session {
|
|
|
80
90
|
tokens = await refreshTokens(this.conn.api, this.conn.clientId, this.conn.refreshToken, this.f);
|
|
81
91
|
}
|
|
82
92
|
catch {
|
|
83
|
-
throw new
|
|
93
|
+
throw new SignInNeeded(`Your sign-in to ${this.conn.projectName} expired. Run \`mapled auth login\` to sign in again.`);
|
|
84
94
|
}
|
|
85
95
|
this.conn = { ...this.conn, ...tokens };
|
|
86
96
|
this.store = upsertConnection(this.store, this.conn);
|
package/dist/args.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CliError } from "./errors.js";
|
|
|
2
2
|
/** Minimal argument parsing: the leading words name the command;
|
|
3
3
|
`--flag`, `--flag=value` and `--flag value` (for the flags that take
|
|
4
4
|
one) follow. Nothing here is user content beyond the command line. */
|
|
5
|
-
const VALUE_FLAGS = new Set(["api", "project", "out", "key", "locale"]);
|
|
5
|
+
const VALUE_FLAGS = new Set(["api", "project", "out", "key", "locale", "input"]);
|
|
6
6
|
export function parseArgs(argv) {
|
|
7
7
|
const words = [];
|
|
8
8
|
const flags = {};
|
package/dist/commands.d.ts
CHANGED
|
@@ -22,6 +22,19 @@ type Flags = ParsedArgs["flags"];
|
|
|
22
22
|
export declare function login(ctx: Ctx, flags: Flags): Promise<Connection>;
|
|
23
23
|
export declare function logout(ctx: Ctx, flags: Flags): Promise<void>;
|
|
24
24
|
export declare function link(ctx: Ctx, flags: Flags): Promise<FoundConfig>;
|
|
25
|
+
/** `mapled connect [--project <id>]` — everything an agent needs before
|
|
26
|
+
its first tool call, in one run: sign in through the browser (the
|
|
27
|
+
consent opens on the named project), write mapled.json, print
|
|
28
|
+
Mapled's guide for agents. A repository that is already connected
|
|
29
|
+
opens no browser. */
|
|
30
|
+
export declare function connect(ctx: Ctx, flags: Flags): Promise<void>;
|
|
31
|
+
/** `mapled tools [--json]` — every tool the hosted MCP endpoint offers,
|
|
32
|
+
with its description and input: what `mapled tool` can call. */
|
|
33
|
+
export declare function tools(ctx: Ctx, flags: Flags): Promise<void>;
|
|
34
|
+
/** `mapled tool <name> [<json> | --input <file>]` — one tool call, the
|
|
35
|
+
way an MCP client makes it: the answer is printed as Mapled worded
|
|
36
|
+
it, a refusal exits 1. */
|
|
37
|
+
export declare function tool(ctx: Ctx, flags: Flags, words: string[]): Promise<number>;
|
|
25
38
|
export declare function generate(ctx: Ctx, flags: Flags): Promise<void>;
|
|
26
39
|
export declare function schemaPull(ctx: Ctx, flags: Flags): Promise<void>;
|
|
27
40
|
export declare function schemaDiff(ctx: Ctx, flags: Flags): Promise<number>;
|
package/dist/commands.js
CHANGED
|
@@ -2,10 +2,10 @@ import { readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ApiError, apiGet, Session } from "./api.js";
|
|
4
4
|
import { stringFlag } from "./args.js";
|
|
5
|
-
import { CONFIG_FILE, DEFAULT_TYPES_PATH, findConfig, resolveApi, writeConfig } from "./config.js";
|
|
5
|
+
import { CONFIG_FILE, DEFAULT_API, DEFAULT_TYPES_PATH, findConfig, resolveApi, writeConfig } from "./config.js";
|
|
6
6
|
import { connectionsFor, findConnection, readCredentials, removeConnection, upsertConnection, writeCredentials, } from "./credentials.js";
|
|
7
7
|
import * as dr from "./doctor.js";
|
|
8
|
-
import { CliError } from "./errors.js";
|
|
8
|
+
import { CliError, SignInNeeded } from "./errors.js";
|
|
9
9
|
import { checkManifest, compareManifests, fileExistsIn, MANIFEST_FILE, manifestSummary, orderManifest, parseManifest, plural, readManifestFile, writeManifestFile, } from "./manifest.js";
|
|
10
10
|
import { authorize, clientKnown, exchangeCode, registerClient, revokeToken } from "./oauth.js";
|
|
11
11
|
import { formatChecks, GLYPH, paint, summarize, useColor } from "./output.js";
|
|
@@ -28,6 +28,14 @@ async function ensureClient(store, api, f) {
|
|
|
28
28
|
clientId,
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
/** `--project <id>`: a project id as Mapled writes it, or nothing. */
|
|
32
|
+
function projectFlag(flags) {
|
|
33
|
+
const value = stringFlag(flags, "project")?.toLowerCase();
|
|
34
|
+
if (value !== undefined && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value)) {
|
|
35
|
+
throw new CliError("--project takes a project id — the one in the prompt copied from Mapled.");
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
31
39
|
export async function login(ctx, flags) {
|
|
32
40
|
const found = await findConfig(ctx.cwd);
|
|
33
41
|
const api = resolveApi(found?.config ?? null, stringFlag(flags, "api"), ctx.env);
|
|
@@ -38,6 +46,7 @@ export async function login(ctx, flags) {
|
|
|
38
46
|
openBrowser: flags["no-browser"] ? async () => false : ctx.openBrowser,
|
|
39
47
|
log: ctx.out,
|
|
40
48
|
timeoutMs: ctx.loginTimeoutMs,
|
|
49
|
+
project: projectFlag(flags),
|
|
41
50
|
});
|
|
42
51
|
const tokens = await exchangeCode(api, client.clientId, grant, ctx.fetch);
|
|
43
52
|
const status = await apiGet(api, "/v1/agent/integration", tokens.accessToken, ctx.fetch);
|
|
@@ -51,7 +60,7 @@ export async function login(ctx, flags) {
|
|
|
51
60
|
};
|
|
52
61
|
store = upsertConnection(store, conn);
|
|
53
62
|
await writeCredentials(ctx.credentialsFile, store);
|
|
54
|
-
ctx.out(`Signed in to ${conn.projectName}.`);
|
|
63
|
+
ctx.out(`Signed in to ${clean(conn.projectName, 80)}.`);
|
|
55
64
|
if (!found)
|
|
56
65
|
ctx.out("Next: run `mapled project link` in the site's repository.");
|
|
57
66
|
else if (found.config.project !== conn.projectId) {
|
|
@@ -131,6 +140,122 @@ export async function link(ctx, flags) {
|
|
|
131
140
|
ctx.out("Next: `mapled types generate`, then `mapled doctor`.");
|
|
132
141
|
return { path: file, dir: path.resolve(ctx.cwd), config };
|
|
133
142
|
}
|
|
143
|
+
/** `mapled connect [--project <id>]` — everything an agent needs before
|
|
144
|
+
its first tool call, in one run: sign in through the browser (the
|
|
145
|
+
consent opens on the named project), write mapled.json, print
|
|
146
|
+
Mapled's guide for agents. A repository that is already connected
|
|
147
|
+
opens no browser. */
|
|
148
|
+
export async function connect(ctx, flags) {
|
|
149
|
+
const wanted = projectFlag(flags);
|
|
150
|
+
// the nearest mapled.json, here or above: a subdirectory of a connected
|
|
151
|
+
// repository is connected
|
|
152
|
+
const linked = await findConfig(ctx.cwd);
|
|
153
|
+
const flagApi = stringFlag(flags, "api");
|
|
154
|
+
const api = resolveApi(linked?.config ?? null, flagApi, ctx.env);
|
|
155
|
+
// The guide is text an agent follows, so it comes from Mapled or from
|
|
156
|
+
// an origin the person named on this run (the flag, the environment) —
|
|
157
|
+
// never from one only a file of the repository names: mapled.json is
|
|
158
|
+
// committed, and whoever wrote it would be writing the agent's guide.
|
|
159
|
+
const guideApi = !flagApi && linked?.config.api && api !== DEFAULT_API ? DEFAULT_API : api;
|
|
160
|
+
const store = await readCredentials(ctx.credentialsFile);
|
|
161
|
+
const target = wanted ?? linked?.config.project;
|
|
162
|
+
let conn = target ? findConnection(store, api, target) : undefined;
|
|
163
|
+
if (conn) {
|
|
164
|
+
// a connection revoked in Integrations is of no use: ask the API once
|
|
165
|
+
try {
|
|
166
|
+
await new Session(store, ctx.credentialsFile, conn, ctx.fetch).get("/v1/agent/integration");
|
|
167
|
+
ctx.out(`Already connected to ${clean(conn.projectName, 80)}.`);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
// only a connection that is gone asks for a new sign-in: an API
|
|
171
|
+
// that can't be reached right now is said as it is
|
|
172
|
+
if (!(err instanceof SignInNeeded))
|
|
173
|
+
throw err;
|
|
174
|
+
conn = undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (!conn && api !== guideApi) {
|
|
178
|
+
// Signing in sends the person's browser to this origin, and what it
|
|
179
|
+
// answers is printed for the agent: an origin only the committed
|
|
180
|
+
// mapled.json names has to be named by the person too.
|
|
181
|
+
throw new CliError(`${CONFIG_FILE} here names another API, ${api}. To sign in there, run \`mapled connect --api ${api}\`.`);
|
|
182
|
+
}
|
|
183
|
+
if (!conn) {
|
|
184
|
+
// a repository linked before opens the consent on its own project
|
|
185
|
+
conn = await login(ctx, target ? { ...flags, project: target } : flags);
|
|
186
|
+
if (wanted && conn.projectId !== wanted) {
|
|
187
|
+
ctx.out(`Note: ${clean(conn.projectName, 80)} was picked in the browser — not the project the prompt named. Continuing with it.`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!linked || linked.config.project !== conn.projectId) {
|
|
191
|
+
await link(ctx, { ...flags, project: conn.projectId });
|
|
192
|
+
}
|
|
193
|
+
ctx.out("");
|
|
194
|
+
try {
|
|
195
|
+
const res = await ctx.fetch(`${guideApi}/agents.md`);
|
|
196
|
+
if (!res.ok)
|
|
197
|
+
throw new Error();
|
|
198
|
+
for (const line of (await res.text()).split("\n"))
|
|
199
|
+
ctx.out(line);
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
ctx.out(`Connected. The guide for agents: ${guideApi}/agents.md`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** `mapled tools [--json]` — every tool the hosted MCP endpoint offers,
|
|
206
|
+
with its description and input: what `mapled tool` can call. */
|
|
207
|
+
export async function tools(ctx, flags) {
|
|
208
|
+
const { session } = await openSession(ctx, flags);
|
|
209
|
+
const { tools: list } = await session.rpc("tools/list");
|
|
210
|
+
if (flags.json) {
|
|
211
|
+
ctx.out(JSON.stringify(list, null, 2));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const tool of list) {
|
|
215
|
+
const required = new Set(tool.inputSchema?.required ?? []);
|
|
216
|
+
const input = Object.keys(tool.inputSchema?.properties ?? {}).map((k) => (required.has(k) ? k : `${k}?`));
|
|
217
|
+
ctx.out(`${tool.name}${input.length > 0 ? ` { ${input.join(", ")} }` : ""}`);
|
|
218
|
+
if (tool.description)
|
|
219
|
+
ctx.out(` ${tool.description}`);
|
|
220
|
+
ctx.out("");
|
|
221
|
+
}
|
|
222
|
+
ctx.out("Call one: `mapled tool <name> '<json>'` or `mapled tool <name> --input <file.json>`; `mapled tools --json` shows each input's schema.");
|
|
223
|
+
}
|
|
224
|
+
/** `mapled tool <name> [<json> | --input <file>]` — one tool call, the
|
|
225
|
+
way an MCP client makes it: the answer is printed as Mapled worded
|
|
226
|
+
it, a refusal exits 1. */
|
|
227
|
+
export async function tool(ctx, flags, words) {
|
|
228
|
+
const [name, inline] = words;
|
|
229
|
+
if (!name)
|
|
230
|
+
throw new CliError("Which tool? `mapled tools` lists them.", 2);
|
|
231
|
+
const file = stringFlag(flags, "input");
|
|
232
|
+
if (file !== undefined && inline !== undefined)
|
|
233
|
+
throw new CliError("Pass the input as JSON or with --input, not both.", 2);
|
|
234
|
+
let raw = inline ?? "{}";
|
|
235
|
+
if (file !== undefined) {
|
|
236
|
+
try {
|
|
237
|
+
raw = await readFile(path.resolve(ctx.cwd, file), "utf8");
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
throw new CliError(`Couldn't read ${file}.`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
let args;
|
|
244
|
+
try {
|
|
245
|
+
args = JSON.parse(raw);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
throw new CliError("The input isn't valid JSON.");
|
|
249
|
+
}
|
|
250
|
+
if (args === null || typeof args !== "object" || Array.isArray(args))
|
|
251
|
+
throw new CliError("The input must be a JSON object.");
|
|
252
|
+
const { session } = await openSession(ctx, flags);
|
|
253
|
+
const answer = await session.rpc("tools/call", { name, arguments: args });
|
|
254
|
+
for (const part of answer.content ?? [])
|
|
255
|
+
if (part.type === "text" && part.text !== undefined)
|
|
256
|
+
ctx.out(part.text);
|
|
257
|
+
return answer.isError ? 1 : 0;
|
|
258
|
+
}
|
|
134
259
|
async function openSession(ctx, flags) {
|
|
135
260
|
const found = await findConfig(ctx.cwd);
|
|
136
261
|
if (!found)
|
package/dist/doctor.js
CHANGED
|
@@ -1082,6 +1082,15 @@ export function checkWebhook(webhook, site) {
|
|
|
1082
1082
|
detail: `${host} resolves to a private address, which Mapled can't reach. Point the webhook at the public deployment.`,
|
|
1083
1083
|
};
|
|
1084
1084
|
}
|
|
1085
|
+
if (last.responseStatus === 503) {
|
|
1086
|
+
// what the SDK's route answers before the site has the signing secret (deployed first, as the README says)
|
|
1087
|
+
return {
|
|
1088
|
+
key: "webhook",
|
|
1089
|
+
label,
|
|
1090
|
+
status: "failed",
|
|
1091
|
+
detail: `The last delivery to ${host} responded 503 — the route answers that while MAPLED_WEBHOOK_SECRET isn't set on the site. Set it there, redeploy, then send a test ping.`,
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1085
1094
|
const reason = last.responseStatus ? `responded ${last.responseStatus}` : (last.error ?? "failed");
|
|
1086
1095
|
return {
|
|
1087
1096
|
key: "webhook",
|
package/dist/errors.d.ts
CHANGED
|
@@ -4,3 +4,9 @@ export declare class CliError extends Error {
|
|
|
4
4
|
exitCode: number;
|
|
5
5
|
constructor(message: string, exitCode?: number);
|
|
6
6
|
}
|
|
7
|
+
/** The stored connection no longer works — revoked in Mapled, or its
|
|
8
|
+
refresh token spent: only a new sign-in helps. A network failure is
|
|
9
|
+
not this. */
|
|
10
|
+
export declare class SignInNeeded extends CliError {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
package/dist/errors.js
CHANGED
|
@@ -8,3 +8,12 @@ export class CliError extends Error {
|
|
|
8
8
|
this.exitCode = exitCode;
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
/** The stored connection no longer works — revoked in Mapled, or its
|
|
12
|
+
refresh token spent: only a new sign-in helps. A network failure is
|
|
13
|
+
not this. */
|
|
14
|
+
export class SignInNeeded extends CliError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "SignInNeeded";
|
|
18
|
+
}
|
|
19
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { createInterface } from "node:readline/promises";
|
|
4
4
|
import { parseArgs } from "./args.js";
|
|
5
5
|
import { openBrowser } from "./browser.js";
|
|
6
|
-
import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, preview, scan, schemaDiff, schemaPull, } from "./commands.js";
|
|
6
|
+
import { bindingsPull, bindingsPush, doctor, formsCreate, formsList, generate, integrationReport, link, login, logout, manifestValidate, mdPull, preview, scan, connect, tool, tools, schemaDiff, schemaPull, } from "./commands.js";
|
|
7
7
|
import { credentialsPath } from "./credentials.js";
|
|
8
8
|
import { CliError } from "./errors.js";
|
|
9
9
|
/** `mapled` — sign in, link a repository to its project, generate types
|
|
@@ -13,6 +13,9 @@ import { CliError } from "./errors.js";
|
|
|
13
13
|
const HELP = `mapled — the Mapled CLI
|
|
14
14
|
|
|
15
15
|
Usage
|
|
16
|
+
mapled connect [--project <id>] Connect this repository: sign in, write mapled.json, print the guide for AI agents
|
|
17
|
+
mapled tools [--json] List the Mapled tools an agent can call
|
|
18
|
+
mapled tool <name> [<json> | --input <file>] Call one tool — the same ones the MCP server offers
|
|
16
19
|
mapled auth login [--api <origin>] [--no-browser] Sign in to a project in your browser
|
|
17
20
|
mapled auth logout [--all] Revoke this machine's access to the linked project
|
|
18
21
|
mapled project link [--project <id>] Write mapled.json for this repository
|
|
@@ -36,6 +39,7 @@ Options
|
|
|
36
39
|
|
|
37
40
|
Credentials stay in your user config directory, never in the repository.
|
|
38
41
|
Run it as \`npx @mapled/cli <command>\`, or install it globally with \`npm install -g @mapled/cli\` to get \`mapled\` on your PATH.`;
|
|
42
|
+
const ROOT_COMMANDS = new Set(["preview", "connect", "tools", "tool"]);
|
|
39
43
|
function version() {
|
|
40
44
|
return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
41
45
|
}
|
|
@@ -80,8 +84,17 @@ async function main(argv) {
|
|
|
80
84
|
version: version(),
|
|
81
85
|
credentialsFile: credentialsPath(),
|
|
82
86
|
};
|
|
83
|
-
//
|
|
84
|
-
|
|
87
|
+
// commands of one word: what follows them is theirs (`preview` takes the
|
|
88
|
+
// record it opens, `tool` the tool it calls)
|
|
89
|
+
switch (ROOT_COMMANDS.has(group) ? group : `${group} ${sub ?? ""}`.trim()) {
|
|
90
|
+
case "connect":
|
|
91
|
+
await connect(ctx, flags);
|
|
92
|
+
return 0;
|
|
93
|
+
case "tools":
|
|
94
|
+
await tools(ctx, flags);
|
|
95
|
+
return 0;
|
|
96
|
+
case "tool":
|
|
97
|
+
return tool(ctx, flags, words.slice(1));
|
|
85
98
|
case "auth login":
|
|
86
99
|
await login(ctx, flags);
|
|
87
100
|
return 0;
|
package/dist/oauth.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export declare function registerClient(api: string, f: Fetch): Promise<string>;
|
|
|
30
30
|
creates nothing on the server. */
|
|
31
31
|
export declare function clientKnown(api: string, clientId: string, f: Fetch): Promise<boolean>;
|
|
32
32
|
export type AuthorizeDeps = {
|
|
33
|
+
/** The project the consent screen opens on — a hint; the person still picks. */
|
|
34
|
+
project?: string;
|
|
33
35
|
openBrowser: (url: string) => Promise<boolean>;
|
|
34
36
|
log: (line: string) => void;
|
|
35
37
|
timeoutMs?: number;
|
package/dist/oauth.js
CHANGED
|
@@ -149,6 +149,7 @@ export function authorize(api, clientId, deps) {
|
|
|
149
149
|
state,
|
|
150
150
|
scope: SCOPE,
|
|
151
151
|
resource: `${api}/mcp`,
|
|
152
|
+
...(deps.project ? { project: deps.project } : {}),
|
|
152
153
|
}).toString();
|
|
153
154
|
deps.log(`Opening your browser to sign in to Mapled. If it doesn't open, visit:\n ${url.toString()}`);
|
|
154
155
|
deps
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mapled/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Mapled CLI — sign in, link a project, generate TypeScript types for its content, create forms, preview drafts and check the site's integration.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://mapled.io",
|
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
"cms",
|
|
10
10
|
"headless-cms",
|
|
11
11
|
"cli",
|
|
12
|
+
"ai-agents",
|
|
13
|
+
"mcp",
|
|
12
14
|
"typescript",
|
|
13
15
|
"codegen"
|
|
14
16
|
],
|