@impractical-ai/motion 0.1.4 → 0.1.6

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.
@@ -2,10 +2,19 @@
2
2
  import {
3
3
  PRODUCTION_APP_URL,
4
4
  PRODUCTION_SUPABASE_URL
5
- } from "./chunk-EFP4P3WA.js";
5
+ } from "./chunk-LJ4YH3YJ.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");
@@ -44,7 +53,7 @@ function readStoredConfig() {
44
53
  if (error instanceof SyntaxError) {
45
54
  throw new Error(
46
55
  `Invalid JSON in ${configPath()}: ${error.message}
47
- Run: npx --yes @impractical-ai/motion setup --claude`
56
+ Run: npx --yes @impractical-ai/motion setup --agent`
48
57
  );
49
58
  }
50
59
  throw new Error(
@@ -63,18 +72,34 @@ 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() {
72
97
  const config = loadConfig();
73
98
  if (!config.supabaseUrl) {
74
- throw new Error("Not configured. Run: npx --yes @impractical-ai/motion setup --claude");
99
+ throw new Error("Not configured. Run: npx --yes @impractical-ai/motion setup --agent");
75
100
  }
76
101
  if (!config.apiKey) {
77
- throw new Error("Not connected. Run: npx --yes @impractical-ai/motion setup --claude");
102
+ throw new Error("Not connected. Run: npx --yes @impractical-ai/motion setup --agent");
78
103
  }
79
104
  return config;
80
105
  }
@@ -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,
@@ -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.6",
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",
@@ -57,15 +57,16 @@ var package_default = {
57
57
  node: ">=22"
58
58
  },
59
59
  scripts: {
60
- build: 'node ../scripts/build-engine-dts.mjs && rm -rf dist && 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',
60
+ 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',
61
61
  typecheck: "tsc --noEmit",
62
- test: "npm run build && esbuild tests/onboarding.test.ts tests/mcp-tools.test.ts tests/mcp-frames.test.ts tests/engine-dts.test.ts tests/mcp-connect.test.ts tests/mcp-longpoll.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/engine-dts.test.mjs dist/mcp-connect.test.mjs dist/mcp-longpoll.test.mjs",
62
+ test: "npm run typecheck && npm run build && esbuild tests/onboarding.test.ts tests/mcp-tools.test.ts tests/mcp-frames.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/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",
63
63
  prepack: "npm run build"
64
64
  },
65
65
  publishConfig: {
66
66
  access: "public"
67
67
  },
68
68
  devDependencies: {
69
+ "@types/node": "^26.1.0",
69
70
  esbuild: "^0.25.0",
70
71
  typescript: "^5.6.3"
71
72
  },
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  setup
4
- } from "./chunk-KC6BJJXH.js";
4
+ } from "./chunk-BUHNO2W2.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-KITRCE33.js";
15
+ import "./chunk-LJ4YH3YJ.js";
16
16
 
17
17
  // src/commands.ts
18
18
  import { readFileSync as readFileSync2, writeFileSync } from "node:fs";
@@ -1142,6 +1142,26 @@ function parseSceneFiles(pasted) {
1142
1142
  return files;
1143
1143
  }
1144
1144
 
1145
+ // ../src/shared/local-project.ts
1146
+ 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;
1147
+ var LOCAL_SCENE_NAME_RE = /^[a-z0-9][a-z0-9-]{0,63}$/;
1148
+ function isLocalProjectId(value) {
1149
+ return typeof value === "string" && LOCAL_PROJECT_ID_RE.test(value);
1150
+ }
1151
+ function localProjectMarker(input) {
1152
+ if (!isLocalProjectId(input.projectId) || !LOCAL_SCENE_NAME_RE.test(input.sceneName)) {
1153
+ throw new Error("A local project requires a valid project and scene identity");
1154
+ }
1155
+ return `${JSON.stringify({
1156
+ projectId: input.projectId,
1157
+ sceneName: input.sceneName,
1158
+ createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1159
+ valueProp: input.valueProp.trim(),
1160
+ ...input.brand ? { brand: input.brand } : {}
1161
+ })}
1162
+ `;
1163
+ }
1164
+
1145
1165
  // ../src/shared/initial-files.ts
1146
1166
  var TITLE_TEMPLATE = TEMPLATES.find((t) => t.id === "title-card");
1147
1167
  var BLANK_DIRECTION = TEMPLATES.find((t) => t.id === "blank").generate({});
@@ -1162,7 +1182,11 @@ function buildInitialFiles(input) {
1162
1182
  }
1163
1183
  })
1164
1184
  };
1165
- const projectMarker = JSON.stringify({
1185
+ const projectMarker = input.localProject ? localProjectMarker({
1186
+ ...input.localProject,
1187
+ valueProp: input.valueProp,
1188
+ brand: input.brand?.slug
1189
+ }) : JSON.stringify({
1166
1190
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1167
1191
  valueProp: input.valueProp.trim(),
1168
1192
  ...input.brand ? { brand: input.brand.slug } : {}
@@ -1193,12 +1217,11 @@ var flagList = (flags, name) => {
1193
1217
  var flagBool = (flags, name) => flags[name] === true;
1194
1218
  var out = (line) => console.log(line);
1195
1219
  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
- }
1220
+ const claude = flagBool(flags, "claude");
1221
+ const agent = flagBool(flags, "agent");
1200
1222
  await setup({
1201
- installAgentSkills: false,
1223
+ installAgentMcp: agent,
1224
+ installAgentSkills: agent,
1202
1225
  installClaudePlugin: claude,
1203
1226
  openBrowser: !flagBool(flags, "no-open"),
1204
1227
  log: out
@@ -1214,7 +1237,7 @@ async function login(flags) {
1214
1237
  if (appUrl) patch.appUrl = appUrl;
1215
1238
  const config = { ...loadConfig(), ...patch };
1216
1239
  if (!config.apiKey || !config.supabaseUrl) {
1217
- out("Missing API key. Run: npx --yes @impractical-ai/motion setup --claude");
1240
+ out("Missing API key. Run: npx --yes @impractical-ai/motion setup --agent");
1218
1241
  process.exitCode = 1;
1219
1242
  return;
1220
1243
  }
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-LJ4YH3YJ.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-P5CDHPDP.js"),
143
+ loadApi: () => import("./api-C7KJDYN4.js"),
144
+ loadMcp: () => import("./stdio-GHDI4S7P.js")
145
145
  }).catch(async (error) => {
146
- const { ApiError } = await import("./api-64X3G4M2.js");
146
+ const { ApiError } = await import("./api-C7KJDYN4.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
  }