@cursor/july 0.1.32 → 0.1.34
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 +1 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -5
- package/dist/bin/agent-serve.d.ts +1 -0
- package/dist/bin/agent-serve.d.ts.map +1 -1
- package/dist/bin/agent-serve.js +32 -2
- package/dist/channels/github/types.d.ts +10 -7
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.C2aVczmo.js → app.FPupl4SP.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.WoYunhnT.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DLEzetbD.js → VPLocalSearchBox.Cd182Cu0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.BkOH1oMp.js → theme.BEM3Okcd.js} +2 -2
- package/dist/docs/assets/{example-agents_weather-agent.md.Dmrcphhl.js → example-agents_weather-agent.md.BADkPqxQ.js} +6 -4
- package/dist/docs/assets/example-agents_weather-agent.md.BADkPqxQ.lean.js +1 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.js +9 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.lean.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +10 -8
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/files-backends/agent-store-presigned-url.d.ts.map +1 -1
- package/dist/files-backends/agent-store-presigned-url.js +19 -810
- package/dist/files.d.ts +20 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/artifacts-store.d.ts +14 -6
- package/dist/internal/artifacts-store.d.ts.map +1 -1
- package/dist/internal/artifacts-store.js +160 -16
- package/dist/internal/cli-convert-automation.d.ts +39 -0
- package/dist/internal/cli-convert-automation.d.ts.map +1 -0
- package/dist/internal/cli-convert-automation.js +280 -0
- package/dist/internal/cli-deploy.d.ts +2 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/cli-mcp-oauth.d.ts +23 -0
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +57 -33
- package/dist/internal/convert-automation/convert-workflow.d.ts +10 -0
- package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -0
- package/dist/internal/convert-automation/convert-workflow.js +998 -0
- package/dist/internal/convert-automation/index.d.ts +3 -0
- package/dist/internal/convert-automation/index.d.ts.map +1 -0
- package/dist/internal/convert-automation/index.js +1 -0
- package/dist/internal/convert-automation/slug.d.ts +11 -0
- package/dist/internal/convert-automation/slug.d.ts.map +1 -0
- package/dist/internal/convert-automation/slug.js +36 -0
- package/dist/internal/convert-automation/types.d.ts +191 -0
- package/dist/internal/convert-automation/types.d.ts.map +1 -0
- package/dist/internal/convert-automation/types.js +13 -0
- package/dist/internal/cursor-relay-core.d.ts +12 -0
- package/dist/internal/cursor-relay-core.d.ts.map +1 -1
- package/dist/internal/cursor-relay-core.js +42 -8
- package/dist/internal/deploy-client.d.ts +31 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +48 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +3 -32
- package/dist/internal/init-project.d.ts +1 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +1 -1
- package/dist/internal/mcp-endpoint.d.ts +6 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +168 -0
- package/dist/internal/resolved-connections.d.ts +15 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +12 -5
- package/dist/internal/review-comments.d.ts +9 -1
- package/dist/internal/review-comments.d.ts.map +1 -1
- package/dist/internal/review-comments.js +76 -0
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +83 -8
- package/dist/internal/session-engine.d.ts +56 -3
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +252 -83
- package/dist/internal/storage-coordinator.d.ts +0 -15
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +0 -52
- package/dist/internal/workspace.d.ts +10 -5
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +13 -0
- package/dist/memory.d.ts +22 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +100 -8
- package/dist/playground/assets/index-CDDWw0YX.js +85 -0
- package/dist/playground/assets/index-MVuNTd8v.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +0 -4
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/example-agents/weather-agent.md +29 -11
- package/docs/guides/cloud-runtime.md +6 -4
- package/package.json +3 -22
- package/src/artifacts.ts +0 -6
- package/src/bin/agent-serve.ts +36 -2
- package/src/channels/github/types.ts +10 -7
- package/src/files-backends/agent-store-presigned-url.ts +7 -5
- package/src/files.ts +27 -0
- package/src/index.ts +6 -1
- package/src/internal/artifacts-store.ts +185 -22
- package/src/internal/cli-convert-automation.ts +352 -0
- package/src/internal/cli-deploy.ts +1 -1
- package/src/internal/cli-mcp-oauth.ts +69 -31
- package/src/internal/convert-automation/convert-workflow.ts +1422 -0
- package/src/internal/convert-automation/index.ts +10 -0
- package/src/internal/convert-automation/slug.ts +38 -0
- package/src/internal/convert-automation/types.ts +199 -0
- package/src/internal/cursor-relay-core.ts +62 -3
- package/src/internal/deploy-client.ts +78 -0
- package/src/internal/discovery.ts +3 -37
- package/src/internal/init-project.ts +1 -1
- package/src/internal/mcp-endpoint.ts +243 -0
- package/src/internal/resolved-connections.ts +32 -6
- package/src/internal/review-comments.ts +102 -2
- package/src/internal/server.ts +108 -2
- package/src/internal/session-engine.ts +329 -99
- package/src/internal/storage-coordinator.ts +0 -62
- package/src/internal/workspace.ts +19 -5
- package/src/memory.ts +126 -10
- package/src/storage.ts +0 -6
- package/src/types.ts +15 -0
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.xgMnlUSO.js +0 -1
- package/dist/docs/assets/example-agents_weather-agent.md.Dmrcphhl.lean.js +0 -1
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.js +0 -9
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.lean.js +0 -1
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/multi-tenant.d.ts +0 -80
- package/dist/multi-tenant.d.ts.map +0 -1
- package/dist/multi-tenant.js +0 -69
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/index-50PKeJlG.css +0 -1
- package/dist/playground/assets/index-C0f2Wl1q.js +0 -85
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent-serve convert-automation <url>` — export a Cursor Automation into
|
|
3
|
+
* an agentkit project.
|
|
4
|
+
*
|
|
5
|
+
* Fetches the automation's workflow from the backend export endpoint
|
|
6
|
+
* (Bearer user API key, same credential chain as deploy), runs the pure
|
|
7
|
+
* converter, writes the converted files (wx — existing files are kept),
|
|
8
|
+
* fills in the init scaffold around them, and installs npm dependencies
|
|
9
|
+
* (like `init`). MCP servers convert to Cursor-account connections resolved
|
|
10
|
+
* at runtime (no URLs or credentials in the project; authorization is
|
|
11
|
+
* managed in Cursor, dashboard → MCP). The generated project is NEVER
|
|
12
|
+
* imported/executed here — it embeds server-derived data, so checking it is
|
|
13
|
+
* left to an explicit `validate` next step the user runs after review.
|
|
14
|
+
* Exit 0 even with warnings; nonzero only for bad args, auth/fetch
|
|
15
|
+
* failures, or filesystem write failures.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
19
|
+
import { dirname, join, resolve } from "node:path";
|
|
20
|
+
import {
|
|
21
|
+
type ConvertedMcpConnection,
|
|
22
|
+
type ConvertWorkflowResult,
|
|
23
|
+
convertWorkflow,
|
|
24
|
+
type WorkflowJson,
|
|
25
|
+
} from "./convert-automation/index.js";
|
|
26
|
+
import { slugify } from "./convert-automation/slug.js";
|
|
27
|
+
import {
|
|
28
|
+
cursorExternalApiUrl,
|
|
29
|
+
resolveApiKey,
|
|
30
|
+
SIGN_IN_HINT,
|
|
31
|
+
} from "./cursor/credentials.js";
|
|
32
|
+
import {
|
|
33
|
+
AgentServeDeployClient,
|
|
34
|
+
type AutomationExport,
|
|
35
|
+
DeployApiError,
|
|
36
|
+
} from "./deploy-client.js";
|
|
37
|
+
import { CLI_COMMAND_NAME as CLI } from "./distribution.js";
|
|
38
|
+
import {
|
|
39
|
+
formatCdCommand,
|
|
40
|
+
type InitFileEntry,
|
|
41
|
+
initProject,
|
|
42
|
+
installScaffoldDependencies,
|
|
43
|
+
isEexist,
|
|
44
|
+
} from "./init-project.js";
|
|
45
|
+
import { stdoutPalette } from "./terminal-style.js";
|
|
46
|
+
|
|
47
|
+
export interface ConvertAutomationCliOptions {
|
|
48
|
+
/** Positional: automation URL or bare uuid. */
|
|
49
|
+
url?: string;
|
|
50
|
+
/** Target directory; defaults to ./<automation-name-slug>. */
|
|
51
|
+
dir?: string;
|
|
52
|
+
json?: boolean;
|
|
53
|
+
apiKey?: string;
|
|
54
|
+
/** Test seams. */
|
|
55
|
+
fetchImpl?: typeof fetch;
|
|
56
|
+
out?: (text: string) => void;
|
|
57
|
+
err?: (text: string) => void;
|
|
58
|
+
installImpl?: typeof installScaffoldDependencies;
|
|
59
|
+
cwd?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const UUID_PATTERN =
|
|
63
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract the automation uuid from a dashboard URL
|
|
67
|
+
* (`/automations/<uuid>` or `/custom-agents/<uuid>`, any origin — dev
|
|
68
|
+
* hosts included) or a bare uuid. Undefined when nothing matches.
|
|
69
|
+
*/
|
|
70
|
+
export function parseAutomationRef(input: string): string | undefined {
|
|
71
|
+
const trimmed = input.trim();
|
|
72
|
+
if (UUID_PATTERN.test(trimmed)) {
|
|
73
|
+
return trimmed.toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
let parsed: URL;
|
|
76
|
+
try {
|
|
77
|
+
parsed = new URL(trimmed);
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const segments = parsed.pathname.split("/").filter((s) => s !== "");
|
|
85
|
+
if (segments.length !== 2) {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
const [kind, uuid] = segments;
|
|
89
|
+
if (kind !== "automations" && kind !== "custom-agents") {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (uuid === undefined || !UUID_PATTERN.test(uuid)) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
return uuid.toLowerCase();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function cmdConvertAutomation(
|
|
99
|
+
options: ConvertAutomationCliOptions
|
|
100
|
+
): Promise<number> {
|
|
101
|
+
const out =
|
|
102
|
+
options.out ?? ((text: string) => void process.stdout.write(text));
|
|
103
|
+
const err =
|
|
104
|
+
options.err ?? ((text: string) => void process.stderr.write(text));
|
|
105
|
+
const json = options.json === true;
|
|
106
|
+
// Failures stay machine-readable under --json (stderr keeps the prose).
|
|
107
|
+
const fail = (message: string): 1 => {
|
|
108
|
+
if (json) {
|
|
109
|
+
out(`${JSON.stringify({ ok: false, error: message })}\n`);
|
|
110
|
+
}
|
|
111
|
+
err(`${message}\n`);
|
|
112
|
+
return 1;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
if (options.url === undefined || options.url.trim() === "") {
|
|
116
|
+
return fail(
|
|
117
|
+
`Usage: ${CLI} convert-automation <automation-url-or-uuid> [--dir <path>] [--json]`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const uuid = parseAutomationRef(options.url);
|
|
121
|
+
if (uuid === undefined) {
|
|
122
|
+
return fail(
|
|
123
|
+
`Could not parse ${JSON.stringify(options.url)} — expected an automation URL (https://cursor.com/automations/<uuid> or .../custom-agents/<uuid>) or a bare uuid.`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const resolved = await resolveApiKey({ apiKey: options.apiKey });
|
|
128
|
+
if (resolved === undefined) {
|
|
129
|
+
return fail(`Not signed in to Cursor. ${SIGN_IN_HINT}`);
|
|
130
|
+
}
|
|
131
|
+
const client = new AgentServeDeployClient({
|
|
132
|
+
backendUrl: cursorExternalApiUrl(),
|
|
133
|
+
apiKey: resolved.apiKey,
|
|
134
|
+
fetchImpl: options.fetchImpl,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
let payload: AutomationExport;
|
|
138
|
+
try {
|
|
139
|
+
payload = await client.exportAutomation({ uuid });
|
|
140
|
+
} catch (error) {
|
|
141
|
+
if (error instanceof DeployApiError) {
|
|
142
|
+
return fail(error.message);
|
|
143
|
+
}
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const result = convertWorkflow({
|
|
148
|
+
automation: payload.automation,
|
|
149
|
+
workflow: payload.workflow as WorkflowJson,
|
|
150
|
+
mcpServers: payload.mcpServers,
|
|
151
|
+
exportedAt: new Date().toISOString(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const displayDir =
|
|
155
|
+
options.dir ?? `./${slugify(payload.automation.name, "automation")}`;
|
|
156
|
+
const rootDir = resolve(options.cwd ?? process.cwd(), displayDir);
|
|
157
|
+
|
|
158
|
+
let files: InitFileEntry[];
|
|
159
|
+
try {
|
|
160
|
+
files = await writeProjectFiles(rootDir, result);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return fail(
|
|
163
|
+
`Could not write project files under ${rootDir}: ${describe(error)}`
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Same npm install as `init`, so the printed `validate` / `dev` next steps
|
|
168
|
+
// work immediately. Failure (npm exit or spawn error) is loud but never
|
|
169
|
+
// fails the conversion.
|
|
170
|
+
const install = options.installImpl ?? installScaffoldDependencies;
|
|
171
|
+
if (!json) {
|
|
172
|
+
out("\nInstalling npm packages\n");
|
|
173
|
+
}
|
|
174
|
+
let installed: Awaited<ReturnType<typeof installScaffoldDependencies>>;
|
|
175
|
+
try {
|
|
176
|
+
installed = await install(rootDir, {
|
|
177
|
+
stdio: json ? "pipe" : "inherit",
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
installed = { ok: false, exitCode: -1, stderr: describe(error) };
|
|
181
|
+
}
|
|
182
|
+
if (!installed.ok && !json) {
|
|
183
|
+
const reason = installed.stderr.trim() || `exit ${installed.exitCode}`;
|
|
184
|
+
err(
|
|
185
|
+
`npm install failed (${reason}); run \`npm install\` in ${displayDir} before \`${CLI} dev\`.\n`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (json) {
|
|
190
|
+
out(
|
|
191
|
+
`${JSON.stringify(
|
|
192
|
+
{
|
|
193
|
+
ok: true,
|
|
194
|
+
directory: rootDir,
|
|
195
|
+
files: files.map(({ path, action }) => ({ path, action })),
|
|
196
|
+
warnings: result.warnings,
|
|
197
|
+
setupSteps: result.setupSteps,
|
|
198
|
+
installed: installed.ok,
|
|
199
|
+
installError: installed.ok
|
|
200
|
+
? null
|
|
201
|
+
: installed.stderr.trim() ||
|
|
202
|
+
`npm install exited ${installed.exitCode}`,
|
|
203
|
+
mcpConnections: result.mcpConnections.map((connection) => ({
|
|
204
|
+
name: connection.name,
|
|
205
|
+
kind: connection.kind,
|
|
206
|
+
})),
|
|
207
|
+
},
|
|
208
|
+
null,
|
|
209
|
+
2
|
|
210
|
+
)}\n`
|
|
211
|
+
);
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
out(
|
|
216
|
+
renderSummary({
|
|
217
|
+
automationName: payload.automation.name,
|
|
218
|
+
displayDir,
|
|
219
|
+
rootDir,
|
|
220
|
+
cwd: options.cwd ?? process.cwd(),
|
|
221
|
+
files,
|
|
222
|
+
result,
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Converter files first (wx — existing files are kept), then the init
|
|
230
|
+
* scaffold fills in around them with the same skip-existing semantics.
|
|
231
|
+
*/
|
|
232
|
+
async function writeProjectFiles(
|
|
233
|
+
rootDir: string,
|
|
234
|
+
result: ConvertWorkflowResult
|
|
235
|
+
): Promise<InitFileEntry[]> {
|
|
236
|
+
const entries: InitFileEntry[] = [];
|
|
237
|
+
const converterPaths = new Set<string>();
|
|
238
|
+
await mkdir(rootDir, { recursive: true });
|
|
239
|
+
for (const file of result.files) {
|
|
240
|
+
converterPaths.add(file.path);
|
|
241
|
+
const abs = join(rootDir, file.path);
|
|
242
|
+
await mkdir(dirname(abs), { recursive: true });
|
|
243
|
+
try {
|
|
244
|
+
await writeFile(abs, file.contents, { flag: "wx" });
|
|
245
|
+
entries.push({ path: file.path, action: "create" });
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (!isEexist(error)) {
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
entries.push({ path: file.path, action: "exist" });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const init = await initProject({ dir: rootDir });
|
|
255
|
+
|
|
256
|
+
// Scaffold files that make sense in a fresh `init` but not in an exported
|
|
257
|
+
// automation: the default memory hook (only when the automation had memory
|
|
258
|
+
// enabled — the converter emits its own copy then) and the echo demo tool.
|
|
259
|
+
const dropPaths = new Set<string>();
|
|
260
|
+
for (const scaffoldPath of ["agent/hooks/memory.ts", "agent/tools/echo.ts"]) {
|
|
261
|
+
if (
|
|
262
|
+
!converterPaths.has(scaffoldPath) &&
|
|
263
|
+
init.created.includes(scaffoldPath)
|
|
264
|
+
) {
|
|
265
|
+
dropPaths.add(scaffoldPath);
|
|
266
|
+
await rm(join(rootDir, scaffoldPath));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
for (const entry of init.files) {
|
|
270
|
+
if (converterPaths.has(entry.path) || dropPaths.has(entry.path)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
entries.push(entry);
|
|
274
|
+
}
|
|
275
|
+
return entries;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function renderSummary(args: {
|
|
279
|
+
automationName: string;
|
|
280
|
+
displayDir: string;
|
|
281
|
+
rootDir: string;
|
|
282
|
+
cwd: string;
|
|
283
|
+
files: InitFileEntry[];
|
|
284
|
+
result: ConvertWorkflowResult;
|
|
285
|
+
}): string {
|
|
286
|
+
const c = stdoutPalette();
|
|
287
|
+
const lines: string[] = [];
|
|
288
|
+
lines.push("");
|
|
289
|
+
lines.push(`Converted "${args.automationName}" → ${args.displayDir}`);
|
|
290
|
+
lines.push("");
|
|
291
|
+
let anyExisting = false;
|
|
292
|
+
for (const file of args.files) {
|
|
293
|
+
if (file.path.endsWith(".gitkeep")) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
anyExisting ||= file.action === "exist";
|
|
297
|
+
lines.push(c.dim(`${file.action.padStart(12)} ${file.path}`));
|
|
298
|
+
}
|
|
299
|
+
if (anyExisting) {
|
|
300
|
+
lines.push(c.dim(`\n "exist" files already existed and were kept as-is.`));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const warnings = args.result.warnings;
|
|
304
|
+
if (warnings.length > 0) {
|
|
305
|
+
lines.push("");
|
|
306
|
+
lines.push("Warnings:");
|
|
307
|
+
for (const warning of warnings) {
|
|
308
|
+
lines.push(` - ${warning}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (args.result.setupSteps.length > 0) {
|
|
313
|
+
lines.push("");
|
|
314
|
+
lines.push("Setup steps:");
|
|
315
|
+
for (const [index, step] of args.result.setupSteps.entries()) {
|
|
316
|
+
lines.push(` ${index + 1}. ${step}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (args.result.mcpConnections.length > 0) {
|
|
321
|
+
lines.push("");
|
|
322
|
+
lines.push("MCP connections:");
|
|
323
|
+
for (const connection of args.result.mcpConnections) {
|
|
324
|
+
lines.push(` - ${connection.name}: ${connectionStatus(connection)}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
lines.push("");
|
|
329
|
+
lines.push("Next steps:");
|
|
330
|
+
const cd = formatCdCommand(args.cwd, args.rootDir);
|
|
331
|
+
const steps = [
|
|
332
|
+
...(cd === undefined ? [] : [cd]),
|
|
333
|
+
`${CLI} validate`,
|
|
334
|
+
`${CLI} dev`,
|
|
335
|
+
];
|
|
336
|
+
for (const [index, step] of steps.entries()) {
|
|
337
|
+
lines.push(` ${index + 1}. ${step}`);
|
|
338
|
+
}
|
|
339
|
+
lines.push("");
|
|
340
|
+
return `${lines.join("\n")}\n`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function connectionStatus(connection: ConvertedMcpConnection): string {
|
|
344
|
+
if (connection.kind === "cursorAccount") {
|
|
345
|
+
return "resolved from your Cursor account at runtime — manage authorization in Cursor (dashboard → MCP)";
|
|
346
|
+
}
|
|
347
|
+
return "needs manual setup — see the stub file and warnings above";
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function describe(error: unknown): string {
|
|
351
|
+
return error instanceof Error ? error.message : String(error);
|
|
352
|
+
}
|
|
@@ -1345,7 +1345,7 @@ function promptHidden(
|
|
|
1345
1345
|
}
|
|
1346
1346
|
|
|
1347
1347
|
/** Buffered line reader over a piped stream; undefined once it ends. */
|
|
1348
|
-
function createLineReader(
|
|
1348
|
+
export function createLineReader(
|
|
1349
1349
|
stream: NodeJS.ReadableStream
|
|
1350
1350
|
): () => Promise<string | undefined> {
|
|
1351
1351
|
let buffer = "";
|
|
@@ -96,30 +96,77 @@ export async function cmdMcpOauth(
|
|
|
96
96
|
return 1;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const flow = await authorizeHostMcpOauth({
|
|
100
|
+
connection: options.connection,
|
|
101
|
+
url: transport.url,
|
|
102
|
+
type: transport.type === "sse" ? "sse" : "http",
|
|
103
|
+
headers: transport.headers,
|
|
104
|
+
out,
|
|
105
|
+
err,
|
|
106
|
+
});
|
|
107
|
+
if (!flow.ok) {
|
|
108
|
+
return 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (options.store !== true) {
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
return storeOauthSecrets({
|
|
115
|
+
connection: options.connection,
|
|
116
|
+
dir: options.dir,
|
|
117
|
+
slug: options.slug,
|
|
118
|
+
team: options.team,
|
|
119
|
+
apiKey: options.apiKey,
|
|
120
|
+
tokens: flow.tokens,
|
|
121
|
+
clientId: flow.clientId,
|
|
122
|
+
err,
|
|
123
|
+
out,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface HostMcpOauthArgs {
|
|
128
|
+
/** Connection name — the local token-store identifier. */
|
|
129
|
+
connection: string;
|
|
130
|
+
url: string;
|
|
131
|
+
type?: "http" | "sse";
|
|
132
|
+
headers?: Record<string, string>;
|
|
133
|
+
out: (text: string) => void;
|
|
134
|
+
err: (text: string) => void;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export type HostMcpOauthResult =
|
|
138
|
+
| { ok: true; tokens: OAuthTokens | undefined; clientId: string | undefined }
|
|
139
|
+
| { ok: false };
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The interactive host-OAuth browser flow against an MCP server URL. Takes
|
|
143
|
+
* the transport metadata directly so callers that already hold it never have
|
|
144
|
+
* to load the authored project to authorize.
|
|
145
|
+
*/
|
|
146
|
+
export async function authorizeHostMcpOauth(
|
|
147
|
+
args: HostMcpOauthArgs
|
|
148
|
+
): Promise<HostMcpOauthResult> {
|
|
149
|
+
const { connection, out, err } = args;
|
|
150
|
+
out(`${LOG_PREFIX} Authorizing "${connection}" (${args.url})…\n`);
|
|
102
151
|
|
|
103
152
|
let authorizationUrl: URL | undefined;
|
|
104
153
|
const provider = await createHostMcpOAuthProvider({
|
|
105
|
-
identifier:
|
|
106
|
-
resourceUrl:
|
|
154
|
+
identifier: connection,
|
|
155
|
+
resourceUrl: args.url,
|
|
107
156
|
onRedirect: (url) => {
|
|
108
157
|
authorizationUrl = url;
|
|
109
158
|
},
|
|
110
159
|
});
|
|
111
160
|
|
|
112
161
|
const client = new Client({ name: "agent-sdk-mcp-oauth", version: "0.1.0" });
|
|
113
|
-
const url = new URL(
|
|
162
|
+
const url = new URL(args.url);
|
|
114
163
|
const requestInit =
|
|
115
|
-
|
|
116
|
-
? undefined
|
|
117
|
-
: { headers: transport.headers };
|
|
164
|
+
args.headers === undefined ? undefined : { headers: args.headers };
|
|
118
165
|
|
|
119
166
|
const makeTransport = ():
|
|
120
167
|
| StreamableHTTPClientTransport
|
|
121
168
|
| SSEClientTransport => {
|
|
122
|
-
if (
|
|
169
|
+
if (args.type === "sse") {
|
|
123
170
|
return new SSEClientTransport(url, {
|
|
124
171
|
authProvider: provider,
|
|
125
172
|
requestInit,
|
|
@@ -135,15 +182,15 @@ export async function cmdMcpOauth(
|
|
|
135
182
|
let authorized = false;
|
|
136
183
|
try {
|
|
137
184
|
await client.connect(mcpTransport);
|
|
138
|
-
out(`${LOG_PREFIX} "${
|
|
185
|
+
out(`${LOG_PREFIX} "${connection}" already authorized.\n`);
|
|
139
186
|
await client.close().catch(() => undefined);
|
|
140
187
|
authorized = true;
|
|
141
188
|
} catch (error) {
|
|
142
189
|
if (!(error instanceof UnauthorizedError)) {
|
|
143
190
|
err(
|
|
144
|
-
`${LOG_PREFIX} Failed to connect to "${
|
|
191
|
+
`${LOG_PREFIX} Failed to connect to "${connection}": ${describe(error)}\n`
|
|
145
192
|
);
|
|
146
|
-
return
|
|
193
|
+
return { ok: false };
|
|
147
194
|
}
|
|
148
195
|
}
|
|
149
196
|
|
|
@@ -153,7 +200,7 @@ export async function cmdMcpOauth(
|
|
|
153
200
|
err(
|
|
154
201
|
`${LOG_PREFIX} Auth required but no authorization URL was provided.\n`
|
|
155
202
|
);
|
|
156
|
-
return
|
|
203
|
+
return { ok: false };
|
|
157
204
|
}
|
|
158
205
|
|
|
159
206
|
out(`${LOG_PREFIX} Open if needed:\n ${redirect.toString()}\n`);
|
|
@@ -165,14 +212,14 @@ export async function cmdMcpOauth(
|
|
|
165
212
|
callback = await waitForMcpOAuthCallback();
|
|
166
213
|
} catch (error) {
|
|
167
214
|
err(`${LOG_PREFIX} OAuth callback failed: ${describe(error)}\n`);
|
|
168
|
-
return
|
|
215
|
+
return { ok: false };
|
|
169
216
|
}
|
|
170
217
|
|
|
171
218
|
try {
|
|
172
219
|
await mcpTransport.finishAuth(callback.code);
|
|
173
220
|
} catch (error) {
|
|
174
221
|
err(`${LOG_PREFIX} Token exchange failed: ${describe(error)}\n`);
|
|
175
|
-
return
|
|
222
|
+
return { ok: false };
|
|
176
223
|
}
|
|
177
224
|
|
|
178
225
|
mcpTransport = makeTransport();
|
|
@@ -180,31 +227,22 @@ export async function cmdMcpOauth(
|
|
|
180
227
|
await client.connect(mcpTransport);
|
|
181
228
|
const tools = await client.listTools();
|
|
182
229
|
out(
|
|
183
|
-
`${LOG_PREFIX} Authorized "${
|
|
230
|
+
`${LOG_PREFIX} Authorized "${connection}" (${tools.tools.length} tools). Local tokens in the agent-serve config dir.\n`
|
|
184
231
|
);
|
|
185
232
|
await client.close().catch(() => undefined);
|
|
186
233
|
} catch (error) {
|
|
187
234
|
err(
|
|
188
|
-
`${LOG_PREFIX} Reconnect failed: ${describe(error)}. Re-run \`${CLI} mcp oauth ${
|
|
235
|
+
`${LOG_PREFIX} Reconnect failed: ${describe(error)}. Re-run \`${CLI} mcp oauth ${connection}\`.\n`
|
|
189
236
|
);
|
|
190
|
-
return
|
|
237
|
+
return { ok: false };
|
|
191
238
|
}
|
|
192
239
|
}
|
|
193
240
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
return storeOauthSecrets({
|
|
198
|
-
connection: options.connection,
|
|
199
|
-
dir: options.dir,
|
|
200
|
-
slug: options.slug,
|
|
201
|
-
team: options.team,
|
|
202
|
-
apiKey: options.apiKey,
|
|
241
|
+
return {
|
|
242
|
+
ok: true,
|
|
203
243
|
tokens: provider.tokens(),
|
|
204
244
|
clientId: provider.clientInformation()?.client_id,
|
|
205
|
-
|
|
206
|
-
out,
|
|
207
|
-
});
|
|
245
|
+
};
|
|
208
246
|
}
|
|
209
247
|
|
|
210
248
|
/**
|