@impractical-ai/motion 0.1.3 → 0.1.5

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.
@@ -1,260 +1,34 @@
1
1
  #!/usr/bin/env node
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __commonJS = (cb, mod) => function __require() {
9
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
- };
11
- var __export = (target, all) => {
12
- for (var name in all)
13
- __defProp(target, name, { get: all[name], enumerable: true });
14
- };
15
- var __copyProps = (to, from, except, desc) => {
16
- if (from && typeof from === "object" || typeof from === "function") {
17
- for (let key of __getOwnPropNames(from))
18
- if (!__hasOwnProp.call(to, key) && key !== except)
19
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
- }
21
- return to;
22
- };
23
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
- // If the importer is in node compatibility mode or this is not an ESM
25
- // file that has been converted to a CommonJS file using a Babel-
26
- // compatible transform (i.e. "__esModule" has not been set), then set
27
- // "default" to the CommonJS "module.exports" for node compatibility.
28
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
- mod
30
- ));
31
-
32
- // src/constants.ts
33
- var PRODUCTION_APP_URL = "https://motion.impractical.ai";
34
- var PRODUCTION_SUPABASE_URL = "https://uaxjweednprtbhbceify.supabase.co";
35
- var PACKAGE_NAME = "@impractical-ai/motion";
36
- var PACKAGE_VERSION = "0.1.2";
37
- var PACKAGE_COMMAND = `npx --yes ${PACKAGE_NAME}`;
38
-
39
- // src/config.ts
40
- import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, renameSync } from "node:fs";
41
- import { homedir } from "node:os";
42
- import { join } from "node:path";
43
- var configDir = () => join(homedir(), ".impractical");
44
- var configPath = () => join(configDir(), "config.json");
45
- function recoverCorruptConfig() {
46
- const path = configPath();
47
- if (!existsSync(path)) return null;
48
- try {
49
- const parsed = JSON.parse(readFileSync(path, "utf8"));
50
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
51
- throw new SyntaxError("the root value must be an object");
52
- }
53
- return null;
54
- } catch (error) {
55
- if (!(error instanceof SyntaxError)) {
56
- throw new Error(
57
- `Could not inspect ${path}: ${error instanceof Error ? error.message : String(error)}`
58
- );
59
- }
60
- let backupPath = `${path}.corrupt-${Date.now()}`;
61
- let suffix = 1;
62
- while (existsSync(backupPath)) backupPath = `${path}.corrupt-${Date.now()}-${suffix++}`;
63
- renameSync(path, backupPath);
64
- return backupPath;
65
- }
66
- }
67
- function readStoredConfig() {
68
- try {
69
- const parsed = JSON.parse(readFileSync(configPath(), "utf8"));
70
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
71
- throw new SyntaxError("the root value must be an object");
72
- }
73
- return parsed;
74
- } catch (error) {
75
- if (error?.code === "ENOENT") return {};
76
- if (error instanceof SyntaxError) {
77
- throw new Error(`Invalid JSON in ${configPath()}: ${error.message}`);
78
- }
79
- throw new Error(
80
- `Could not read ${configPath()}: ${error instanceof Error ? error.message : String(error)}`
81
- );
82
- }
83
- }
84
- function loadConfig() {
85
- const stored = readStoredConfig();
86
- return {
87
- apiKey: process.env.IMPRACTICAL_API_KEY ?? stored.apiKey,
88
- supabaseUrl: process.env.IMPRACTICAL_SUPABASE_URL ?? stored.supabaseUrl ?? PRODUCTION_SUPABASE_URL,
89
- appUrl: process.env.IMPRACTICAL_APP_URL ?? stored.appUrl ?? PRODUCTION_APP_URL
90
- };
91
- }
92
- function saveConfig(patch) {
93
- const stored = readStoredConfig();
94
- const next = { ...stored, ...patch };
95
- mkdirSync(configDir(), { recursive: true });
96
- writeFileSync(configPath(), JSON.stringify(next, null, 2) + "\n");
97
- chmodSync(configPath(), 384);
98
- return next;
99
- }
100
- function requireConfig() {
101
- const config = loadConfig();
102
- if (!config.supabaseUrl) {
103
- throw new Error("Not configured. Run: npx --yes @impractical-ai/motion setup --claude");
104
- }
105
- if (!config.apiKey) {
106
- throw new Error("Not connected. Run: npx --yes @impractical-ai/motion setup --claude");
107
- }
108
- return config;
109
- }
110
-
111
- // src/api.ts
112
- import { AsyncLocalStorage } from "node:async_hooks";
113
- import { randomUUID } from "node:crypto";
114
- var admissionContext = new AsyncLocalStorage();
115
- var ApiError = class extends Error {
116
- constructor(status, message, fn, details) {
117
- super(message);
118
- this.status = status;
119
- this.fn = fn;
120
- this.details = details;
121
- }
122
- };
123
- var ApiContractError = class extends Error {
124
- constructor(fn, expected) {
125
- super(`${fn} returned an invalid response: expected ${expected}. Please retry; if this persists, update Impractical Motion or contact support.`);
126
- this.fn = fn;
127
- }
128
- };
129
- function isRecord(value) {
130
- return typeof value === "object" && value !== null && !Array.isArray(value);
131
- }
132
- function bodyRecord(body) {
133
- return isRecord(body) ? body : {};
134
- }
135
- function validateApiResponse(name, body, data) {
136
- if (!isRecord(data)) throw new ApiContractError(name, "a JSON object");
137
- const input = bodyRecord(body);
138
- if (name === "workspace-bootstrap") {
139
- const billing = data.billing;
140
- if (typeof data.orgId !== "string" || typeof data.plan !== "string" || typeof data.hasAccess !== "boolean" || !isRecord(billing) || typeof billing.status !== "string") {
141
- throw new ApiContractError(
142
- name,
143
- "an orgId, plan, hasAccess flag, and billing status"
144
- );
145
- }
146
- }
147
- if (name === "list-projects") {
148
- const key = input.projectId ? "project" : "projects";
149
- const value = data[key];
150
- if (key === "projects" ? !Array.isArray(value) : !isRecord(value)) {
151
- throw new ApiContractError(name, `an object with a ${key} ${key === "projects" ? "array" : "object"}`);
152
- }
153
- }
154
- if (name === "prepare-generation") {
155
- if (typeof data.projectId !== "string" || !Array.isArray(data.drafts) || data.drafts.length === 0 || !isRecord(data.drafts[0]) || typeof data.drafts[0].id !== "string") {
156
- throw new ApiContractError(
157
- name,
158
- "a projectId and at least one draft with an id"
159
- );
160
- }
161
- }
162
- if (name === "get-draft" && (!isRecord(data.files) || typeof data.draftId !== "string")) {
163
- throw new ApiContractError(name, "draft metadata and a files object");
164
- }
165
- if (name === "save-draft" && typeof data.revision !== "number") {
166
- throw new ApiContractError(name, "a numeric revision");
167
- }
168
- if (name === "job-status" && !Array.isArray(data.jobs)) {
169
- throw new ApiContractError(name, "an object with a jobs array");
170
- }
171
- if (name === "start-export" && typeof data.jobId !== "string") {
172
- throw new ApiContractError(name, "an object with a jobId");
173
- }
174
- if (name === "brand-kits") {
175
- const key = input.action === "list" ? "brands" : input.action === "get" ? "brand" : null;
176
- if (key && (key === "brands" ? !Array.isArray(data[key]) : !isRecord(data[key]))) {
177
- throw new ApiContractError(name, `an object with a ${key} ${key === "brands" ? "array" : "object"}`);
178
- }
179
- }
180
- return data;
181
- }
182
- async function invoke(name, body = {}) {
183
- const { apiKey, supabaseUrl } = requireConfig();
184
- const receipt = admissionContext.getStore();
185
- return invokeAt(supabaseUrl, name, body, apiKey, receipt);
186
- }
187
- async function invokeAt(supabaseUrl, name, body = {}, apiKey, admissionReceipt) {
188
- const response = await fetch(`${supabaseUrl.replace(/\/$/, "")}/functions/v1/${name}`, {
189
- method: "POST",
190
- headers: {
191
- ...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
192
- ...admissionReceipt ? { "x-impractical-admission": admissionReceipt } : {},
193
- "content-type": "application/json"
194
- },
195
- body: JSON.stringify(body)
196
- });
197
- const text = await response.text();
198
- let data = null;
199
- try {
200
- data = text ? JSON.parse(text) : null;
201
- } catch {
202
- }
203
- if (!response.ok) {
204
- const details = isRecord(data) ? data : void 0;
205
- const message = typeof details?.message === "string" && details.message || typeof details?.error === "string" && details.error || text || `${name} failed (${response.status})`;
206
- throw new ApiError(response.status, message, name, details);
207
- }
208
- return validateApiResponse(name, body, data);
209
- }
210
- async function admitMcpCall(toolName, callId = randomUUID()) {
211
- const { apiKey, supabaseUrl } = requireConfig();
212
- return invokeAt(supabaseUrl, "mcp-admission", { callId, toolName }, apiKey);
213
- }
214
- function withAdmissionReceipt(receipt, task) {
215
- return admissionContext.run(receipt, task);
216
- }
217
- async function withMeteredCall(toolName, task) {
218
- const admission = await admitMcpCall(toolName);
219
- return withAdmissionReceipt(admission.receipt, task);
220
- }
221
- var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
222
- async function jobStatus(jobIds) {
223
- const result = await invoke("job-status", { jobIds });
224
- return result.jobs;
225
- }
226
- async function waitForJobs(jobIds, onTick, deadlineMs = 72e5) {
227
- const deadline = Date.now() + deadlineMs;
228
- const terminal = /* @__PURE__ */ new Set();
229
- let latest = [];
230
- while (terminal.size < jobIds.length) {
231
- if (Date.now() >= deadline) throw new Error("Timed out waiting for jobs");
232
- latest = await jobStatus(jobIds);
233
- for (const row of latest) {
234
- if (row.status === "completed" || row.status === "failed" || row.status === "canceled") {
235
- terminal.add(row.id);
236
- }
237
- }
238
- onTick(latest);
239
- if (terminal.size < jobIds.length) await delay(2e3);
240
- }
241
- return latest;
242
- }
243
- function studioLink(appUrl, projectId, slug) {
244
- const base = (appUrl ?? "http://localhost:5173").replace(/\/$/, "");
245
- return `${base}/projects/${projectId}/studio${slug ? `?scene=${slug}` : ""}`;
246
- }
2
+ import {
3
+ ApiError,
4
+ delay,
5
+ invokeAt,
6
+ loadConfig,
7
+ recoverCorruptConfig,
8
+ saveConfig
9
+ } from "./chunk-7724464Z.js";
10
+ import {
11
+ PACKAGE_COMMAND,
12
+ PACKAGE_NAME,
13
+ PACKAGE_VERSION,
14
+ PRODUCTION_APP_URL,
15
+ PRODUCTION_SUPABASE_URL
16
+ } from "./chunk-JDN3QHCX.js";
247
17
 
248
18
  // src/onboarding.ts
249
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
250
- import { homedir as homedir2 } from "node:os";
251
- import { dirname, join as join2 } from "node:path";
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { dirname, join } from "node:path";
252
22
  import { fileURLToPath } from "node:url";
253
23
  import { spawnSync } from "node:child_process";
254
24
  var CLAUDE_MARKETPLACE = "Openpod/impractical-agent-plugins";
255
25
  var CLAUDE_PLUGIN = "impractical-motion@impractical";
256
26
  var CLAUDE_MARKETPLACE_NAME = "impractical";
257
27
  var AUTH_VERIFY_MIN_VERSION = "0.1.2";
28
+ var CLAUDE_INSTALL_COMMAND = "npm install -g @anthropic-ai/claude-code";
29
+ var CLAUDE_INSTALL_URL = "https://code.claude.com/docs/en/installation";
30
+ var MCP_SERVER_NAME = "impractical";
31
+ var MCP_COMMAND = ["npx", "--yes", `${PACKAGE_NAME}@latest`, "mcp"];
258
32
  function parseSemver(value) {
259
33
  const match = value.trim().match(
260
34
  /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
@@ -294,20 +68,20 @@ function compareSemver(left, right) {
294
68
  return 0;
295
69
  }
296
70
  function readJson(path) {
297
- if (!existsSync2(path)) return void 0;
71
+ if (!existsSync(path)) return void 0;
298
72
  try {
299
- return JSON.parse(readFileSync2(path, "utf8"));
73
+ return JSON.parse(readFileSync(path, "utf8"));
300
74
  } catch {
301
75
  return void 0;
302
76
  }
303
77
  }
304
- var claudeConfigDir = () => process.env.CLAUDE_CONFIG_DIR?.trim() || join2(homedir2(), ".claude");
305
- function marketplaceRecord(configDir2) {
306
- const value = readJson(join2(configDir2, "plugins", "known_marketplaces.json"));
78
+ var claudeConfigDir = () => process.env.CLAUDE_CONFIG_DIR?.trim() || join(homedir(), ".claude");
79
+ function marketplaceRecord(configDir) {
80
+ const value = readJson(join(configDir, "plugins", "known_marketplaces.json"));
307
81
  return value?.impractical;
308
82
  }
309
- function installedPluginVersion(configDir2) {
310
- const value = readJson(join2(configDir2, "plugins", "installed_plugins.json"));
83
+ function installedPluginVersion(configDir) {
84
+ const value = readJson(join(configDir, "plugins", "installed_plugins.json"));
311
85
  const entries = value?.plugins?.[CLAUDE_PLUGIN];
312
86
  if (!Array.isArray(entries)) return void 0;
313
87
  const versions = entries.filter((entry) => entry?.scope === "user" && typeof entry.version === "string").map((entry) => entry.version);
@@ -317,10 +91,10 @@ function installedPluginVersion(configDir2) {
317
91
  return comparison !== void 0 && comparison > 0 ? version : highest;
318
92
  }, void 0);
319
93
  }
320
- function marketplacePluginVersion(configDir2) {
321
- const record = marketplaceRecord(configDir2);
322
- const location = typeof record?.installLocation === "string" ? record.installLocation : join2(configDir2, "plugins", "marketplaces", CLAUDE_MARKETPLACE_NAME);
323
- const value = readJson(join2(location, ".claude-plugin", "marketplace.json"));
94
+ function marketplacePluginVersion(configDir) {
95
+ const record = marketplaceRecord(configDir);
96
+ const location = typeof record?.installLocation === "string" ? record.installLocation : join(configDir, "plugins", "marketplaces", CLAUDE_MARKETPLACE_NAME);
97
+ const value = readJson(join(location, ".claude-plugin", "marketplace.json"));
324
98
  return value?.plugins?.find((plugin) => plugin.name === "impractical-motion")?.version;
325
99
  }
326
100
  function expectedMarketplace(record) {
@@ -334,9 +108,133 @@ function marketplaceSourceLabel(record) {
334
108
  function commandResult(command, args) {
335
109
  return spawnSync(command, args, { encoding: "utf8", windowsHide: true });
336
110
  }
111
+ function commandAvailable(command) {
112
+ const result = commandResult(command, ["--version"]);
113
+ return !result.error && result.status === 0;
114
+ }
115
+ function verifyAgentPrerequisites() {
116
+ const found = commandAvailable("claude") || commandAvailable("codex") || commandAvailable("cursor-agent") || commandAvailable("cursor") || commandAvailable("opencode");
117
+ if (!found) {
118
+ throw new Error(
119
+ "No supported coding agent was detected. Install Claude Code, Codex, Cursor, or OpenCode, then rerun setup."
120
+ );
121
+ }
122
+ }
123
+ function commandDetail(result) {
124
+ return `${result.stdout ?? ""}
125
+ ${result.stderr ?? ""}`.trim();
126
+ }
127
+ function runMcpCommand(command, args, harness) {
128
+ const result = commandResult(command, args);
129
+ if (!result.error && result.status === 0) return;
130
+ throw new Error(
131
+ `${harness} MCP setup failed: ${commandDetail(result) || result.error?.message || "unknown error"}`
132
+ );
133
+ }
134
+ function hasExpectedMcp(result) {
135
+ if (result.error || result.status !== 0) return false;
136
+ const detail = commandDetail(result);
137
+ return detail.includes("@impractical-ai/motion@latest") && /\bmcp\b/.test(detail);
138
+ }
139
+ function installNativeMcp(command, harness) {
140
+ if (!commandAvailable(command)) return void 0;
141
+ const existing = commandResult(command, ["mcp", "get", MCP_SERVER_NAME]);
142
+ if (!hasExpectedMcp(existing)) {
143
+ if (command === "claude" && !existing.error && existing.status === 0) {
144
+ runMcpCommand(command, ["mcp", "remove", MCP_SERVER_NAME, "-s", "user"], harness);
145
+ }
146
+ const addArgs = command === "claude" ? ["mcp", "add", "-s", "user", MCP_SERVER_NAME, "--", ...MCP_COMMAND] : ["mcp", "add", MCP_SERVER_NAME, "--", ...MCP_COMMAND];
147
+ runMcpCommand(command, addArgs, harness);
148
+ }
149
+ return { harness };
150
+ }
151
+ function readJsonObject(path) {
152
+ if (!existsSync(path)) return {};
153
+ let value;
154
+ try {
155
+ value = JSON.parse(readFileSync(path, "utf8"));
156
+ } catch {
157
+ throw new Error(`Cannot configure MCP because ${path} is not valid JSON.`);
158
+ }
159
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
160
+ throw new Error(`Cannot configure MCP because ${path} must contain a JSON object.`);
161
+ }
162
+ return value;
163
+ }
164
+ function writeJsonObject(path, value) {
165
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
166
+ writeFileSync(path, `${JSON.stringify(value, null, 2)}
167
+ `, { mode: 384 });
168
+ }
169
+ function installCursorMcp(home) {
170
+ if (!commandAvailable("cursor-agent") && !commandAvailable("cursor")) return void 0;
171
+ const path = join(home, ".cursor", "mcp.json");
172
+ const config = readJsonObject(path);
173
+ const servers = config.mcpServers && typeof config.mcpServers === "object" && !Array.isArray(config.mcpServers) ? config.mcpServers : {};
174
+ const expected = {
175
+ command: MCP_COMMAND[0],
176
+ args: MCP_COMMAND.slice(1)
177
+ };
178
+ if (JSON.stringify(servers[MCP_SERVER_NAME]) === JSON.stringify(expected)) {
179
+ return { harness: "Cursor", path };
180
+ }
181
+ config.mcpServers = {
182
+ ...servers,
183
+ [MCP_SERVER_NAME]: expected
184
+ };
185
+ writeJsonObject(path, config);
186
+ return { harness: "Cursor", path };
187
+ }
188
+ function openCodeConfigHome(home) {
189
+ if (process.env.XDG_CONFIG_HOME?.trim()) return process.env.XDG_CONFIG_HOME.trim();
190
+ if (process.platform === "win32" && process.env.APPDATA?.trim()) return process.env.APPDATA.trim();
191
+ return join(home, ".config");
192
+ }
193
+ function verifyAgentConfigs(home = homedir()) {
194
+ if (commandAvailable("cursor-agent") || commandAvailable("cursor")) {
195
+ readJsonObject(join(home, ".cursor", "mcp.json"));
196
+ }
197
+ if (commandAvailable("opencode")) {
198
+ readJsonObject(join(openCodeConfigHome(home), "opencode", "opencode.json"));
199
+ }
200
+ }
201
+ function installOpenCodeMcp(home) {
202
+ if (!commandAvailable("opencode")) return void 0;
203
+ const path = join(openCodeConfigHome(home), "opencode", "opencode.json");
204
+ const config = readJsonObject(path);
205
+ const servers = config.mcp && typeof config.mcp === "object" && !Array.isArray(config.mcp) ? config.mcp : {};
206
+ const expected = {
207
+ type: "local",
208
+ command: MCP_COMMAND
209
+ };
210
+ if (JSON.stringify(servers[MCP_SERVER_NAME]) === JSON.stringify(expected)) {
211
+ return { harness: "OpenCode", path };
212
+ }
213
+ config.mcp = {
214
+ ...servers,
215
+ [MCP_SERVER_NAME]: expected
216
+ };
217
+ writeJsonObject(path, config);
218
+ return { harness: "OpenCode", path };
219
+ }
220
+ function installAgentMcps(home = homedir()) {
221
+ return [
222
+ installNativeMcp("claude", "Claude Code"),
223
+ installNativeMcp("codex", "Codex"),
224
+ installCursorMcp(home),
225
+ installOpenCodeMcp(home)
226
+ ].filter((result) => Boolean(result));
227
+ }
337
228
  function requireCommand(command, label) {
338
229
  const result = commandResult(command, ["--version"]);
339
230
  if (result.error || result.status !== 0) {
231
+ if (command === "claude") {
232
+ throw new Error(
233
+ `${label} is not installed. Install it, then rerun setup:
234
+ ${CLAUDE_INSTALL_COMMAND}
235
+ Official setup guide: ${CLAUDE_INSTALL_URL}`
236
+ );
237
+ }
340
238
  throw new Error(`${label} is required before setup can continue.`);
341
239
  }
342
240
  }
@@ -345,7 +243,7 @@ function hasChrome() {
345
243
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
346
244
  "/Applications/Chromium.app/Contents/MacOS/Chromium"
347
245
  ];
348
- if (macPaths.some(existsSync2)) return true;
246
+ if (macPaths.some(existsSync)) return true;
349
247
  return ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"].some((command) => {
350
248
  const result = spawnSync(command, ["--version"], { stdio: "ignore", windowsHide: true });
351
249
  return !result.error && result.status === 0;
@@ -381,9 +279,9 @@ ${result.stderr ?? ""}`.trim();
381
279
  }
382
280
  function installClaudePlugin(log = () => {
383
281
  }) {
384
- const configDir2 = claudeConfigDir();
385
- const beforeVersion = installedPluginVersion(configDir2);
386
- let record = marketplaceRecord(configDir2);
282
+ const configDir = claudeConfigDir();
283
+ const beforeVersion = installedPluginVersion(configDir);
284
+ let record = marketplaceRecord(configDir);
387
285
  const preserveSource = Boolean(record && !expectedMarketplace(record));
388
286
  if (preserveSource) {
389
287
  log(
@@ -391,12 +289,12 @@ function installClaudePlugin(log = () => {
391
289
  );
392
290
  } else if (!record) {
393
291
  runClaude(["plugin", "marketplace", "add", CLAUDE_MARKETPLACE], log, true);
394
- record = marketplaceRecord(configDir2);
292
+ record = marketplaceRecord(configDir);
395
293
  }
396
294
  if (!beforeVersion) {
397
295
  runClaude(["plugin", "install", CLAUDE_PLUGIN, "--scope", "user"], log, true);
398
296
  } else if (!preserveSource) {
399
- const advertisedVersion = marketplacePluginVersion(configDir2);
297
+ const advertisedVersion = marketplacePluginVersion(configDir);
400
298
  const comparison = advertisedVersion ? compareSemver(beforeVersion, advertisedVersion) : void 0;
401
299
  if (comparison === void 0) {
402
300
  log(
@@ -408,8 +306,8 @@ function installClaudePlugin(log = () => {
408
306
  );
409
307
  } else {
410
308
  runClaude(["plugin", "marketplace", "update", CLAUDE_MARKETPLACE_NAME], log, true);
411
- const refreshedVersion = marketplacePluginVersion(configDir2);
412
- const currentVersion = installedPluginVersion(configDir2) ?? beforeVersion;
309
+ const refreshedVersion = marketplacePluginVersion(configDir);
310
+ const currentVersion = installedPluginVersion(configDir) ?? beforeVersion;
413
311
  const refreshedComparison = refreshedVersion ? compareSemver(currentVersion, refreshedVersion) : void 0;
414
312
  if (refreshedComparison !== void 0 && refreshedComparison < 0) {
415
313
  runClaude(["plugin", "update", CLAUDE_PLUGIN], log, true);
@@ -420,7 +318,7 @@ function installClaudePlugin(log = () => {
420
318
  }
421
319
  }
422
320
  }
423
- const afterVersion = installedPluginVersion(configDir2);
321
+ const afterVersion = installedPluginVersion(configDir);
424
322
  const change = beforeVersion && afterVersion ? compareSemver(afterVersion, beforeVersion) : void 0;
425
323
  if (change !== void 0 && change < 0) {
426
324
  log(`WARNING: ${CLAUDE_PLUGIN} resolved to ${afterVersion}, below its prior ${beforeVersion}.`);
@@ -454,44 +352,44 @@ function pluginResultLine(result) {
454
352
  return `${CLAUDE_PLUGIN} already at ${result.afterVersion}; left unchanged.`;
455
353
  }
456
354
  var skillSourcePath = () => fileURLToPath(new URL("../assets/impractical/SKILL.md", import.meta.url));
457
- function installSkills(home = homedir2(), sourcePath = skillSourcePath()) {
458
- const source = readFileSync2(sourcePath, "utf8");
355
+ function installSkills(home = homedir(), sourcePath = skillSourcePath()) {
356
+ const source = readFileSync(sourcePath, "utf8");
459
357
  const targets = [
460
358
  {
461
359
  harness: "Claude Code",
462
- path: join2(home, ".claude", "skills", "impractical", "SKILL.md")
360
+ path: join(home, ".claude", "skills", "impractical", "SKILL.md")
463
361
  },
464
362
  {
465
363
  harness: "Codex",
466
- path: join2(home, ".codex", "skills", "impractical", "SKILL.md")
364
+ path: join(home, ".codex", "skills", "impractical", "SKILL.md")
467
365
  }
468
366
  ];
469
367
  for (const target of targets) {
470
- mkdirSync2(dirname(target.path), { recursive: true, mode: 448 });
471
- writeFileSync2(target.path, source, { mode: 420 });
368
+ mkdirSync(dirname(target.path), { recursive: true, mode: 448 });
369
+ writeFileSync(target.path, source, { mode: 420 });
472
370
  }
473
371
  return targets;
474
372
  }
475
- function reconcileLegacySkills(home = homedir2(), sourcePath = skillSourcePath()) {
476
- const source = readFileSync2(sourcePath, "utf8");
373
+ function reconcileLegacySkills(home = homedir(), sourcePath = skillSourcePath()) {
374
+ const source = readFileSync(sourcePath, "utf8");
477
375
  const targets = [
478
376
  {
479
377
  harness: "Claude Code",
480
- path: join2(home, ".claude", "skills", "impractical", "SKILL.md")
378
+ path: join(home, ".claude", "skills", "impractical", "SKILL.md")
481
379
  },
482
380
  {
483
381
  harness: "Codex",
484
- path: join2(home, ".codex", "skills", "impractical", "SKILL.md")
382
+ path: join(home, ".codex", "skills", "impractical", "SKILL.md")
485
383
  }
486
384
  ];
487
385
  const reconciled = [];
488
386
  for (const target of targets) {
489
- if (!existsSync2(target.path)) continue;
490
- const previous = readFileSync2(target.path, "utf8");
387
+ if (!existsSync(target.path)) continue;
388
+ const previous = readFileSync(target.path, "utf8");
491
389
  if (previous === source) continue;
492
390
  const backupPath = `${target.path}.pre-plugin`;
493
- if (!existsSync2(backupPath)) writeFileSync2(backupPath, previous, { mode: 420 });
494
- writeFileSync2(target.path, source, { mode: 420 });
391
+ if (!existsSync(backupPath)) writeFileSync(backupPath, previous, { mode: 420 });
392
+ writeFileSync(target.path, source, { mode: 420 });
495
393
  reconciled.push({ ...target, backupPath });
496
394
  }
497
395
  return reconciled;
@@ -509,7 +407,7 @@ function openBrowser(url) {
509
407
  });
510
408
  return !result.error && result.status === 0;
511
409
  }
512
- async function waitForApproval(supabaseUrl, authorization, log) {
410
+ async function waitForApproval(supabaseUrl, authorization, log, setupFlag) {
513
411
  const deadline = Date.parse(authorization.expiresAt);
514
412
  const intervalMs = Math.max(1, authorization.intervalSeconds) * 1e3;
515
413
  let announced = false;
@@ -525,16 +423,21 @@ async function waitForApproval(supabaseUrl, authorization, log) {
525
423
  }
526
424
  await delay(intervalMs);
527
425
  }
528
- throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup --claude\` again.`);
426
+ throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup ${setupFlag}\` again.`);
529
427
  }
530
428
  async function setup(options) {
531
429
  if (options.installClaudePlugin) verifyClaudePrerequisites();
430
+ if (options.installAgentMcp) {
431
+ verifyAgentPrerequisites();
432
+ verifyAgentConfigs();
433
+ }
532
434
  const corruptConfigBackup = recoverCorruptConfig();
533
435
  if (corruptConfigBackup) {
534
436
  options.log(`Backed up invalid configuration to ${corruptConfigBackup}; starting clean.`);
535
437
  }
536
438
  const supabaseUrl = process.env.IMPRACTICAL_SUPABASE_URL?.trim() || PRODUCTION_SUPABASE_URL;
537
439
  const appUrl = process.env.IMPRACTICAL_APP_URL?.trim() || PRODUCTION_APP_URL;
440
+ const setupFlag = options.installAgentMcp ? "--agent" : "--claude";
538
441
  const existing = loadConfig();
539
442
  let apiKey = existing.supabaseUrl === supabaseUrl ? existing.apiKey : void 0;
540
443
  let verified;
@@ -561,10 +464,10 @@ async function setup(options) {
561
464
  options.log(opened ? "Opened your browser." : "Could not open a browser automatically; use the link above.");
562
465
  }
563
466
  try {
564
- apiKey = await waitForApproval(supabaseUrl, authorization, options.log);
467
+ apiKey = await waitForApproval(supabaseUrl, authorization, options.log, setupFlag);
565
468
  } catch (error) {
566
469
  if (error instanceof ApiError && error.status === 410) {
567
- throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup --claude\` again.`);
470
+ throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup ${setupFlag}\` again.`);
568
471
  }
569
472
  throw error;
570
473
  }
@@ -572,6 +475,7 @@ async function setup(options) {
572
475
  saveConfig({ apiKey, supabaseUrl, appUrl });
573
476
  }
574
477
  const claudePlugin = options.installClaudePlugin ? installClaudePlugin(options.log) : void 0;
478
+ const configuredMcps = options.installAgentMcp ? installAgentMcps() : [];
575
479
  const installed = options.installAgentSkills ? installSkills() : [];
576
480
  const reconciled = reconcileLegacySkills();
577
481
  options.log("");
@@ -579,39 +483,27 @@ async function setup(options) {
579
483
  for (const skill of installed) {
580
484
  options.log(`Installed /impractical for ${skill.harness}.`);
581
485
  }
486
+ for (const mcp of configuredMcps) {
487
+ options.log(`Configured Impractical MCP for ${mcp.harness}.`);
488
+ }
582
489
  for (const skill of reconciled) {
583
490
  options.log(`Refreshed legacy /impractical for ${skill.harness}; previous copy preserved at ${skill.backupPath}.`);
584
491
  }
585
492
  if (claudePlugin) options.log(pluginResultLine(claudePlugin));
586
493
  options.log(`Workspace: ${verified.orgId}${verified.plan ? ` \xB7 ${verified.plan}` : ""}`);
587
494
  options.log("");
588
- options.log("Run /reload-plugins, then type continue.");
589
495
  if (options.installClaudePlugin) {
496
+ options.log("Run /reload-plugins, then type continue.");
497
+ } else if (options.installAgentMcp) {
498
+ options.log("Restart your coding agents, then ask them to use the Impractical MCP.");
499
+ }
500
+ if (options.installClaudePlugin || options.installAgentMcp) {
590
501
  for (const line of authenticatedWhoamiGuidance()) options.log(line);
591
502
  }
592
503
  return { installed, appUrl, supabaseUrl };
593
504
  }
594
505
 
595
506
  export {
596
- __commonJS,
597
- __export,
598
- __toESM,
599
- PRODUCTION_APP_URL,
600
- PRODUCTION_SUPABASE_URL,
601
- PACKAGE_VERSION,
602
- loadConfig,
603
- saveConfig,
604
- requireConfig,
605
- ApiError,
606
- validateApiResponse,
607
- invoke,
608
- invokeAt,
609
- admitMcpCall,
610
- withAdmissionReceipt,
611
- withMeteredCall,
612
- jobStatus,
613
- waitForJobs,
614
- studioLink,
615
507
  openBrowser,
616
508
  setup
617
509
  };