@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.
- package/README.md +19 -0
- package/assets/engine.d.ts +793 -5
- package/assets/impractical/SKILL.md +53 -6
- package/assets/kits/actions.ts +803 -0
- package/assets/kits/ensemble.ts +483 -0
- package/assets/kits/highlight.ts +374 -0
- package/assets/kits/impact.ts +103 -0
- package/assets/kits/kits.ts +343 -0
- package/assets/kits/looks.ts +935 -0
- package/assets/kits/morphkit.ts +438 -0
- package/assets/kits/reveal.ts +1364 -0
- package/assets/kits/sentence.ts +218 -0
- package/assets/kits/streamkit.ts +195 -0
- package/dist/{api-KD6BNKBQ.js → api-DR3NJ7TI.js} +5 -2
- package/dist/chunk-77HVPD4G.js +43 -0
- package/dist/{chunk-DMAQPSA4.js → chunk-M7SHFM25.js} +222 -27
- package/dist/{chunk-7724464Z.js → chunk-MQKW7NFI.js} +65 -14
- package/dist/chunk-Z74SLKEU.js +67 -0
- package/dist/{commands-Y35XPDVH.js → commands-R3LJU2BW.js} +29 -4
- package/dist/index.js +6 -5
- package/dist/main-BEONOBG5.js +2218 -0
- package/dist/{stdio-ZFPBM6Z6.js → stdio-XUVQKDCB.js} +3169 -431
- package/package.json +4 -3
- package/dist/chunk-JDN3QHCX.js +0 -98
|
@@ -6,17 +6,26 @@ import {
|
|
|
6
6
|
loadConfig,
|
|
7
7
|
recoverCorruptConfig,
|
|
8
8
|
saveConfig
|
|
9
|
-
} from "./chunk-
|
|
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-
|
|
16
|
+
} from "./chunk-Z74SLKEU.js";
|
|
17
17
|
|
|
18
18
|
// src/onboarding.ts
|
|
19
|
-
import {
|
|
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
|
|
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 (
|
|
143
|
-
if (
|
|
144
|
-
|
|
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
|
-
|
|
147
|
-
runMcpCommand(command, addArgs, harness);
|
|
234
|
+
return { harness };
|
|
148
235
|
}
|
|
149
|
-
|
|
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 (
|
|
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
|
-
|
|
314
|
+
checks.push({ harness: "Cursor", path: join(home, ".cursor", "mcp.json") });
|
|
196
315
|
}
|
|
197
316
|
if (commandAvailable("opencode")) {
|
|
198
|
-
|
|
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 (
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
|
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
|
|
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-
|
|
5
|
+
} from "./chunk-Z74SLKEU.js";
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
|
-
import {
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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-
|
|
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-
|
|
15
|
-
import "./chunk-
|
|
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 =
|
|
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-
|
|
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-
|
|
143
|
-
loadApi: () => import("./api-
|
|
144
|
-
loadMcp: () => import("./stdio-
|
|
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-
|
|
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.");
|