drafted 1.19.17 → 1.19.18
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/cli/drafted.mjs +7 -2
- package/mcp/server.mjs +25 -14
- package/package.json +1 -1
package/cli/drafted.mjs
CHANGED
|
@@ -301,9 +301,14 @@ async function readApiGet(command, apiPath, org) {
|
|
|
301
301
|
// so a 401/403 here means the credential is genuinely missing/expired — surface
|
|
302
302
|
// an actionable next step instead of the bare server "Unauthenticated" string.
|
|
303
303
|
if (res.status === 401 || res.status === 403) {
|
|
304
|
+
// Name BOTH paths. `drafted login` is interactive, so an agent that reads
|
|
305
|
+
// only this line hands its user a shell command instead of a link — which
|
|
306
|
+
// is what happened. An agent should call the MCP auth tool and surface the
|
|
307
|
+
// URL it returns; approval writes the shared auth file, fixing the CLI too.
|
|
304
308
|
msg = `Not authenticated for ${serverUrl} — your Drafted session is missing or expired. `
|
|
305
|
-
+ `
|
|
306
|
-
+ `
|
|
309
|
+
+ `If you are a person at a terminal: run \`drafted login\`. `
|
|
310
|
+
+ `If you are an agent: call the Drafted MCP auth tool with action="get_link" and give the URL to your user — you cannot complete a sign-in yourself. `
|
|
311
|
+
+ `Either way the approval lands in ${DEFAULT_AUTH_FILE}, which the CLI and MCP share.`;
|
|
307
312
|
}
|
|
308
313
|
jsonOut(false, command, msg);
|
|
309
314
|
console.error(`❌ ${msg}`);
|
package/mcp/server.mjs
CHANGED
|
@@ -2082,12 +2082,33 @@ async function waitForBootstrapAuth(deadline, staleSid) {
|
|
|
2082
2082
|
return null;
|
|
2083
2083
|
}
|
|
2084
2084
|
|
|
2085
|
-
if (!isRemote) tool('auth', 'Sign in to Drafted
|
|
2085
|
+
if (!isRemote) tool('auth', 'Sign in to Drafted.\n\n`action=get_link` ALWAYS returns a sign-in URL. This is the action to use when you are an agent: you cannot complete a sign-in, so put the URL in your reply and stop — the human opens it in their own browser, approves, and your next Drafted call picks up the session. Approval also writes ~/.drafted/auth.json, so the `drafted` CLI is signed in too.\n\n`action=login` signs in and WAITS for it to land, opening the desktop app when one is installed. Use it only when a human is watching this terminal.\n\n(The web MCP uses OAuth2 and does not have this tool.)', {
|
|
2086
2086
|
action: z.enum(['get_link', 'login']).describe('Operation to perform.'),
|
|
2087
2087
|
}, async ({ action }) => {
|
|
2088
2088
|
try {
|
|
2089
|
-
//
|
|
2090
|
-
//
|
|
2089
|
+
// get_link's ONE job is to return a URL a human can click. It runs before the
|
|
2090
|
+
// already-authenticated check and before the desktop app on purpose:
|
|
2091
|
+
// - an agent cannot complete a sign-in, so handing back a link is the only
|
|
2092
|
+
// safe shape — the human clicks it out of band, in their own browser;
|
|
2093
|
+
// - "already authenticated" is about THIS MCP's session, which says nothing
|
|
2094
|
+
// about the CLI's on-disk credential, so short-circuiting on it used to
|
|
2095
|
+
// make the link unobtainable exactly when it was needed;
|
|
2096
|
+
// - opening the desktop app returns no link at all, which is useless to an
|
|
2097
|
+
// agent that must put something in its reply.
|
|
2098
|
+
// Approval writes ~/.drafted/auth.json, so the CLI picks it up too.
|
|
2099
|
+
if (action === 'get_link') {
|
|
2100
|
+
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
2101
|
+
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
2102
|
+
const data = await codeRes.json();
|
|
2103
|
+
persistPendingDeviceCode(data);
|
|
2104
|
+
return ok({
|
|
2105
|
+
signInUrl: data.verificationUrl,
|
|
2106
|
+
instruction: 'Give this URL to the user and stop. They open it in their own browser and approve; you cannot complete this for them. Retry your request afterwards — the next Drafted call picks up the approved session.',
|
|
2107
|
+
expiresInSeconds: data.expiresIn ?? null,
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
// `login` waits for the sign-in to land, so the desktop app is the better
|
|
2111
|
+
// surface when one is installed: no code to type.
|
|
2091
2112
|
{
|
|
2092
2113
|
const activeSession = getState().sessionId;
|
|
2093
2114
|
const bootstrapSession = getBootstrapSessionId();
|
|
@@ -2108,9 +2129,6 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. On a local install the DESKTOP
|
|
|
2108
2129
|
}
|
|
2109
2130
|
const staleSid = getBootstrapSessionId();
|
|
2110
2131
|
if (await launchDesktopSignin()) {
|
|
2111
|
-
if (action === 'get_link') {
|
|
2112
|
-
return ok('Opening the Drafted app to sign in — approve in the app window, then retry your request.');
|
|
2113
|
-
}
|
|
2114
2132
|
const sid = await waitForBootstrapAuth(Date.now() + 180000, staleSid);
|
|
2115
2133
|
if (!sid) throw new Error('Timed out waiting for sign-in. Complete sign-in in the Drafted app window, then retry.');
|
|
2116
2134
|
getState().sessionId = null;
|
|
@@ -2129,13 +2147,6 @@ if (!isRemote) tool('auth', 'Sign in to Drafted. On a local install the DESKTOP
|
|
|
2129
2147
|
return ok({ status: 'logged_in', via: 'desktop-app' });
|
|
2130
2148
|
}
|
|
2131
2149
|
}
|
|
2132
|
-
if (action === 'get_link') {
|
|
2133
|
-
const codeRes = await serverFetch(`${getServerUrl()}/auth/device/code`, { method: 'POST' });
|
|
2134
|
-
if (!codeRes.ok) throw new Error(`Failed to start device authorization (HTTP ${codeRes.status})`);
|
|
2135
|
-
const data = await codeRes.json();
|
|
2136
|
-
persistPendingDeviceCode(data);
|
|
2137
|
-
return ok(data.verificationUrl);
|
|
2138
|
-
}
|
|
2139
2150
|
if (action === 'login') {
|
|
2140
2151
|
// Prefer the active request session (injected by runWithRequestState on
|
|
2141
2152
|
// remote, or cloneSession on stdio) over the on-disk bootstrap session, so
|
|
@@ -3802,7 +3813,7 @@ tool('fs', 'Navigate Drafted like a local filesystem. A FOLDER is the single con
|
|
|
3802
3813
|
// Agents reach the repos table through this tool. Content stays in git;
|
|
3803
3814
|
// Drafted keeps a read-only index. `add`/`rescan` mutate; `list`/`entries`
|
|
3804
3815
|
// are read-only and paginated with a compact mode (collection rule).
|
|
3805
|
-
tool('repo', 'Registered git repos — the org index of .agents/ skills + identities. A connected repo is the source of truth for its skills (git owns content, Drafted renders + indexes). Dispatch by `action`:\n- `list` — the org\'s linked repos (paginated, compact mode).\n\nLINKING AND UNLINKING ARE NOT HERE. A person does them in the Drafted UI (Settings > Organization > GitHub to connect an account, then the folder menu), because linking moves a folder\'s wiki + skills into git and makes Drafted read-only for them. Agents read and rescan; they do not change who owns the content.\n
|
|
3816
|
+
tool('repo', 'Registered git repos — the org index of .agents/ skills + identities. A connected repo is the source of truth for its skills (git owns content, Drafted renders + indexes). Dispatch by `action`:\n- `list` — the org\'s linked repos (paginated, compact mode).\n- `entries` — search the index across all the org\'s repos (skills + identities).\n- `rescan` — re-fetch the tracked branch and refresh the index.\n\nLINKING AND UNLINKING ARE NOT HERE. A person does them in the Drafted UI (Settings > Organization > GitHub to connect an account, then the folder menu), because linking moves a folder\'s wiki + skills into git and makes Drafted read-only for them. Agents read and rescan; they do not change who owns the content.\n\nAuthoring a Drafted skill whose slug collides with a repo-indexed skill returns 409 repo_owned pointing at the repo — commit to git instead.', {
|
|
3806
3817
|
action: z.enum(['list', 'entries', 'rescan']).describe('list: the org\'s connected repos; entries: search the git index across them; rescan: re-fetch a tracked branch and refresh its index.'),
|
|
3807
3818
|
slug: z.string().optional().describe('[rescan] the repo slug or UUID.'),
|
|
3808
3819
|
repoId: z.string().optional().describe('[rescan] repo UUID (alternative to slug).'),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drafted",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.18",
|
|
4
4
|
"description": "Drafted — visual thinking surface for humans and AI agents. Renders HTML, markdown, images, and code as frames on a zoomable canvas, with MCP tools for AI agents and real-time sync for humans.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|