@impractical-ai/motion 0.1.4 → 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.
package/README.md CHANGED
@@ -1,22 +1,34 @@
1
1
  # Impractical Motion CLI
2
2
 
3
- Connect Claude Code to Impractical Motion through its MCP server.
3
+ Connect Claude Code, Codex, Cursor, or OpenCode to Impractical Motion through
4
+ its MCP server.
4
5
 
5
6
  ## Requirements
6
7
 
7
8
  - Node.js 22 or newer
8
- - Claude Code installed and signed in
9
+ - At least one supported coding agent: Claude Code, Codex, Cursor, or OpenCode
9
10
  - Google Chrome or Chromium for frame inspection
10
11
 
11
12
  ## Setup
12
13
 
14
+ ```sh
15
+ npx --yes @impractical-ai/motion setup --agent
16
+ ```
17
+
18
+ Approve the device code in the browser, then restart the detected coding agents.
19
+ Setup installs the global `impractical` MCP configuration for every detected
20
+ Claude Code, Codex, Cursor, and OpenCode client, plus the shared skill for Claude
21
+ Code and Codex. Re-running setup verifies and reuses a working credential,
22
+ preserves unrelated client configuration, and does not require another browser
23
+ authorization.
24
+
25
+ For the full Claude Code plugin workflow, run:
26
+
13
27
  ```sh
14
28
  npx --yes @impractical-ai/motion setup --claude
15
29
  ```
16
30
 
17
- Approve the device code in the browser, then run `/reload-plugins` in Claude Code and type
18
- `continue`. Re-running setup verifies and reuses a working credential, refreshes the user-scoped
19
- plugin, and does not require another browser authorization.
31
+ Then run `/reload-plugins` in Claude Code and type `continue`.
20
32
 
21
33
  Setup writes the API credential to `~/.impractical/config.json` with mode `0600`. Environment
22
34
  variables `IMPRACTICAL_API_KEY`, `IMPRACTICAL_SUPABASE_URL`, and `IMPRACTICAL_APP_URL` take
@@ -24,7 +36,7 @@ precedence over that file.
24
36
 
25
37
  ## MCP server
26
38
 
27
- The installed Claude plugin launches the server automatically. Other MCP clients can run:
39
+ The installer configures supported clients to run:
28
40
 
29
41
  ```sh
30
42
  npx --yes @impractical-ai/motion@latest mcp
@@ -13,13 +13,15 @@ source code lives in the cloud, never in the user's project directory.
13
13
  ## 0. Setup check (once per session)
14
14
 
15
15
  Call the `account_status` tool (`whoami` is its legacy alias). If the
16
- Impractical tools are not available, the Claude plugin is not connected. Tell
17
- the user to run:
16
+ Impractical tools are not available, the current coding agent is not connected.
17
+ Tell the user to run:
18
18
 
19
- npx --yes @impractical-ai/motion setup --claude
19
+ npx --yes @impractical-ai/motion setup --agent
20
20
 
21
- Then ask them to run `/reload-plugins` and type `continue`. If the weekly quota
22
- is exhausted, call `upgrade` to give the user the authenticated billing page.
21
+ Then ask them to restart or reload their coding agent and type `continue`.
22
+ Claude Code users who specifically want the full plugin can run the same command
23
+ with `--claude` instead. If the weekly quota is exhausted, call `upgrade` to give
24
+ the user the authenticated billing page.
23
25
 
24
26
  ## 1. Read the engine before you write anything
25
27
 
@@ -12,8 +12,8 @@ import {
12
12
  waitForJobs,
13
13
  withAdmissionReceipt,
14
14
  withMeteredCall
15
- } from "./chunk-M77TS5RO.js";
16
- import "./chunk-EFP4P3WA.js";
15
+ } from "./chunk-7724464Z.js";
16
+ import "./chunk-JDN3QHCX.js";
17
17
  export {
18
18
  ApiContractError,
19
19
  ApiError,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  PRODUCTION_APP_URL,
4
4
  PRODUCTION_SUPABASE_URL
5
- } from "./chunk-EFP4P3WA.js";
5
+ } from "./chunk-JDN3QHCX.js";
6
6
 
7
7
  // src/config.ts
8
8
  import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, renameSync } from "node:fs";
@@ -44,7 +44,7 @@ function readStoredConfig() {
44
44
  if (error instanceof SyntaxError) {
45
45
  throw new Error(
46
46
  `Invalid JSON in ${configPath()}: ${error.message}
47
- Run: npx --yes @impractical-ai/motion setup --claude`
47
+ Run: npx --yes @impractical-ai/motion setup --agent`
48
48
  );
49
49
  }
50
50
  throw new Error(
@@ -71,10 +71,10 @@ function saveConfig(patch) {
71
71
  function requireConfig() {
72
72
  const config = loadConfig();
73
73
  if (!config.supabaseUrl) {
74
- throw new Error("Not configured. Run: npx --yes @impractical-ai/motion setup --claude");
74
+ throw new Error("Not configured. Run: npx --yes @impractical-ai/motion setup --agent");
75
75
  }
76
76
  if (!config.apiKey) {
77
- throw new Error("Not connected. Run: npx --yes @impractical-ai/motion setup --claude");
77
+ throw new Error("Not connected. Run: npx --yes @impractical-ai/motion setup --agent");
78
78
  }
79
79
  return config;
80
80
  }
@@ -6,14 +6,14 @@ import {
6
6
  loadConfig,
7
7
  recoverCorruptConfig,
8
8
  saveConfig
9
- } from "./chunk-M77TS5RO.js";
9
+ } from "./chunk-7724464Z.js";
10
10
  import {
11
11
  PACKAGE_COMMAND,
12
12
  PACKAGE_NAME,
13
13
  PACKAGE_VERSION,
14
14
  PRODUCTION_APP_URL,
15
15
  PRODUCTION_SUPABASE_URL
16
- } from "./chunk-EFP4P3WA.js";
16
+ } from "./chunk-JDN3QHCX.js";
17
17
 
18
18
  // src/onboarding.ts
19
19
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -27,6 +27,8 @@ var CLAUDE_MARKETPLACE_NAME = "impractical";
27
27
  var AUTH_VERIFY_MIN_VERSION = "0.1.2";
28
28
  var CLAUDE_INSTALL_COMMAND = "npm install -g @anthropic-ai/claude-code";
29
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"];
30
32
  function parseSemver(value) {
31
33
  const match = value.trim().match(
32
34
  /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
@@ -106,6 +108,123 @@ function marketplaceSourceLabel(record) {
106
108
  function commandResult(command, args) {
107
109
  return spawnSync(command, args, { encoding: "utf8", windowsHide: true });
108
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
+ }
109
228
  function requireCommand(command, label) {
110
229
  const result = commandResult(command, ["--version"]);
111
230
  if (result.error || result.status !== 0) {
@@ -288,7 +407,7 @@ function openBrowser(url) {
288
407
  });
289
408
  return !result.error && result.status === 0;
290
409
  }
291
- async function waitForApproval(supabaseUrl, authorization, log) {
410
+ async function waitForApproval(supabaseUrl, authorization, log, setupFlag) {
292
411
  const deadline = Date.parse(authorization.expiresAt);
293
412
  const intervalMs = Math.max(1, authorization.intervalSeconds) * 1e3;
294
413
  let announced = false;
@@ -304,16 +423,21 @@ async function waitForApproval(supabaseUrl, authorization, log) {
304
423
  }
305
424
  await delay(intervalMs);
306
425
  }
307
- throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup --claude\` again.`);
426
+ throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup ${setupFlag}\` again.`);
308
427
  }
309
428
  async function setup(options) {
310
429
  if (options.installClaudePlugin) verifyClaudePrerequisites();
430
+ if (options.installAgentMcp) {
431
+ verifyAgentPrerequisites();
432
+ verifyAgentConfigs();
433
+ }
311
434
  const corruptConfigBackup = recoverCorruptConfig();
312
435
  if (corruptConfigBackup) {
313
436
  options.log(`Backed up invalid configuration to ${corruptConfigBackup}; starting clean.`);
314
437
  }
315
438
  const supabaseUrl = process.env.IMPRACTICAL_SUPABASE_URL?.trim() || PRODUCTION_SUPABASE_URL;
316
439
  const appUrl = process.env.IMPRACTICAL_APP_URL?.trim() || PRODUCTION_APP_URL;
440
+ const setupFlag = options.installAgentMcp ? "--agent" : "--claude";
317
441
  const existing = loadConfig();
318
442
  let apiKey = existing.supabaseUrl === supabaseUrl ? existing.apiKey : void 0;
319
443
  let verified;
@@ -340,10 +464,10 @@ async function setup(options) {
340
464
  options.log(opened ? "Opened your browser." : "Could not open a browser automatically; use the link above.");
341
465
  }
342
466
  try {
343
- apiKey = await waitForApproval(supabaseUrl, authorization, options.log);
467
+ apiKey = await waitForApproval(supabaseUrl, authorization, options.log, setupFlag);
344
468
  } catch (error) {
345
469
  if (error instanceof ApiError && error.status === 410) {
346
- throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup --claude\` again.`);
470
+ throw new Error(`Authorization expired. Run \`${PACKAGE_COMMAND} setup ${setupFlag}\` again.`);
347
471
  }
348
472
  throw error;
349
473
  }
@@ -351,6 +475,7 @@ async function setup(options) {
351
475
  saveConfig({ apiKey, supabaseUrl, appUrl });
352
476
  }
353
477
  const claudePlugin = options.installClaudePlugin ? installClaudePlugin(options.log) : void 0;
478
+ const configuredMcps = options.installAgentMcp ? installAgentMcps() : [];
354
479
  const installed = options.installAgentSkills ? installSkills() : [];
355
480
  const reconciled = reconcileLegacySkills();
356
481
  options.log("");
@@ -358,14 +483,21 @@ async function setup(options) {
358
483
  for (const skill of installed) {
359
484
  options.log(`Installed /impractical for ${skill.harness}.`);
360
485
  }
486
+ for (const mcp of configuredMcps) {
487
+ options.log(`Configured Impractical MCP for ${mcp.harness}.`);
488
+ }
361
489
  for (const skill of reconciled) {
362
490
  options.log(`Refreshed legacy /impractical for ${skill.harness}; previous copy preserved at ${skill.backupPath}.`);
363
491
  }
364
492
  if (claudePlugin) options.log(pluginResultLine(claudePlugin));
365
493
  options.log(`Workspace: ${verified.orgId}${verified.plan ? ` \xB7 ${verified.plan}` : ""}`);
366
494
  options.log("");
367
- options.log("Run /reload-plugins, then type continue.");
368
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) {
369
501
  for (const line of authenticatedWhoamiGuidance()) options.log(line);
370
502
  }
371
503
  return { installed, appUrl, supabaseUrl };
@@ -32,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
32
32
  // package.json
33
33
  var package_default = {
34
34
  name: "@impractical-ai/motion",
35
- version: "0.1.4",
35
+ version: "0.1.5",
36
36
  private: false,
37
37
  description: "Create and refine Impractical Motion videos from coding agents and the terminal.",
38
38
  homepage: "https://github.com/Openpod/impractical-motion#readme",
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setup
4
- } from "./chunk-KC6BJJXH.js";
4
+ } from "./chunk-DMAQPSA4.js";
5
5
  import {
6
6
  invoke,
7
7
  invokeAt,
@@ -11,8 +11,8 @@ import {
11
11
  saveConfig,
12
12
  studioLink,
13
13
  waitForJobs
14
- } from "./chunk-M77TS5RO.js";
15
- import "./chunk-EFP4P3WA.js";
14
+ } from "./chunk-7724464Z.js";
15
+ import "./chunk-JDN3QHCX.js";
16
16
 
17
17
  // src/commands.ts
18
18
  import { readFileSync as readFileSync2, writeFileSync } from "node:fs";
@@ -1193,12 +1193,11 @@ var flagList = (flags, name) => {
1193
1193
  var flagBool = (flags, name) => flags[name] === true;
1194
1194
  var out = (line) => console.log(line);
1195
1195
  async function setup2(flags) {
1196
- const claude = flagBool(flags, "claude") || flagBool(flags, "agent");
1197
- if (flagBool(flags, "agent")) {
1198
- out("Note: --agent is deprecated; use --claude next time.");
1199
- }
1196
+ const claude = flagBool(flags, "claude");
1197
+ const agent = flagBool(flags, "agent");
1200
1198
  await setup({
1201
- installAgentSkills: false,
1199
+ installAgentMcp: agent,
1200
+ installAgentSkills: agent,
1202
1201
  installClaudePlugin: claude,
1203
1202
  openBrowser: !flagBool(flags, "no-open"),
1204
1203
  log: out
@@ -1214,7 +1213,7 @@ async function login(flags) {
1214
1213
  if (appUrl) patch.appUrl = appUrl;
1215
1214
  const config = { ...loadConfig(), ...patch };
1216
1215
  if (!config.apiKey || !config.supabaseUrl) {
1217
- out("Missing API key. Run: npx --yes @impractical-ai/motion setup --claude");
1216
+ out("Missing API key. Run: npx --yes @impractical-ai/motion setup --agent");
1218
1217
  process.exitCode = 1;
1219
1218
  return;
1220
1219
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PACKAGE_VERSION
4
- } from "./chunk-EFP4P3WA.js";
4
+ } from "./chunk-JDN3QHCX.js";
5
5
 
6
6
  // src/cli-args.ts
7
7
  import { parseArgs } from "node:util";
@@ -46,7 +46,7 @@ var HELP = `impractical \u2014 Impractical Motion from your terminal (and your c
46
46
 
47
47
  Setup
48
48
  setup --claude Sign in and install the Claude plugin + MCP
49
- setup --agent Deprecated alias for setup --claude
49
+ setup --agent Sign in and install MCP for detected coding agents
50
50
  login --key imk_... Manual credential override for development
51
51
  whoami Show org and plan status
52
52
  keys [list|revoke <id>] List or revoke agent API keys
@@ -115,7 +115,7 @@ async function runCli(args, deps) {
115
115
  case "push":
116
116
  throw new Error(
117
117
  `\`impractical ${command}\` has been removed. Draft files are edited through the MCP server now \u2014 no working copy on disk. Connect it with:
118
- npx --yes @impractical-ai/motion setup --claude`
118
+ npx --yes @impractical-ai/motion setup --agent`
119
119
  );
120
120
  case "export":
121
121
  commands.validateMeteredCommand("export", rest, flags);
@@ -139,15 +139,15 @@ async function runCli(args, deps) {
139
139
  // src/index.ts
140
140
  runCli(process.argv.slice(2), {
141
141
  writeLine: (line) => console.log(line),
142
- loadCommands: () => import("./commands-WYBUEYA6.js"),
143
- loadApi: () => import("./api-64X3G4M2.js"),
144
- loadMcp: () => import("./stdio-JCRFEHDW.js")
142
+ loadCommands: () => import("./commands-Y35XPDVH.js"),
143
+ loadApi: () => import("./api-KD6BNKBQ.js"),
144
+ loadMcp: () => import("./stdio-ZFPBM6Z6.js")
145
145
  }).catch(async (error) => {
146
- const { ApiError } = await import("./api-64X3G4M2.js");
146
+ const { ApiError } = await import("./api-KD6BNKBQ.js");
147
147
  if (error instanceof ApiError) {
148
148
  console.error(`Error (${error.status} from ${error.fn}): ${error.message}`);
149
149
  if (error.status === 429) console.error("Weekly MCP allowance reached. Upgrade or wait for Monday UTC.");
150
- if (error.status === 401) console.error("Reconnect with: npx --yes @impractical-ai/motion setup --claude");
150
+ if (error.status === 401) console.error("Reconnect with: npx --yes @impractical-ai/motion setup --agent");
151
151
  } else {
152
152
  console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
153
153
  }