@petercjl/infinite-canvas-cli 0.1.2 → 0.1.4
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 +2 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/bin/infinite-canvas.mjs +6 -3
- package/capabilities.json +24 -5
- package/package.json +6 -4
- package/skills/infinite-canvas-video/SKILL.md +13 -4
- package/skills/infinite-canvas-video/references/runtime.md +15 -0
- package/src/contracts.mjs +4 -2
- package/src/ffprobe.mjs +30 -0
- package/src/media-cli.mjs +1 -0
- package/src/postinstall.mjs +2 -0
- package/src/workflow.mjs +4 -1
package/README.md
CHANGED
|
@@ -52,3 +52,5 @@ See the bundled [Skill](skills/infinite-canvas-video/SKILL.md) and [runtime comm
|
|
|
52
52
|
Canvas operations reuse the existing helper CLI. Frame grouping provides the missing atomic operation for reparenting existing nodes, with backup, fresh-read conflict checks and save/readback verification. Website-authenticated read and canvas operations are isolated in one browser adapter. The adapter does not directly submit generation endpoints.
|
|
53
53
|
|
|
54
54
|
The service has no documented compare-and-swap canvas save contract. Avoid concurrent manual editing during a job; detected unsaved or conflicting changes stop progress for recovery. Canvas synchronization relies on the current webpage editor adapter and must be revalidated when the webpage changes.
|
|
55
|
+
|
|
56
|
+
Video verification uses the npm-managed platform ffprobe, checked before video submission and exposed by `doctor --json`. Install with optional dependencies enabled. Advanced hosts can configure `INFINITE_CANVAS_FFPROBE_BIN`; see THIRD_PARTY_NOTICES.md for supported binaries and licensing. Existing generated tasks resume verification after dependency repair without another paid submission.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# ffprobe runtime dependency
|
|
2
|
+
|
|
3
|
+
This package depends on `@ffprobe-installer/ffprobe` (LGPL-2.1), which installs a platform-specific ffprobe executable from npm. The wrapper and platform packages retain their own licenses and binary build notices. This dependency does not change the license of this package's source code.
|
|
4
|
+
|
|
5
|
+
Wrapper source, platform build sources and licensing details: https://github.com/SavageCore/node-ffprobe-installer
|
|
6
|
+
|
|
7
|
+
FFmpeg / ffprobe upstream source and licensing: https://ffmpeg.org/download.html and https://ffmpeg.org/legal.html
|
|
8
|
+
|
|
9
|
+
Supported packaged targets: macOS arm64/x64, Windows ia32/x64, Linux arm/arm64/ia32/x64. Other targets may supply their own working ffprobe executable. Executables are invoked as separate processes; they are not linked into this package.
|
package/bin/infinite-canvas.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
|
+
import {ffprobeStatus,setupFFprobe} from '../src/ffprobe.mjs';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import crypto from 'node:crypto';
|
|
5
6
|
import {META,ROOT,need,parse,readJSON,execute,stateRoot,newJSON,redact,safeId} from '../src/core.mjs';
|
|
@@ -13,8 +14,9 @@ async function main(argv){
|
|
|
13
14
|
need(Number(process.versions.node.split('.')[0])>=22,'CAPABILITY_UNAVAILABLE','Node.js 22 or newer is required.');
|
|
14
15
|
if(['version','--version','-v'].includes(command)){const {args,options}=parse(argv.slice(1),{booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');return options.json?out({ok:true,name:META.name,version:META.version,entry:path.join(ROOT,'bin/infinite-canvas.mjs')}):console.log('infinite-canvas '+META.version);}
|
|
15
16
|
if(argv.includes('--help')||argv.includes('-h')){need(['install','video','task','frame','browser','board','skill','update','doctor','capabilities'].includes(command),'UNKNOWN_COMMAND','Unknown command.');return console.log(HELP);}
|
|
17
|
+
if(command==='dependencies'&&action==='setup'){const {args}=parse(rest,{booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');const check=setupFFprobe();if(!check.ok)process.exitCode=1;return out({ok:check.ok,ffprobe:check});}
|
|
16
18
|
if(command==='install'){
|
|
17
|
-
const {options:o,args}=parse(argv.slice(1),{values:['agent','path'],booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');return out(await completeInstallation(o.agent,o.path));
|
|
19
|
+
const {options:o,args}=parse(argv.slice(1),{values:['agent','path'],booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');const dependency=setupFFprobe();need(dependency.ok,'DEPENDENCY_UNAVAILABLE',dependency.error?.message,{dependency:'ffprobe'});return out(await completeInstallation(o.agent,o.path));
|
|
18
20
|
}
|
|
19
21
|
if(command==='_worker'){need(argv.length===2,'INVALID_ARGUMENT','Invalid worker invocation.');const {processJob}=await import('../src/workflow.mjs');return out(await processJob(action));}
|
|
20
22
|
if(command==='skill'){
|
|
@@ -31,13 +33,13 @@ async function main(argv){
|
|
|
31
33
|
}
|
|
32
34
|
if(command==='capabilities'){const {args}=parse(argv.slice(1),{booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');await automaticCheck();return out({ok:true,...manifest});}
|
|
33
35
|
if(command==='doctor'){
|
|
34
|
-
const {options:o,args}=parse(argv.slice(1),{values:['canvas'],booleans:['live','json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');await automaticCheck();const checks={node:process.version,entry:path.join(ROOT,'bin/infinite-canvas.mjs'),platform:process.platform,skill:SKILL};
|
|
36
|
+
const {options:o,args}=parse(argv.slice(1),{values:['canvas'],booleans:['live','json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');await automaticCheck();const checks={ffprobe:ffprobeStatus(),node:process.version,entry:path.join(ROOT,'bin/infinite-canvas.mjs'),platform:process.platform,skill:SKILL};
|
|
35
37
|
for(const library of ['playwright-core','image-size']){try{await import(library);checks[library]={ok:true};}catch{checks[library]={ok:false};}}
|
|
36
38
|
try{checks.media={ok:true,version:(await execute(process.env.INFINITE_CANVAS_MEDIA_BIN||'sealseek-media',['--version'],{timeout:15000})).stdout.trim()};}catch{checks.media={ok:false};}
|
|
37
39
|
const {ChromeRuntime}=await import('../src/browser-runtime.mjs');const runtime=new ChromeRuntime();
|
|
38
40
|
try{checks.browser=await runtime.configuredLauncher('status')||await runtime.status();}catch(e){checks.browser={ok:false,error:redact(e)};}
|
|
39
41
|
if(o.live){need(o.canvas,'INVALID_ARGUMENT','Live doctor requires --canvas ID.');const {BrowserDriver,LOCATORS}=await import('../src/browser.mjs');const driver=new BrowserDriver();try{await driver.connect(o.canvas,{open:false});const board=await driver.getBoard();checks.live={canvas_id:String(board.id),title:board.title,locators:{}};for(const [k,s]of Object.entries(LOCATORS))checks.live.locators[k]=await driver.page.locator(s).count();}finally{await driver.close();}}
|
|
40
|
-
const ok=checks.media.ok&&checks['playwright-core'].ok&&checks['image-size'].ok&&checks.browser.ok;if(!ok)process.exitCode=1;return out({ok,checks,browser_ready:!!checks.browser.ready,paid_action:false,end_to_end:'not-tested-by-doctor'});
|
|
42
|
+
const ok=checks.ffprobe.ok&&checks.media.ok&&checks['playwright-core'].ok&&checks['image-size'].ok&&checks.browser.ok;if(!ok)process.exitCode=1;return out({ok,checks,browser_ready:!!checks.browser.ready,paid_action:false,end_to_end:'not-tested-by-doctor'});
|
|
41
43
|
}
|
|
42
44
|
if(command==='browser'){
|
|
43
45
|
const {args}=parse(rest,{booleans:['json']});need(!args.length&&['status','open'].includes(action),'INVALID_ARGUMENT','Use browser status/open --json.');
|
|
@@ -48,6 +50,7 @@ async function main(argv){
|
|
|
48
50
|
if(command==='video'&&action==='generate'){
|
|
49
51
|
const {options:o,args}=parse(rest,{values:['canvas','prompt','prompt-file','output','request-id'],repeat:['reference'],booleans:['new','submit','dry-run','json']});need(!args.length&&!(o.submit&&o['dry-run']),'INVALID_ARGUMENT','Conflicting options or extra arguments.');need(!!o.prompt!==!!o['prompt-file'],'INVALID_ARGUMENT','Provide exactly one prompt source.');need(!(o.new&&o['request-id']),'INVALID_ARGUMENT','Use new or request-id.');
|
|
50
52
|
const {prepareInput,createJob,summary}=await import('../src/state.mjs');const input=await prepareInput({canvas:o.canvas,prompt:o.prompt??await fs.readFile(o['prompt-file'],'utf8'),references:o.reference,output:o.output});await automaticCheck();
|
|
53
|
+
const probe=ffprobeStatus();need(probe.ok,'DEPENDENCY_UNAVAILABLE',probe.error?.message,{dependency:'ffprobe',paid_action:false});
|
|
51
54
|
if(!o.submit)return out({ok:true,dry_run:true,paid_action:false,canvas_id:input.canvasId,prompt:input.prompt,references:input.references.map((r,i)=>({label:'图'+(i+1),source:r.source,sha256:r.sha256||null})),workflow:['import-references','new-native-conversation','ordered-attachments','send-prompt','follow-run','download','deduplicate-video','group-original-nodes-in-frame'],generation_submission:'webpage Agent Send button',model:'inherit webpage settings'});
|
|
52
55
|
const {job,reused}=await createJob(input,{requestId:o['request-id'],newRequest:!!o.new});if(!reused){const {spawnWorker}=await import('../src/workflow.mjs');await spawnWorker(job.id);}return out({...summary(job),reused});
|
|
53
56
|
}
|
package/capabilities.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema": "infinite-canvas-capabilities",
|
|
3
3
|
"schema_version": "1.0.0",
|
|
4
4
|
"package": "@petercjl/infinite-canvas-cli",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.4",
|
|
6
6
|
"installationLifecycle": {
|
|
7
7
|
"required_components": [
|
|
8
8
|
"cli",
|
|
@@ -57,6 +57,12 @@
|
|
|
57
57
|
"concurrency": "separate conversations in the same tab; queued UI submissions and canvas writes; parallel remote runs",
|
|
58
58
|
"submission": "click native Send",
|
|
59
59
|
"directGenerationAPI": false,
|
|
60
|
+
"mediaHelperScope": [
|
|
61
|
+
"canvas.get",
|
|
62
|
+
"canvas.add",
|
|
63
|
+
"image.upload"
|
|
64
|
+
],
|
|
65
|
+
"requestedVideoParameters": "Convey task requirements in the prepared native Agent prompt; explicit final prompts stay unchanged; verify actual artifact metadata.",
|
|
60
66
|
"recovery": "saved sessionId/runId; read-only run and history APIs",
|
|
61
67
|
"model": "inherit webpage settings"
|
|
62
68
|
},
|
|
@@ -66,11 +72,15 @@
|
|
|
66
72
|
"canvasAPI": "sealseek-media >=0.6.7, public CLI commands",
|
|
67
73
|
"libraries": [
|
|
68
74
|
"playwright-core",
|
|
69
|
-
"image-size"
|
|
75
|
+
"image-size",
|
|
76
|
+
"@ffprobe-installer/ffprobe"
|
|
70
77
|
],
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
78
|
+
"ffprobe": {
|
|
79
|
+
"requiredFor": "video preflight and artifact verification",
|
|
80
|
+
"discovery": "INFINITE_CANVAS_FFPROBE_BIN, npm platform binary, PATH",
|
|
81
|
+
"check": "doctor --json -> checks.ffprobe",
|
|
82
|
+
"recovery": "Reinstall with optional dependencies enabled or configure a working ffprobe; resume saved tasks without resubmission."
|
|
83
|
+
}
|
|
74
84
|
},
|
|
75
85
|
"platforms": {
|
|
76
86
|
"codex:macos": "tested: six native Agent video tasks across two boards; one tab per board; downloads, deduplication and Frame readback verified",
|
|
@@ -79,6 +89,15 @@
|
|
|
79
89
|
"generic:windows": "built-in Chrome discovery/startup implemented; actual host video end-to-end verification pending"
|
|
80
90
|
},
|
|
81
91
|
"commandContracts": {
|
|
92
|
+
"dependencies setup": {
|
|
93
|
+
"options": {
|
|
94
|
+
"--json": {
|
|
95
|
+
"type": "boolean"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"sideEffects": "local-write",
|
|
99
|
+
"output": "Verify the npm-managed ffprobe; restore its executable bit on POSIX if install scripts were blocked."
|
|
100
|
+
},
|
|
82
101
|
"install": {
|
|
83
102
|
"options": {
|
|
84
103
|
"--agent": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/infinite-canvas-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Drive native Infinite Canvas Agent conversations and organize reference images and results in a Frame.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"skills",
|
|
13
13
|
"capabilities.json",
|
|
14
14
|
"README.md",
|
|
15
|
-
"LICENSE"
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"THIRD_PARTY_NOTICES.md"
|
|
16
17
|
],
|
|
17
18
|
"engines": {
|
|
18
19
|
"node": ">=22"
|
|
@@ -28,8 +29,9 @@
|
|
|
28
29
|
"check:packed": "node scripts/check-packed-install.mjs"
|
|
29
30
|
},
|
|
30
31
|
"dependencies": {
|
|
31
|
-
"
|
|
32
|
-
"image-size": "2.0.4"
|
|
32
|
+
"@ffprobe-installer/ffprobe": "2.1.2",
|
|
33
|
+
"image-size": "2.0.4",
|
|
34
|
+
"playwright-core": "1.62.1"
|
|
33
35
|
},
|
|
34
36
|
"repository": {
|
|
35
37
|
"type": "git",
|
|
@@ -9,7 +9,7 @@ Execute an explicitly selected canvas video request with the installed `infinite
|
|
|
9
9
|
|
|
10
10
|
## Input and output
|
|
11
11
|
|
|
12
|
-
Require
|
|
12
|
+
Require an existing board file ID or an explicit request to create a named board, the exact prompt and 1–20 ordered PNG, JPEG or WebP references (local paths or HTTPS URLs). Ask only for missing required inputs. Preserve the prompt and image order; 图1 means the first attached image. Use the webpage model and native Agent workflow. When composing the execution prompt from a task, include the requested duration, aspect ratio, resolution and reference order in natural language. Preserve a final prompt explicitly supplied verbatim; resolve conflicts with webpage settings before submission. The absence of separate CLI flags is not evidence that the webpage Agent cannot receive these requirements. An authorized generation request allows one paid native Agent submission. A planning or dry-run request allows validation only.
|
|
13
13
|
|
|
14
14
|
Return the local MP4 from `artifacts[].path`, task ID, board ID and Frame ID. Success means `status=succeeded`, a nonempty downloaded video, original reference node IDs plus one video node in the saved Frame, and verified readback. A generated video with unfinished grouping is a partial result, reported with its task ID and recovery action.
|
|
15
15
|
|
|
@@ -17,7 +17,15 @@ Return the local MP4 from `artifacts[].path`, task ID, board ID and Frame ID. Su
|
|
|
17
17
|
|
|
18
18
|
Resolve `infinite-canvas` from PATH. Run `version --json`, `capabilities --json` and `skill source --json` to inspect the installed implementation and current canonical Skill. Minimum contract: native Agent Send, ordered references, existing-node grouping, task get/wait/resume, local video output. Load the selected host adapter in `adapters/`; `generic.json` is the portable fallback. If the entry or required feature is missing, return `CAPABILITY_UNAVAILABLE`; report the missing dependency instead of substituting another generation provider.
|
|
19
19
|
|
|
20
|
-
External dependencies are Node.js 22+, `playwright-core` and `image-size` (package managed), installed Google Chrome on macOS or Windows, and `sealseek-media` with image upload, canvas get/add capabilities. This Skill invokes the helper through the CLI contract; it does not compose another generation Skill. Browser authentication stays in the user's dedicated Chrome profile. Never copy cookies or tokens into a prompt, output or package.
|
|
20
|
+
External dependencies are Node.js 22+, `playwright-core` and `image-size` and platform `ffprobe` (package managed), installed Google Chrome on macOS or Windows, and `sealseek-media` with image upload, canvas get/add capabilities. This Skill invokes the helper through the CLI contract; it does not compose another generation Skill. Browser authentication stays in the user's dedicated Chrome profile. Never copy cookies or tokens into a prompt, output or package.
|
|
21
|
+
|
|
22
|
+
## Generation route and helper boundary
|
|
23
|
+
|
|
24
|
+
Video generation in this Skill always uses `infinite-canvas video generate`: one native Agent conversation and the webpage Send button. `sealseek-media` supplies canvas and media-management operations only. Never call its video/image generation commands or load its generation Skill as an alternative workflow. A missing flag, authentication error or UI issue does not authorize changing the generation route.
|
|
25
|
+
|
|
26
|
+
Use the supplied board ID. If the user explicitly requests a new board, create it through the helper's documented `canvas create --title TITLE --submit --json`, retain the returned canvas ID and return to this Skill's native video flow. The helper may require its own CLI login for canvas reads/writes and image upload; browser login and CLI authentication are separate. On `AUTH_REJECTED`, complete the helper's normal `auth login` flow and verify it before resuming the same canvas operation. Login does not authorize generation.
|
|
27
|
+
|
|
28
|
+
Inspect `capabilities` and dry-run output to distinguish explicit command options, inherited webpage settings and requirements conveyed in the prompt. Report a specific unverified or unsupported requirement accurately. Verify the actual returned video's dimensions and duration when inspection is available; a prompt or local dry run alone is not proof of delivery parameters.
|
|
21
29
|
|
|
22
30
|
## Installation and update
|
|
23
31
|
|
|
@@ -25,7 +33,7 @@ Treat plugin installation/update as one operation covering the CLI and this comp
|
|
|
25
33
|
|
|
26
34
|
## Main line
|
|
27
35
|
|
|
28
|
-
1. Validate required inputs and authorization. Run `doctor --json
|
|
36
|
+
1. Validate required inputs and authorization. Run `doctor --json` and require `checks.ffprobe.ok=true` before video submission. If npm scripts were blocked, run `dependencies setup --json` to restore and verify the package-managed executable; missing binaries require npm optional dependencies. Its browser check means Chrome is available to launch; `browser_ready` separately reports runtime readiness. Use `browser open --json` to prepare Chrome. The CLI discovers and launches the system browser itself with fixed loopback 9223 and the persistent current-user `.dianshang-chrome-profile`; a separate launcher installation is unnecessary. For a UI compatibility check use `doctor --live --canvas ID --json`. On missing login, have the user sign in in that Chrome window, then return here. The CLI uses CDP port 9223 and the dedicated ecommerce profile. Enforce the single-tab constraint below before any submission or canvas write.
|
|
29
37
|
2. Prepare the exact prompt in a new file when needed; preserve any existing file. Run `video generate --canvas ID --prompt-file FILE --reference IMAGE1 --reference IMAGE2 --dry-run --json`, repeating `--reference` in order. Dry run checks local inputs; it does not prove provider availability.
|
|
30
38
|
3. For an authorized real request, run the same inputs with `--submit --json`. Save the returned task ID immediately. Repeated identical inputs reuse the same task. `--request-id KEY` provides caller-controlled idempotency; `--new` deliberately creates a new paid task and requires explicit intent to generate another video.
|
|
31
39
|
4. Follow `task wait TASK_ID --timeout 30 --json` or `task get TASK_ID --json`. The worker imports references, creates a new native Agent conversation, attaches images, pastes the exact prompt and clicks Send. Status/history reads track the captured session and run. Generation is submitted only through the native UI.
|
|
@@ -45,6 +53,7 @@ If duplicate tabs for a board are detected, stop new submissions and writes with
|
|
|
45
53
|
|
|
46
54
|
## Branches and recovery
|
|
47
55
|
|
|
56
|
+
- `DEPENDENCY_UNAVAILABLE` / `INSPECTION_UNAVAILABLE`: read `doctor --json`, restore npm optional dependencies or configure `INFINITE_CANVAS_FFPROBE_BIN` to a working probe. For an already generated video, resume the same task after repair; dependency recovery never authorizes another generation. Return to step 1 before submission or step 4 after submission.
|
|
48
57
|
- `CHROME_NOT_FOUND`: install Google Chrome or set `INFINITE_CANVAS_CHROME_BIN` to its actual executable; return to step 1.
|
|
49
58
|
- `BROWSER_PORT_CONFLICT` / `BROWSER_OWNERSHIP_UNVERIFIED`: preserve existing browser processes/profile files, resolve the 9223 ownership conflict, then return to step 1.
|
|
50
59
|
- `BROWSER_START_TIMEOUT`: inspect the dedicated profile’s existing Chrome and startup permissions; preserve task IDs and return to step 1 after readiness is restored.
|
|
@@ -61,6 +70,6 @@ Read [runtime reference](references/runtime.md) only when command details or rec
|
|
|
61
70
|
|
|
62
71
|
## QA and evolution
|
|
63
72
|
|
|
64
|
-
Check one clean user message, captured native session/run IDs, attachment order at submission, exactly one result URL, returned reference metadata when provided, artifact bytes/hash and saved Frame membership.
|
|
73
|
+
Check one clean user message, captured native session/run IDs, attachment order at submission, exactly one result URL, returned reference metadata when provided, artifact bytes/hash and saved Frame membership. Verify actual video dimensions and duration through the packaged ffprobe; prompt parameters and MP4 headers alone do not prove these values. Report unavailable reference metadata honestly. Scope claims to actually tested hosts and operating systems. The current browser adapter needs live validation after webpage changes; independent clean-context regression is user gated.
|
|
65
74
|
|
|
66
75
|
Install/update only the executing/requested Agent through package-managed `skill install/update --agent AGENT_NAME --path RESOLVED_SKILL_DIR`. A named adapter supplies a default only for that host; other valid Agent names use the generic adapter and their own explicitly resolved Skill directory. The package is the source of truth. Automatic update detection is bounded and can be disabled; installation is explicit. Keep model defaults and private assets outside the package.
|
|
@@ -38,3 +38,18 @@ The CLI never prints browser credentials. Changing source, a live webpage deploy
|
|
|
38
38
|
Only Google Chrome is an external browser prerequisite. macOS discovery checks system and current-user Applications; Windows checks per-user and Program Files locations, then App Paths registry entries. `INFINITE_CANVAS_CHROME_BIN` selects an actual executable for a custom installation. No extra launcher is required. `INFINITE_CANVAS_BROWSER_BIN` remains an explicit legacy override and is verified against the same port/profile contract.
|
|
39
39
|
|
|
40
40
|
`browser status` is read-only. `chrome.installed` and `available` describe installation; `running` and `ready` describe the dedicated instance. A stopped but installed Chrome passes the browser dependency check in `doctor`. `browser open` and normal video execution launch/reuse it, verify process ownership, loopback CDP 9223 and the current-user profile. On Windows this profile is under the current home directory; on macOS it is under the current home directory with the same `.dianshang-chrome-profile` name. Windows uses built-in PowerShell process/TCP queries; macOS uses bounded nonblocking system process queries. Startup is queued so concurrent jobs share the browser. `doctor --live --canvas ID` requires the running, authenticated, editable board as well.
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
## Native video route and Windows text I/O
|
|
44
|
+
|
|
45
|
+
`infinite-canvas video generate` sends the exact prepared prompt to the webpage Agent. Duration, aspect ratio and resolution requested in a task belong in the prepared natural-language prompt unless an explicitly selected final prompt must remain verbatim. The CLI does not advertise separate flags for these settings; preserve the native route and distinguish webpage settings from prompt-level requirements. Verify actual output metadata before reporting exact dimensions or duration.
|
|
46
|
+
|
|
47
|
+
`sealseek-media` is a canvas/media helper, including board creation, reads, uploads and adding existing media. It is not this Skill's generation route. Its CLI credentials can require a separate login even while the dedicated Chrome is logged in. Resolve that dependency and return to the native flow; do not submit a second generation through the helper.
|
|
48
|
+
|
|
49
|
+
On Windows, use the host's declared shell and text-encoding contract. Keep JSON intact and parse it before selecting fields; a truncated text excerpt is not the complete result. Console replacement characters are inconclusive: inspect UTF-8 bytes or hashes before changing prompts or source files. If native CLI output or redirection is decoded incorrectly, report the host encoding issue. Preserve the existing task ID and submission receipt; recovery inspects that same task and never repeats a paid Send to obtain readable output.
|
|
50
|
+
|
|
51
|
+
## Video inspection dependency
|
|
52
|
+
|
|
53
|
+
The npm package manages `@ffprobe-installer/ffprobe` and its platform binary. `doctor --json` includes `checks.ffprobe` with readiness, version and executable source. `INFINITE_CANVAS_FFPROBE_BIN` explicitly overrides the binary; PATH is used when the packaged probe is unavailable. Install with npm optional dependencies enabled. A failed preflight stops before paid submission. For an existing task, dependency repair followed by `task resume` reuses its native session/run and generated video. Exact dimensions and duration require actual metadata inspection.
|
|
54
|
+
|
|
55
|
+
When npm blocks dependency lifecycle scripts, `dependencies setup --json` restores the executable bit only on the canonical npm-managed ffprobe binary and verifies its version. It preserves configured external binaries. Ordinary doctor/inspection stays read-only; missing platform packages require installation with optional dependencies enabled.
|
package/src/contracts.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import {META} from './core.mjs';
|
|
|
2
2
|
import {installationContract} from './lifecycle.mjs';
|
|
3
3
|
const s={type:'string'},b={type:'boolean'};
|
|
4
4
|
export const commandContracts={
|
|
5
|
+
'dependencies setup':{options:{'--json':b},sideEffects:'local-write',output:'Verify the npm-managed ffprobe; restore its executable bit on POSIX if install scripts were blocked.'},
|
|
5
6
|
'install':{options:{'--agent':{...s,required:true},'--path':s,'--json':b},sideEffects:'local-write',output:'Verified CLI and current Agent companion Skill; complete=true only when both are ready'},
|
|
6
7
|
'video generate':{options:{'--canvas':s,'--prompt':s,'--prompt-file':s,'--reference':{...s,repeatable:true},'--output':s,'--request-id':s,'--new':b,'--submit':b,'--dry-run':b,'--json':b},sideEffects:'local-write',externalMutation:'With --submit: import references, send native Agent conversation, import a missing video, group selected elements in a Frame.',paid:'Only native webpage sending may charge credits.'},
|
|
7
8
|
'task get|wait|resume|sync':{options:{'--timeout':s,'--json':b},sideEffects:'local-write',externalMutation:'resume continues original task; sync reapplies existing video archival and grouping; never a second generation.'},
|
|
@@ -19,8 +20,8 @@ export const manifest={schema:'infinite-canvas-capabilities',schema_version:'1.0
|
|
|
19
20
|
input:{required:['canvas_id','prompt','references'],references:'ordered PNG/JPEG/WebP local files or HTTPS URLs'},
|
|
20
21
|
output:{terminal:'local MP4 + saved Frame containing original reference nodes and one video node',fields:['task_id','status','canvas_id','session_id','run_id','frame_id','reference_element_ids','video_element_id','artifacts']},
|
|
21
22
|
operations:['browser.status','browser.open','board.inspect','video.generate.native-agent','task.inspect','task.wait','task.resume','task.sync','frame.group-existing'],
|
|
22
|
-
generationTransport:{kind:'native-browser-ui',newConversationPerLogicalVideo:true,singleTabPerCanvas:true,concurrency:'separate conversations in the same tab; queued UI submissions and canvas writes; parallel remote runs',submission:'click native Send',directGenerationAPI:false,recovery:'saved sessionId/runId; read-only run and history APIs',model:'inherit webpage settings'},
|
|
23
|
-
dependencies:{node:'>=22',browser:'Installed Google Chrome; built-in macOS/Windows discovery and startup on loopback 9223 with current-user dedicated profile',canvasAPI:'sealseek-media >=0.6.7, public CLI commands',libraries:['playwright-core','image-size'],
|
|
23
|
+
generationTransport:{kind:'native-browser-ui',newConversationPerLogicalVideo:true,singleTabPerCanvas:true,concurrency:'separate conversations in the same tab; queued UI submissions and canvas writes; parallel remote runs',submission:'click native Send',directGenerationAPI:false,mediaHelperScope:['canvas.get','canvas.add','image.upload'],requestedVideoParameters:'Convey task requirements in the prepared native Agent prompt; explicit final prompts stay unchanged; verify actual artifact metadata.',recovery:'saved sessionId/runId; read-only run and history APIs',model:'inherit webpage settings'},
|
|
24
|
+
dependencies:{node:'>=22',browser:'Installed Google Chrome; built-in macOS/Windows discovery and startup on loopback 9223 with current-user dedicated profile',canvasAPI:'sealseek-media >=0.6.7, public CLI commands',libraries:['playwright-core','image-size','@ffprobe-installer/ffprobe'],ffprobe:{requiredFor:'video preflight and artifact verification',discovery:'INFINITE_CANVAS_FFPROBE_BIN, npm platform binary, PATH',check:'doctor --json -> checks.ffprobe',recovery:'Reinstall with optional dependencies enabled or configure a working ffprobe; resume saved tasks without resubmission.'}},
|
|
24
25
|
platforms:{'codex:macos':'tested: six native Agent video tasks across two boards; one tab per board; downloads, deduplication and Frame readback verified','generic:macos':'spec-compatible, runtime untested','sealseek:macos':'spec-compatible, runtime untested','generic:windows':'built-in Chrome discovery/startup implemented; actual host video end-to-end verification pending'},
|
|
25
26
|
commandContracts,
|
|
26
27
|
automaticUpdates:{enabledByDefault:true,defaultMode:'notify',intervalHours:24,timeoutMs:1500,trigger:'ordinary video/frame/doctor/capabilities entry; excludes help/version/skill/task polling',configuration:'private update-policy.json; update auto on|off|status',disable:'INFINITE_CANVAS_AUTO_UPDATE=0',recovery:'offline backoff preserves installation; source checkout updates through Git; explicit install keeps a package backup',installation:'explicit --yes only; no automatic installation'},
|
|
@@ -44,6 +45,7 @@ frame group --canvas ID --elements IDS.json --frame-id ID [--name TEXT]
|
|
|
44
45
|
browser status --json (read-only installation and runtime state)
|
|
45
46
|
browser open --json (built-in system Chrome startup/reuse)
|
|
46
47
|
board inspect --canvas ID --json
|
|
48
|
+
dependencies setup --json (verify/repair npm-managed ffprobe)
|
|
47
49
|
doctor [--live --canvas ID] --json
|
|
48
50
|
capabilities --json
|
|
49
51
|
version [--json]
|
package/src/ffprobe.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {statSync,chmodSync} from 'node:fs';
|
|
2
|
+
import {createRequire} from 'node:module';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
const require=createRequire(import.meta.url);
|
|
5
|
+
const recovery='Run dependencies setup --json to verify this package-managed binary; reinstall with optional dependencies enabled if it is missing. Alternatively install FFmpeg/ffprobe in PATH or set INFINITE_CANVAS_FFPROBE_BIN to its executable.';
|
|
6
|
+
const bundled=()=>require('@ffprobe-installer/ffprobe').path;
|
|
7
|
+
export function ffprobeCommand({env=process.env,packaged=bundled}={}){
|
|
8
|
+
if(env.INFINITE_CANVAS_FFPROBE_BIN)return {file:env.INFINITE_CANVAS_FFPROBE_BIN,source:'configured'};
|
|
9
|
+
try{const file=packaged();if(file)return {file,source:'package'};}catch{}
|
|
10
|
+
return {file:'ffprobe',source:'PATH'};
|
|
11
|
+
}
|
|
12
|
+
export function runFFprobe(args,{spawn=spawnSync,...options}={}){
|
|
13
|
+
const command=ffprobeCommand(options);
|
|
14
|
+
return spawn(command.file,args,{encoding:'utf8',timeout:30000,maxBuffer:1024*1024,windowsHide:true,shell:false,env:options.env||process.env});
|
|
15
|
+
}
|
|
16
|
+
export function ffprobeStatus(options={}){
|
|
17
|
+
const command=ffprobeCommand(options),r=runFFprobe(['-version'],options);
|
|
18
|
+
const version=r.status===0&&/^ffprobe version ([^\r\n]+)/m.exec(r.stdout||'')?.[1];
|
|
19
|
+
return version?{ok:true,source:command.source,version}:{ok:false,source:command.source,error:{code:'DEPENDENCY_UNAVAILABLE',dependency:'ffprobe',message:recovery}};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function setupFFprobe(){
|
|
23
|
+
const command=ffprobeCommand();
|
|
24
|
+
// Only the canonical npm dependency's own binary may have its executable bit restored.
|
|
25
|
+
if(command.source==='package'&&process.platform!=='win32'){
|
|
26
|
+
const info=statSync(command.file);
|
|
27
|
+
if(info.isFile()&&!(info.mode&0o100))chmodSync(command.file,info.mode|0o100);
|
|
28
|
+
}
|
|
29
|
+
return ffprobeStatus();
|
|
30
|
+
}
|
package/src/media-cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import {execute,newJSON,readJSON,exists,canonical,need,Fault} from './core.mjs';
|
|
|
4
4
|
export class MediaCLI {
|
|
5
5
|
constructor(){this.bin=process.env.INFINITE_CANVAS_MEDIA_BIN||'sealseek-media';}
|
|
6
6
|
async call(args){
|
|
7
|
+
need(['canvas get','canvas add','image upload'].includes(args.slice(0,2).join(' ')), 'MEDIA_HELPER_SCOPE', 'The media helper only reads/writes canvas media. Video generation uses the native Infinite Canvas Agent.');
|
|
7
8
|
try{const {stdout}=await execute(this.bin,[...args,'--json'],{timeout:90000,maxBuffer:32*1024*1024,env:{...process.env,SEALSEEK_MEDIA_AUTO_UPDATE:'0'}});const r=JSON.parse(stdout);need(r.ok!==false,r.error?.code||'MEDIA_API_FAILED',r.error?.message||'Canvas operation failed.');return r;}
|
|
8
9
|
catch(e){if(e instanceof Fault)throw e;let r;try{r=JSON.parse(e.stdout);}catch{}throw new Fault(r?.error?.code||'MEDIA_CLI_FAILED',r?.error?.message||'sealseek-media could not complete this operation.',{command:args.slice(0,2),cause:e.code});}
|
|
9
10
|
}
|
package/src/postinstall.mjs
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
import {setupFFprobe} from './ffprobe.mjs';
|
|
2
|
+
setupFFprobe();
|
|
1
3
|
import {installationContract} from './lifecycle.mjs';
|
|
2
4
|
console.log(JSON.stringify({package:'@petercjl/infinite-canvas-cli',complete:false,code:'SKILL_SETUP_REQUIRED',message:'The CLI and bundled Skill are installed in the npm package. The current Agent must install/verify the companion Skill in its own Skill directory before reporting installation or update complete.',...installationContract}));
|
package/src/workflow.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
|
+
import {runFFprobe} from './ffprobe.mjs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import crypto from 'node:crypto';
|
|
4
5
|
import {spawn} from 'node:child_process';
|
|
@@ -55,7 +56,9 @@ async function downloadResult(job){
|
|
|
55
56
|
need(!/text|json/.test(response.headers.get('content-type')||''),'OUTPUT_CONTRACT_FAILED','Video URL returned text.');
|
|
56
57
|
const chunks=[];let size=0;for await(const c of response.body){size+=c.length;need(size<=1024*1024*1024,'OUTPUT_CONTRACT_FAILED','Video exceeds the download limit.');chunks.push(c);}const data=Buffer.concat(chunks);need(data.length>0,'OUTPUT_CONTRACT_FAILED','Video is empty.');
|
|
57
58
|
if(await exists(file))need(hash(await fs.readFile(file))===hash(data),'OUTPUT_EXISTS','Preserve a different existing output file.');else await newFile(file,data);
|
|
58
|
-
|
|
59
|
+
const probe=runFFprobe(['-v','error','-show_entries','stream=codec_type,width,height,duration:format=duration,size','-of','json',file]);
|
|
60
|
+
need(!probe.error&&probe.status===0,'INSPECTION_UNAVAILABLE','Saved video could not be inspected. Run doctor --json, repair ffprobe and resume this task without generating again.');
|
|
61
|
+
const inspect=JSON.parse(probe.stdout);need(inspect.streams?.some(s=>s.codec_type==='video'&&s.width>0&&s.height>0),'OUTPUT_CONTRACT_FAILED','Downloaded file has no video stream.');
|
|
59
62
|
job.artifacts=[{path:file,bytes:data.length,sha256:hash(data),inspect:inspect||null}];await saveJob(job);
|
|
60
63
|
}
|
|
61
64
|
function resolveVideoMatches(job,matches){
|