@impractical-ai/motion 0.1.5 → 0.1.7

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.
@@ -6,17 +6,26 @@ import {
6
6
  loadConfig,
7
7
  recoverCorruptConfig,
8
8
  saveConfig
9
- } from "./chunk-7724464Z.js";
9
+ } from "./chunk-MQKW7NFI.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-JDN3QHCX.js";
16
+ } from "./chunk-Z74SLKEU.js";
17
17
 
18
18
  // src/onboarding.ts
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
19
+ import {
20
+ chmodSync,
21
+ existsSync,
22
+ mkdirSync,
23
+ readFileSync,
24
+ renameSync,
25
+ statSync,
26
+ unlinkSync,
27
+ writeFileSync
28
+ } from "node:fs";
20
29
  import { homedir } from "node:os";
21
30
  import { dirname, join } from "node:path";
22
31
  import { fileURLToPath } from "node:url";
@@ -29,6 +38,8 @@ var CLAUDE_INSTALL_COMMAND = "npm install -g @anthropic-ai/claude-code";
29
38
  var CLAUDE_INSTALL_URL = "https://code.claude.com/docs/en/installation";
30
39
  var MCP_SERVER_NAME = "impractical";
31
40
  var MCP_COMMAND = ["npx", "--yes", `${PACKAGE_NAME}@latest`, "mcp"];
41
+ var MCP_TOOL_TIMEOUT_MS = 6e5;
42
+ var MCP_TOOL_TIMEOUT_SECONDS = MCP_TOOL_TIMEOUT_MS / 1e3;
32
43
  function parseSemver(value) {
33
44
  const match = value.trim().match(
34
45
  /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
@@ -136,17 +147,116 @@ function hasExpectedMcp(result) {
136
147
  const detail = commandDetail(result);
137
148
  return detail.includes("@impractical-ai/motion@latest") && /\bmcp\b/.test(detail);
138
149
  }
139
- function installNativeMcp(command, harness) {
150
+ function hasExpectedClaudeMcp(result) {
151
+ if (!hasExpectedMcp(result)) return false;
152
+ return new RegExp(`\\b${MCP_TOOL_TIMEOUT_MS}(?:ms)?\\b`).test(commandDetail(result));
153
+ }
154
+ function hasExpectedCodexMcp(result) {
155
+ if (!hasExpectedMcp(result)) return false;
156
+ return new RegExp(`\\btool_timeout_sec\\s*:\\s*${MCP_TOOL_TIMEOUT_SECONDS}\\b`).test(
157
+ commandDetail(result)
158
+ );
159
+ }
160
+ function codexConfigPath(home) {
161
+ const codexHome = process.env.CODEX_HOME?.trim();
162
+ return join(codexHome || join(home, ".codex"), "config.toml");
163
+ }
164
+ function atomicWrite(path, contents, mode) {
165
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
166
+ const temporary = join(dirname(path), `.${MCP_SERVER_NAME}-${process.pid}-${Date.now()}.tmp`);
167
+ try {
168
+ writeFileSync(temporary, contents, { mode });
169
+ chmodSync(temporary, mode);
170
+ renameSync(temporary, path);
171
+ } finally {
172
+ if (existsSync(temporary)) unlinkSync(temporary);
173
+ }
174
+ }
175
+ function upsertCodexToolTimeout(path) {
176
+ if (!existsSync(path)) throw new Error(`Codex MCP registration did not create ${path}.`);
177
+ const previous = readFileSync(path, "utf8");
178
+ const newline = previous.includes("\r\n") ? "\r\n" : "\n";
179
+ const lines = previous.split(/\r?\n/);
180
+ const section = /^\s*\[mcp_servers\.(?:impractical|"impractical")\]\s*(?:#.*)?$/;
181
+ const sectionStart = lines.findIndex((line) => section.test(line));
182
+ if (sectionStart === -1) {
183
+ throw new Error(`Codex MCP registration did not create [mcp_servers.${MCP_SERVER_NAME}].`);
184
+ }
185
+ let sectionEnd = lines.length;
186
+ for (let index = sectionStart + 1; index < lines.length; index++) {
187
+ if (/^\s*\[/.test(lines[index])) {
188
+ sectionEnd = index;
189
+ break;
190
+ }
191
+ }
192
+ const timeoutLine = /^\s*tool_timeout_sec\s*=/;
193
+ const timeoutIndex = lines.slice(sectionStart + 1, sectionEnd).findIndex((line) => timeoutLine.test(line));
194
+ if (timeoutIndex !== -1 && new RegExp(`^\\s*tool_timeout_sec\\s*=\\s*${MCP_TOOL_TIMEOUT_SECONDS}\\s*(?:#.*)?$`).test(
195
+ lines[sectionStart + 1 + timeoutIndex]
196
+ )) {
197
+ return;
198
+ }
199
+ if (timeoutIndex === -1) {
200
+ lines.splice(sectionEnd, 0, `tool_timeout_sec = ${MCP_TOOL_TIMEOUT_SECONDS}`);
201
+ } else {
202
+ lines[sectionStart + 1 + timeoutIndex] = `tool_timeout_sec = ${MCP_TOOL_TIMEOUT_SECONDS}`;
203
+ }
204
+ atomicWrite(path, lines.join(newline), statSync(path).mode & 511);
205
+ }
206
+ function restoreFile(path, existed, contents, mode) {
207
+ if (existed) atomicWrite(path, contents, mode);
208
+ else if (existsSync(path)) unlinkSync(path);
209
+ }
210
+ function installNativeMcp(command, harness, home) {
140
211
  if (!commandAvailable(command)) return void 0;
141
212
  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);
213
+ if (command === "claude") {
214
+ if (!hasExpectedClaudeMcp(existing)) {
215
+ const existingDetail = commandDetail(existing);
216
+ if (!existing.error && existing.status === 0 && !/No MCP server named\b/i.test(existingDetail)) {
217
+ runMcpCommand(command, ["mcp", "remove", MCP_SERVER_NAME, "-s", "user"], harness);
218
+ }
219
+ const definition = JSON.stringify({
220
+ type: "stdio",
221
+ command: MCP_COMMAND[0],
222
+ args: MCP_COMMAND.slice(1),
223
+ timeout: MCP_TOOL_TIMEOUT_MS
224
+ });
225
+ runMcpCommand(
226
+ command,
227
+ ["mcp", "add-json", "-s", "user", MCP_SERVER_NAME, definition],
228
+ harness
229
+ );
230
+ if (!hasExpectedClaudeMcp(commandResult(command, ["mcp", "get", MCP_SERVER_NAME]))) {
231
+ throw new Error(`${harness} MCP setup failed validation after registration.`);
232
+ }
145
233
  }
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);
234
+ return { harness };
148
235
  }
149
- return { harness };
236
+ const path = codexConfigPath(home);
237
+ mkdirSync(dirname(path), { recursive: true, mode: 448 });
238
+ const existed = existsSync(path);
239
+ const previous = existed ? readFileSync(path, "utf8") : "";
240
+ const mode = existed ? statSync(path).mode & 511 : 384;
241
+ try {
242
+ if (!hasExpectedMcp(existing)) {
243
+ runMcpCommand(command, ["mcp", "add", MCP_SERVER_NAME, "--", ...MCP_COMMAND], harness);
244
+ }
245
+ upsertCodexToolTimeout(path);
246
+ if (!hasExpectedCodexMcp(commandResult(command, ["mcp", "get", MCP_SERVER_NAME]))) {
247
+ throw new Error(`${harness} MCP setup failed validation after registration.`);
248
+ }
249
+ } catch (error) {
250
+ try {
251
+ restoreFile(path, existed, previous, mode);
252
+ } catch (rollbackError) {
253
+ throw new Error(
254
+ `${error instanceof Error ? error.message : String(error)} Rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`
255
+ );
256
+ }
257
+ throw error;
258
+ }
259
+ return { harness, path };
150
260
  }
151
261
  function readJsonObject(path) {
152
262
  if (!existsSync(path)) return {};
@@ -166,6 +276,13 @@ function writeJsonObject(path, value) {
166
276
  writeFileSync(path, `${JSON.stringify(value, null, 2)}
167
277
  `, { mode: 384 });
168
278
  }
279
+ function hasRequiredFields(value, required) {
280
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
281
+ const record = value;
282
+ return Object.entries(required).every(
283
+ ([key, expected]) => JSON.stringify(record[key]) === JSON.stringify(expected)
284
+ );
285
+ }
169
286
  function installCursorMcp(home) {
170
287
  if (!commandAvailable("cursor-agent") && !commandAvailable("cursor")) return void 0;
171
288
  const path = join(home, ".cursor", "mcp.json");
@@ -175,12 +292,13 @@ function installCursorMcp(home) {
175
292
  command: MCP_COMMAND[0],
176
293
  args: MCP_COMMAND.slice(1)
177
294
  };
178
- if (JSON.stringify(servers[MCP_SERVER_NAME]) === JSON.stringify(expected)) {
295
+ if (hasRequiredFields(servers[MCP_SERVER_NAME], expected)) {
179
296
  return { harness: "Cursor", path };
180
297
  }
298
+ const existing = servers[MCP_SERVER_NAME] && typeof servers[MCP_SERVER_NAME] === "object" && !Array.isArray(servers[MCP_SERVER_NAME]) ? servers[MCP_SERVER_NAME] : {};
181
299
  config.mcpServers = {
182
300
  ...servers,
183
- [MCP_SERVER_NAME]: expected
301
+ [MCP_SERVER_NAME]: { ...existing, ...expected }
184
302
  };
185
303
  writeJsonObject(path, config);
186
304
  return { harness: "Cursor", path };
@@ -191,11 +309,38 @@ function openCodeConfigHome(home) {
191
309
  return join(home, ".config");
192
310
  }
193
311
  function verifyAgentConfigs(home = homedir()) {
312
+ const checks = [];
194
313
  if (commandAvailable("cursor-agent") || commandAvailable("cursor")) {
195
- readJsonObject(join(home, ".cursor", "mcp.json"));
314
+ checks.push({ harness: "Cursor", path: join(home, ".cursor", "mcp.json") });
196
315
  }
197
316
  if (commandAvailable("opencode")) {
198
- readJsonObject(join(openCodeConfigHome(home), "opencode", "opencode.json"));
317
+ checks.push({
318
+ harness: "OpenCode",
319
+ path: join(openCodeConfigHome(home), "opencode", "opencode.json")
320
+ });
321
+ }
322
+ const failures = [];
323
+ for (const check of checks) {
324
+ try {
325
+ readJsonObject(check.path);
326
+ } catch (error) {
327
+ failures.push({
328
+ harness: check.harness,
329
+ path: check.path,
330
+ message: error instanceof Error ? error.message : String(error),
331
+ remediation: agentMcpRemediation(check.harness, check.path)
332
+ });
333
+ }
334
+ }
335
+ if (failures.length > 0) {
336
+ const detail = failures.map(
337
+ (failure) => `${failure.harness}: ${failure.message}
338
+ Remediation: ${failure.remediation}`
339
+ ).join("\n");
340
+ throw new Error(
341
+ `Device authorization was not started because ${failures.length} coding agent configuration${failures.length === 1 ? "" : "s"} must be repaired first.
342
+ ${detail}`
343
+ );
199
344
  }
200
345
  }
201
346
  function installOpenCodeMcp(home) {
@@ -205,25 +350,68 @@ function installOpenCodeMcp(home) {
205
350
  const servers = config.mcp && typeof config.mcp === "object" && !Array.isArray(config.mcp) ? config.mcp : {};
206
351
  const expected = {
207
352
  type: "local",
208
- command: MCP_COMMAND
353
+ command: MCP_COMMAND,
354
+ timeout: MCP_TOOL_TIMEOUT_MS
209
355
  };
210
- if (JSON.stringify(servers[MCP_SERVER_NAME]) === JSON.stringify(expected)) {
356
+ if (hasRequiredFields(servers[MCP_SERVER_NAME], expected)) {
211
357
  return { harness: "OpenCode", path };
212
358
  }
359
+ const existing = servers[MCP_SERVER_NAME] && typeof servers[MCP_SERVER_NAME] === "object" && !Array.isArray(servers[MCP_SERVER_NAME]) ? servers[MCP_SERVER_NAME] : {};
213
360
  config.mcp = {
214
361
  ...servers,
215
- [MCP_SERVER_NAME]: expected
362
+ [MCP_SERVER_NAME]: { ...existing, ...expected }
216
363
  };
217
364
  writeJsonObject(path, config);
218
365
  return { harness: "OpenCode", path };
219
366
  }
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));
367
+ function agentMcpRemediation(harness, path) {
368
+ if (path) {
369
+ return `Repair ${path} so it contains a valid configuration object without deleting unrelated entries, then rerun \`${PACKAGE_COMMAND} setup --agent\`.`;
370
+ }
371
+ return `Inspect the ${harness} MCP configuration for \`${MCP_SERVER_NAME}\`, then rerun \`${PACKAGE_COMMAND} setup --agent\`.`;
372
+ }
373
+ function agentMcpFailureMessage(failures) {
374
+ const detail = failures.map((failure) => `${failure.harness}: ${failure.message}
375
+ Remediation: ${failure.remediation}`).join("\n");
376
+ return `Could not configure ${failures.length} coding agent${failures.length === 1 ? "" : "s"}. Successful agent configurations were preserved.
377
+ ${detail}`;
378
+ }
379
+ function configureAgentMcps(home = homedir()) {
380
+ const configured = [];
381
+ const failures = [];
382
+ const installers = [
383
+ { harness: "Claude Code", install: () => installNativeMcp("claude", "Claude Code", home) },
384
+ {
385
+ harness: "Codex",
386
+ path: codexConfigPath(home),
387
+ install: () => installNativeMcp("codex", "Codex", home)
388
+ },
389
+ {
390
+ harness: "Cursor",
391
+ path: join(home, ".cursor", "mcp.json"),
392
+ install: () => installCursorMcp(home)
393
+ },
394
+ {
395
+ harness: "OpenCode",
396
+ path: join(openCodeConfigHome(home), "opencode", "opencode.json"),
397
+ install: () => installOpenCodeMcp(home)
398
+ }
399
+ ];
400
+ for (const installer of installers) {
401
+ try {
402
+ const result = installer.install();
403
+ if (result) configured.push(result);
404
+ } catch (error) {
405
+ const message = error instanceof Error ? error.message : String(error);
406
+ failures.push({
407
+ harness: installer.harness,
408
+ path: installer.path,
409
+ message,
410
+ remediation: agentMcpRemediation(installer.harness, installer.path)
411
+ });
412
+ }
413
+ }
414
+ return { configured, failures };
227
415
  }
228
416
  function requireCommand(command, label) {
229
417
  const result = commandResult(command, ["--version"]);
@@ -429,7 +617,6 @@ async function setup(options) {
429
617
  if (options.installClaudePlugin) verifyClaudePrerequisites();
430
618
  if (options.installAgentMcp) {
431
619
  verifyAgentPrerequisites();
432
- verifyAgentConfigs();
433
620
  }
434
621
  const corruptConfigBackup = recoverCorruptConfig();
435
622
  if (corruptConfigBackup) {
@@ -451,6 +638,7 @@ async function setup(options) {
451
638
  }
452
639
  }
453
640
  if (!apiKey) {
641
+ if (options.installAgentMcp) verifyAgentConfigs();
454
642
  const authorization = await invokeAt(supabaseUrl, "cli-device-auth", {
455
643
  action: "start"
456
644
  });
@@ -475,7 +663,7 @@ async function setup(options) {
475
663
  saveConfig({ apiKey, supabaseUrl, appUrl });
476
664
  }
477
665
  const claudePlugin = options.installClaudePlugin ? installClaudePlugin(options.log) : void 0;
478
- const configuredMcps = options.installAgentMcp ? installAgentMcps() : [];
666
+ const agentMcpSetup = options.installAgentMcp ? configureAgentMcps() : { configured: [], failures: [] };
479
667
  const installed = options.installAgentSkills ? installSkills() : [];
480
668
  const reconciled = reconcileLegacySkills();
481
669
  options.log("");
@@ -483,9 +671,13 @@ async function setup(options) {
483
671
  for (const skill of installed) {
484
672
  options.log(`Installed /impractical for ${skill.harness}.`);
485
673
  }
486
- for (const mcp of configuredMcps) {
674
+ for (const mcp of agentMcpSetup.configured) {
487
675
  options.log(`Configured Impractical MCP for ${mcp.harness}.`);
488
676
  }
677
+ for (const failure of agentMcpSetup.failures) {
678
+ options.log(`Could not configure Impractical MCP for ${failure.harness}: ${failure.message}`);
679
+ options.log(`Remediation for ${failure.harness}: ${failure.remediation}`);
680
+ }
489
681
  for (const skill of reconciled) {
490
682
  options.log(`Refreshed legacy /impractical for ${skill.harness}; previous copy preserved at ${skill.backupPath}.`);
491
683
  }
@@ -500,6 +692,9 @@ async function setup(options) {
500
692
  if (options.installClaudePlugin || options.installAgentMcp) {
501
693
  for (const line of authenticatedWhoamiGuidance()) options.log(line);
502
694
  }
695
+ if (agentMcpSetup.failures.length > 0) {
696
+ throw new Error(agentMcpFailureMessage(agentMcpSetup.failures));
697
+ }
503
698
  return { installed, appUrl, supabaseUrl };
504
699
  }
505
700
 
@@ -2,10 +2,19 @@
2
2
  import {
3
3
  PRODUCTION_APP_URL,
4
4
  PRODUCTION_SUPABASE_URL
5
- } from "./chunk-JDN3QHCX.js";
5
+ } from "./chunk-Z74SLKEU.js";
6
6
 
7
7
  // src/config.ts
8
- import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync, renameSync } from "node:fs";
8
+ import {
9
+ chmodSync,
10
+ existsSync,
11
+ mkdirSync,
12
+ readFileSync,
13
+ renameSync,
14
+ unlinkSync,
15
+ writeFileSync
16
+ } from "node:fs";
17
+ import { randomBytes } from "node:crypto";
9
18
  import { homedir } from "node:os";
10
19
  import { join } from "node:path";
11
20
  var configDir = () => join(homedir(), ".impractical");
@@ -63,9 +72,25 @@ function loadConfig() {
63
72
  function saveConfig(patch) {
64
73
  const stored = readStoredConfig();
65
74
  const next = { ...stored, ...patch };
66
- mkdirSync(configDir(), { recursive: true });
67
- writeFileSync(configPath(), JSON.stringify(next, null, 2) + "\n");
68
- chmodSync(configPath(), 384);
75
+ const directory = configDir();
76
+ const path = configPath();
77
+ mkdirSync(directory, { recursive: true, mode: 448 });
78
+ chmodSync(directory, 448);
79
+ const temporary = join(
80
+ directory,
81
+ `.config-${process.pid}-${randomBytes(8).toString("hex")}.tmp`
82
+ );
83
+ try {
84
+ writeFileSync(temporary, JSON.stringify(next, null, 2) + "\n", {
85
+ encoding: "utf8",
86
+ flag: "wx",
87
+ mode: 384
88
+ });
89
+ chmodSync(temporary, 384);
90
+ renameSync(temporary, path);
91
+ } finally {
92
+ if (existsSync(temporary)) unlinkSync(temporary);
93
+ }
69
94
  return next;
70
95
  }
71
96
  function requireConfig() {
@@ -97,6 +122,16 @@ var ApiContractError = class extends Error {
97
122
  this.fn = fn;
98
123
  }
99
124
  };
125
+ var ApiTransportError = class extends Error {
126
+ constructor(fn, endpoint, reason) {
127
+ super(
128
+ `Could not reach ${endpoint} for ${fn}. Cause: ${reason}. Check your network and IMPRACTICAL_SUPABASE_URL, then retry. If this persists, run \`npx --yes @impractical-ai/motion setup --agent\`.`
129
+ );
130
+ this.fn = fn;
131
+ this.endpoint = endpoint;
132
+ this.reason = reason;
133
+ }
134
+ };
100
135
  function isRecord(value) {
101
136
  return typeof value === "object" && value !== null && !Array.isArray(value);
102
137
  }
@@ -156,15 +191,30 @@ async function invoke(name, body = {}) {
156
191
  return invokeAt(supabaseUrl, name, body, apiKey, receipt);
157
192
  }
158
193
  async function invokeAt(supabaseUrl, name, body = {}, apiKey, admissionReceipt) {
159
- const response = await fetch(`${supabaseUrl.replace(/\/$/, "")}/functions/v1/${name}`, {
160
- method: "POST",
161
- headers: {
162
- ...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
163
- ...admissionReceipt ? { "x-impractical-admission": admissionReceipt } : {},
164
- "content-type": "application/json"
165
- },
166
- body: JSON.stringify(body)
167
- });
194
+ const requestUrl = `${supabaseUrl.replace(/\/$/, "")}/functions/v1/${name}`;
195
+ let response;
196
+ try {
197
+ response = await fetch(requestUrl, {
198
+ method: "POST",
199
+ headers: {
200
+ ...apiKey ? { authorization: `Bearer ${apiKey}` } : {},
201
+ ...admissionReceipt ? { "x-impractical-admission": admissionReceipt } : {},
202
+ "content-type": "application/json"
203
+ },
204
+ body: JSON.stringify(body)
205
+ });
206
+ } catch (error) {
207
+ let endpoint = `configured API/functions/v1/${encodeURIComponent(name)}`;
208
+ try {
209
+ const configured = new URL(supabaseUrl);
210
+ endpoint = `${configured.protocol}//${configured.host}/functions/v1/${encodeURIComponent(name)}`;
211
+ } catch {
212
+ }
213
+ const cause = error instanceof Error ? error.cause : void 0;
214
+ const code = isRecord(cause) && typeof cause.code === "string" ? cause.code : void 0;
215
+ const reason = code && /^[A-Z0-9_]+$/.test(code) ? code : "network transport error";
216
+ throw new ApiTransportError(name, endpoint, reason);
217
+ }
168
218
  const text = await response.text();
169
219
  let data = null;
170
220
  try {
@@ -223,6 +273,7 @@ export {
223
273
  requireConfig,
224
274
  ApiError,
225
275
  ApiContractError,
276
+ ApiTransportError,
226
277
  validateApiResponse,
227
278
  invoke,
228
279
  invokeAt,
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+
3
+ // package.json
4
+ var package_default = {
5
+ name: "@impractical-ai/motion",
6
+ version: "0.1.7",
7
+ private: false,
8
+ description: "Create and refine Impractical Motion videos from coding agents and the terminal.",
9
+ homepage: "https://github.com/Openpod/impractical-motion#readme",
10
+ repository: {
11
+ type: "git",
12
+ url: "git+https://github.com/Openpod/impractical-motion.git"
13
+ },
14
+ bugs: {
15
+ url: "https://github.com/Openpod/impractical-motion/issues"
16
+ },
17
+ license: "UNLICENSED",
18
+ type: "module",
19
+ files: [
20
+ "dist",
21
+ "assets",
22
+ "README.md"
23
+ ],
24
+ bin: {
25
+ impractical: "dist/index.js"
26
+ },
27
+ engines: {
28
+ node: ">=22"
29
+ },
30
+ scripts: {
31
+ build: 'node ../scripts/verify-locked-cli-install.mjs && node ../scripts/build-engine-dts.mjs --cli-only && rm -rf dist && ./node_modules/esbuild/bin/esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --splitting --outdir=dist --external:playwright-core --banner:js="#!/usr/bin/env node" && chmod +x dist/index.js',
32
+ typecheck: "tsc --noEmit",
33
+ test: "npm run typecheck && npm run build && esbuild tests/onboarding.test.ts tests/mcp-tools.test.ts tests/mcp-frames.test.ts tests/mcp-motion.test.ts tests/mcp-motion-score.test.ts tests/mcp-install-font.test.ts tests/mcp-metric-catalog.test.ts tests/mcp-text-census.test.ts tests/mcp-phased-flow.test.ts tests/mcp-preflight.test.ts tests/engine-dts.test.ts tests/mcp-connect.test.ts tests/mcp-longpoll.test.ts tests/mcp-agent-timeout.test.ts tests/api-hardening.test.ts --bundle --platform=node --format=esm --packages=external --outdir=dist --out-extension:.js=.mjs && node --test dist/onboarding.test.mjs dist/mcp-tools.test.mjs dist/mcp-frames.test.mjs dist/mcp-motion.test.mjs dist/mcp-motion-score.test.mjs dist/mcp-install-font.test.mjs dist/mcp-metric-catalog.test.mjs dist/mcp-text-census.test.mjs dist/mcp-phased-flow.test.mjs dist/mcp-preflight.test.mjs dist/engine-dts.test.mjs dist/mcp-connect.test.mjs dist/mcp-longpoll.test.mjs dist/mcp-agent-timeout.test.mjs dist/api-hardening.test.mjs",
34
+ prepack: "npm run build"
35
+ },
36
+ publishConfig: {
37
+ access: "public"
38
+ },
39
+ devDependencies: {
40
+ "@types/node": "^26.1.0",
41
+ esbuild: "^0.25.0",
42
+ typescript: "^5.6.3"
43
+ },
44
+ dependencies: {
45
+ "@modelcontextprotocol/sdk": "^1.30.0",
46
+ "playwright-core": "^1.55.1",
47
+ zod: "^4.4.3"
48
+ }
49
+ };
50
+
51
+ // src/constants.ts
52
+ if (package_default.name !== "@impractical-ai/motion" || typeof package_default.version !== "string" || !package_default.version) {
53
+ throw new Error("The packaged CLI manifest is missing valid release metadata");
54
+ }
55
+ var PRODUCTION_APP_URL = "https://motion.impractical.ai";
56
+ var PRODUCTION_SUPABASE_URL = "https://uaxjweednprtbhbceify.supabase.co";
57
+ var PACKAGE_NAME = "@impractical-ai/motion";
58
+ var PACKAGE_VERSION = package_default.version;
59
+ var PACKAGE_COMMAND = `npx --yes ${PACKAGE_NAME}`;
60
+
61
+ export {
62
+ PRODUCTION_APP_URL,
63
+ PRODUCTION_SUPABASE_URL,
64
+ PACKAGE_NAME,
65
+ PACKAGE_VERSION,
66
+ PACKAGE_COMMAND
67
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setup
4
- } from "./chunk-DMAQPSA4.js";
4
+ } from "./chunk-M7SHFM25.js";
5
5
  import {
6
6
  invoke,
7
7
  invokeAt,
@@ -11,8 +11,9 @@ import {
11
11
  saveConfig,
12
12
  studioLink,
13
13
  waitForJobs
14
- } from "./chunk-7724464Z.js";
15
- import "./chunk-JDN3QHCX.js";
14
+ } from "./chunk-MQKW7NFI.js";
15
+ import "./chunk-Z74SLKEU.js";
16
+ import "./chunk-77HVPD4G.js";
16
17
 
17
18
  // src/commands.ts
18
19
  import { readFileSync as readFileSync2, writeFileSync } from "node:fs";
@@ -1142,6 +1143,26 @@ function parseSceneFiles(pasted) {
1142
1143
  return files;
1143
1144
  }
1144
1145
 
1146
+ // ../src/shared/local-project.ts
1147
+ var LOCAL_PROJECT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1148
+ var LOCAL_SCENE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
1149
+ function isLocalProjectId(value) {
1150
+ return typeof value === "string" && LOCAL_PROJECT_ID_RE.test(value);
1151
+ }
1152
+ function localProjectMarker(input) {
1153
+ if (!isLocalProjectId(input.projectId) || !LOCAL_SCENE_NAME_RE.test(input.sceneName)) {
1154
+ throw new Error("A local project requires a valid project and scene identity");
1155
+ }
1156
+ return `${JSON.stringify({
1157
+ projectId: input.projectId,
1158
+ sceneName: input.sceneName,
1159
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1160
+ valueProp: input.valueProp.trim(),
1161
+ ...input.brand ? { brand: input.brand } : {}
1162
+ })}
1163
+ `;
1164
+ }
1165
+
1145
1166
  // ../src/shared/initial-files.ts
1146
1167
  var TITLE_TEMPLATE = TEMPLATES.find((t) => t.id === "title-card");
1147
1168
  var BLANK_DIRECTION = TEMPLATES.find((t) => t.id === "blank").generate({});
@@ -1162,7 +1183,11 @@ function buildInitialFiles(input) {
1162
1183
  }
1163
1184
  })
1164
1185
  };
1165
- const projectMarker = JSON.stringify({
1186
+ const projectMarker = input.localProject ? localProjectMarker({
1187
+ ...input.localProject,
1188
+ valueProp: input.valueProp,
1189
+ brand: input.brand?.slug
1190
+ }) : JSON.stringify({
1166
1191
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1167
1192
  valueProp: input.valueProp.trim(),
1168
1193
  ...input.brand ? { brand: input.brand.slug } : {}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  PACKAGE_VERSION
4
- } from "./chunk-JDN3QHCX.js";
4
+ } from "./chunk-Z74SLKEU.js";
5
+ import "./chunk-77HVPD4G.js";
5
6
 
6
7
  // src/cli-args.ts
7
8
  import { parseArgs } from "node:util";
@@ -139,11 +140,11 @@ async function runCli(args, deps) {
139
140
  // src/index.ts
140
141
  runCli(process.argv.slice(2), {
141
142
  writeLine: (line) => console.log(line),
142
- loadCommands: () => import("./commands-Y35XPDVH.js"),
143
- loadApi: () => import("./api-KD6BNKBQ.js"),
144
- loadMcp: () => import("./stdio-ZFPBM6Z6.js")
143
+ loadCommands: () => import("./commands-R3LJU2BW.js"),
144
+ loadApi: () => import("./api-DR3NJ7TI.js"),
145
+ loadMcp: () => import("./stdio-XUVQKDCB.js")
145
146
  }).catch(async (error) => {
146
- const { ApiError } = await import("./api-KD6BNKBQ.js");
147
+ const { ApiError } = await import("./api-DR3NJ7TI.js");
147
148
  if (error instanceof ApiError) {
148
149
  console.error(`Error (${error.status} from ${error.fn}): ${error.message}`);
149
150
  if (error.status === 429) console.error("Weekly MCP allowance reached. Upgrade or wait for Monday UTC.");