@petercjl/infinite-canvas-cli 0.0.0-stage → 0.1.1
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/LICENSE +9 -0
- package/README.md +47 -2
- package/bin/infinite-canvas.mjs +62 -0
- package/capabilities.json +238 -0
- package/package.json +38 -4
- package/skills/infinite-canvas-video/SKILL.md +62 -0
- package/skills/infinite-canvas-video/adapters/codex.json +24 -0
- package/skills/infinite-canvas-video/adapters/generic.json +24 -0
- package/skills/infinite-canvas-video/agents/openai.yaml +4 -0
- package/skills/infinite-canvas-video/capabilities.json +52 -0
- package/skills/infinite-canvas-video/references/runtime.md +38 -0
- package/src/browser-runtime.mjs +105 -0
- package/src/browser.mjs +130 -0
- package/src/contracts.mjs +60 -0
- package/src/core.mjs +52 -0
- package/src/frame.mjs +35 -0
- package/src/lifecycle.mjs +80 -0
- package/src/media-cli.mjs +13 -0
- package/src/state.mjs +47 -0
- package/src/sync-contract.mjs +13 -0
- package/src/workflow.mjs +110 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Peter CJL
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Infinite Canvas CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`@petercjl/infinite-canvas-cli` uses an existing SealSeek Infinite Canvas board to generate videos through its native Agent conversation. It imports ordered reference images, sends the supplied prompt in a fresh conversation, downloads the video, and groups the original image nodes and video node in a Frame.
|
|
4
|
+
|
|
5
|
+
This package requires Node.js 22+, installed Google Chrome on macOS or Windows and an installed `sealseek-media` CLI supporting image upload and canvas get/add. The package discovers and launches Chrome itself on loopback port 9223 with a persistent current-user dedicated profile. Browser login is completed in that window. The website supplies the selected model and authenticated account. Each authorized generation can incur charges.
|
|
6
|
+
|
|
7
|
+
Each board file uses exactly one browser tab. Concurrent videos run in separate Agent conversations within that tab; UI submissions and canvas writes are queued while remote generation runs in parallel. Duplicate tabs must be resolved before submission or saving.
|
|
8
|
+
|
|
9
|
+
`doctor` distinguishes Chrome installation from runtime state. An installed executable passes the browser dependency check while Chrome is stopped. `browser open` launches or reuses the dedicated instance; video execution prepares it automatically. A port/profile conflict is reported explicitly. Chrome outside the standard paths can be selected with `INFINITE_CANVAS_CHROME_BIN`.
|
|
10
|
+
|
|
11
|
+
## Agent usage
|
|
12
|
+
|
|
13
|
+
Provide the board ID, prompt and reference image attachments in their intended order. Send the Agent:
|
|
14
|
+
|
|
15
|
+
> 使用 infinite-canvas,画板 ID 为「画板ID」,按所附图片顺序作为图1、图2,生成视频:「完整提示词」。
|
|
16
|
+
|
|
17
|
+
To install:
|
|
18
|
+
|
|
19
|
+
> 安装 @petercjl/infinite-canvas-cli 及其配套 Skill。
|
|
20
|
+
|
|
21
|
+
To update:
|
|
22
|
+
|
|
23
|
+
> 更新 @petercjl/infinite-canvas-cli 及其配套 Skill。
|
|
24
|
+
|
|
25
|
+
To recover:
|
|
26
|
+
|
|
27
|
+
> 使用 infinite-canvas,恢复任务「任务ID」。
|
|
28
|
+
|
|
29
|
+
To synchronize the current Agent’s Skill:
|
|
30
|
+
|
|
31
|
+
> 将 infinite-canvas 当前安装包的配套 Skill 同步到当前 Agent。
|
|
32
|
+
|
|
33
|
+
Codex/macOS has completed six live video tasks across two boards. Other Agent adapters require runtime verification.
|
|
34
|
+
|
|
35
|
+
## Developer entry points
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install
|
|
39
|
+
npm test
|
|
40
|
+
node bin/infinite-canvas.mjs --help
|
|
41
|
+
node bin/infinite-canvas.mjs doctor --json
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
See the bundled [Skill](skills/infinite-canvas-video/SKILL.md) and [runtime commands](skills/infinite-canvas-video/references/runtime.md). `capabilities --json` exposes machine-readable contracts. Video generation defaults to dry run; `--submit` starts a background job and returns its task ID. Resume never clicks Send again after a submission intent has been recorded.
|
|
45
|
+
|
|
46
|
+
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.
|
|
47
|
+
|
|
48
|
+
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.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import {META,ROOT,need,parse,readJSON,execute,stateRoot,newJSON,redact,safeId} from '../src/core.mjs';
|
|
6
|
+
import {manifest,HELP} from '../src/contracts.mjs';
|
|
7
|
+
import {automaticCheck,checkUpdate,installUpdate,updatePolicy,SKILL,skillStatus,installSkill} from '../src/lifecycle.mjs';
|
|
8
|
+
|
|
9
|
+
const out=v=>console.log(JSON.stringify(v,null,2));
|
|
10
|
+
async function main(argv){
|
|
11
|
+
const [command,action,...rest]=argv;
|
|
12
|
+
if(!command||['help','--help','-h'].includes(command))return console.log(HELP);
|
|
13
|
+
need(Number(process.versions.node.split('.')[0])>=22,'CAPABILITY_UNAVAILABLE','Node.js 22 or newer is required.');
|
|
14
|
+
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
|
+
if(argv.includes('--help')||argv.includes('-h')){need(['video','task','frame','browser','board','skill','update','doctor','capabilities'].includes(command),'UNKNOWN_COMMAND','Unknown command.');return console.log(HELP);}
|
|
16
|
+
if(command==='_worker'){need(argv.length===2,'INVALID_ARGUMENT','Invalid worker invocation.');const {processJob}=await import('../src/workflow.mjs');return out(await processJob(action));}
|
|
17
|
+
if(command==='skill'){
|
|
18
|
+
const {options:o,args}=parse(rest,{values:['agent','path'],booleans:['json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');
|
|
19
|
+
if(action==='source')return out({ok:true,source:SKILL,name:'infinite-canvas-video',version:META.version,installation:{named_agents:['codex'],fallback_agent:'generic',custom_path_required:true,scope:'selected Agent only'}});
|
|
20
|
+
if(action==='status')return out(await skillStatus(o.agent||'codex',o.path));
|
|
21
|
+
need(['install','update'].includes(action),'UNKNOWN_COMMAND','Use skill source/status/install/update.');return out(await installSkill(o.agent||'codex',o.path));
|
|
22
|
+
}
|
|
23
|
+
if(command==='update'){
|
|
24
|
+
if(action==='auto'){const [mode,...flags]=rest;const {args}=parse(flags,{booleans:['json']});need(['on','off','status'].includes(mode)&&!args.length,'INVALID_ARGUMENT','Use update auto on/off/status.');return out(await updatePolicy(mode));}
|
|
25
|
+
const {options:o,args}=parse(rest,{values:['agent','path'],booleans:['yes','json']});need(!args.length,'INVALID_ARGUMENT','Unexpected arguments.');
|
|
26
|
+
if(action==='check')return out({ok:true,...await checkUpdate({force:true})});
|
|
27
|
+
need(action==='install','UNKNOWN_COMMAND','Use update check/install.');return out(await installUpdate({yes:o.yes,agent:o.agent,destination:o.path}));
|
|
28
|
+
}
|
|
29
|
+
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});}
|
|
30
|
+
if(command==='doctor'){
|
|
31
|
+
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};
|
|
32
|
+
for(const library of ['playwright-core','image-size']){try{await import(library);checks[library]={ok:true};}catch{checks[library]={ok:false};}}
|
|
33
|
+
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};}
|
|
34
|
+
const {ChromeRuntime}=await import('../src/browser-runtime.mjs');const runtime=new ChromeRuntime();
|
|
35
|
+
try{checks.browser=await runtime.configuredLauncher('status')||await runtime.status();}catch(e){checks.browser={ok:false,error:redact(e)};}
|
|
36
|
+
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();}}
|
|
37
|
+
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'});
|
|
38
|
+
}
|
|
39
|
+
if(command==='browser'){
|
|
40
|
+
const {args}=parse(rest,{booleans:['json']});need(!args.length&&['status','open'].includes(action),'INVALID_ARGUMENT','Use browser status/open --json.');
|
|
41
|
+
const {ChromeRuntime}=await import('../src/browser-runtime.mjs');const runtime=new ChromeRuntime();
|
|
42
|
+
const result=action==='open'?await runtime.open():await runtime.configuredLauncher('status')||await runtime.status();if(!result.ok)process.exitCode=1;return out(result);
|
|
43
|
+
}
|
|
44
|
+
if(command==='board'&&action==='inspect'){const {options:o,args}=parse(rest,{values:['canvas'],booleans:['json']});need(!args.length&&o.canvas,'INVALID_ARGUMENT','Provide --canvas ID.');const {MediaCLI}=await import('../src/media-cli.mjs');return out(await new MediaCLI().get(o.canvas));}
|
|
45
|
+
if(command==='video'&&action==='generate'){
|
|
46
|
+
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.');
|
|
47
|
+
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();
|
|
48
|
+
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'});
|
|
49
|
+
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});
|
|
50
|
+
}
|
|
51
|
+
if(command==='task'){
|
|
52
|
+
const {options:o,args}=parse(rest,{values:['timeout'],booleans:['json']});need(args.length===1,'INVALID_ARGUMENT','Provide one task ID.');const {getJob,summary}=await import('../src/state.mjs');
|
|
53
|
+
if(action==='get'){need(!o.timeout,'INVALID_ARGUMENT','timeout applies only to wait.');return out(summary(await getJob(args[0])));}
|
|
54
|
+
const {resumeJob,waitJob,syncJob}=await import('../src/workflow.mjs');if(['resume','sync'].includes(action)){need(!o.timeout,'INVALID_ARGUMENT','timeout applies only to wait.');return out(await (action==='sync'?syncJob:resumeJob)(args[0]));}need(action==='wait','UNKNOWN_COMMAND','Use task get/wait/resume/sync.');return out(await waitJob(args[0],Number(o.timeout||30)));
|
|
55
|
+
}
|
|
56
|
+
if(command==='frame'&&action==='group'){
|
|
57
|
+
const {options:o,args}=parse(rest,{values:['canvas','elements','frame-id','name'],booleans:['submit','json']});need(!args.length&&o.canvas&&o.elements&&o['frame-id'],'INVALID_ARGUMENT','Provide canvas, elements JSON file, and frame-id.');safeId(o.canvas,'canvas ID');safeId(o['frame-id'],'Frame ID');const ids=await readJSON(o.elements);need(Array.isArray(ids)&&ids.length>0&&ids.length<=30&&new Set(ids).size===ids.length,'INVALID_INPUT','Provide unique element IDs.');ids.forEach(id=>safeId(id,'element ID'));await automaticCheck();
|
|
58
|
+
const {BrowserDriver}=await import('../src/browser.mjs');const {groupIntoFrame}=await import('../src/frame.mjs');const driver=new BrowserDriver();try{await driver.connect(o.canvas);const group=content=>groupIntoFrame(content,{ids,frameId:o['frame-id'],name:o.name});if(!o.submit){const board=await driver.getBoard();const preview=group(board.content);delete preview.content;return out({ok:true,dry_run:true,...preview});}const directory=path.join(stateRoot(),'frame-operations',crypto.randomUUID());return out({ok:true,...await driver.saveFrame(group,directory),content:undefined});}finally{await driver.close();}
|
|
59
|
+
}
|
|
60
|
+
need(false,'UNKNOWN_COMMAND','Unknown command. Use --help.');
|
|
61
|
+
}
|
|
62
|
+
main(process.argv.slice(2)).catch(e=>{out({ok:false,error:redact(e)});process.exitCode=1;});
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "infinite-canvas-capabilities",
|
|
3
|
+
"schema_version": "1.0.0",
|
|
4
|
+
"package": "@petercjl/infinite-canvas-cli",
|
|
5
|
+
"version": "0.1.1",
|
|
6
|
+
"input": {
|
|
7
|
+
"required": [
|
|
8
|
+
"canvas_id",
|
|
9
|
+
"prompt",
|
|
10
|
+
"references"
|
|
11
|
+
],
|
|
12
|
+
"references": "ordered PNG/JPEG/WebP local files or HTTPS URLs"
|
|
13
|
+
},
|
|
14
|
+
"output": {
|
|
15
|
+
"terminal": "local MP4 + saved Frame containing original reference nodes and one video node",
|
|
16
|
+
"fields": [
|
|
17
|
+
"task_id",
|
|
18
|
+
"status",
|
|
19
|
+
"canvas_id",
|
|
20
|
+
"session_id",
|
|
21
|
+
"run_id",
|
|
22
|
+
"frame_id",
|
|
23
|
+
"reference_element_ids",
|
|
24
|
+
"video_element_id",
|
|
25
|
+
"artifacts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"operations": [
|
|
29
|
+
"browser.status",
|
|
30
|
+
"browser.open",
|
|
31
|
+
"board.inspect",
|
|
32
|
+
"video.generate.native-agent",
|
|
33
|
+
"task.inspect",
|
|
34
|
+
"task.wait",
|
|
35
|
+
"task.resume",
|
|
36
|
+
"task.sync",
|
|
37
|
+
"frame.group-existing"
|
|
38
|
+
],
|
|
39
|
+
"generationTransport": {
|
|
40
|
+
"kind": "native-browser-ui",
|
|
41
|
+
"newConversationPerLogicalVideo": true,
|
|
42
|
+
"singleTabPerCanvas": true,
|
|
43
|
+
"concurrency": "separate conversations in the same tab; queued UI submissions and canvas writes; parallel remote runs",
|
|
44
|
+
"submission": "click native Send",
|
|
45
|
+
"directGenerationAPI": false,
|
|
46
|
+
"recovery": "saved sessionId/runId; read-only run and history APIs",
|
|
47
|
+
"model": "inherit webpage settings"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"node": ">=22",
|
|
51
|
+
"browser": "Installed Google Chrome; built-in macOS/Windows discovery and startup on loopback 9223 with current-user dedicated profile",
|
|
52
|
+
"canvasAPI": "sealseek-media >=0.6.7, public CLI commands",
|
|
53
|
+
"libraries": [
|
|
54
|
+
"playwright-core",
|
|
55
|
+
"image-size"
|
|
56
|
+
],
|
|
57
|
+
"optional": [
|
|
58
|
+
"ffprobe"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"platforms": {
|
|
62
|
+
"codex:macos": "tested: six native Agent video tasks across two boards; one tab per board; downloads, deduplication and Frame readback verified",
|
|
63
|
+
"generic:macos": "spec-compatible, runtime untested",
|
|
64
|
+
"sealseek:macos": "spec-compatible, runtime untested",
|
|
65
|
+
"generic:windows": "built-in Chrome discovery/startup implemented; actual host video end-to-end verification pending"
|
|
66
|
+
},
|
|
67
|
+
"commandContracts": {
|
|
68
|
+
"video generate": {
|
|
69
|
+
"options": {
|
|
70
|
+
"--canvas": {
|
|
71
|
+
"type": "string"
|
|
72
|
+
},
|
|
73
|
+
"--prompt": {
|
|
74
|
+
"type": "string"
|
|
75
|
+
},
|
|
76
|
+
"--prompt-file": {
|
|
77
|
+
"type": "string"
|
|
78
|
+
},
|
|
79
|
+
"--reference": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"repeatable": true
|
|
82
|
+
},
|
|
83
|
+
"--output": {
|
|
84
|
+
"type": "string"
|
|
85
|
+
},
|
|
86
|
+
"--request-id": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"--new": {
|
|
90
|
+
"type": "boolean"
|
|
91
|
+
},
|
|
92
|
+
"--submit": {
|
|
93
|
+
"type": "boolean"
|
|
94
|
+
},
|
|
95
|
+
"--dry-run": {
|
|
96
|
+
"type": "boolean"
|
|
97
|
+
},
|
|
98
|
+
"--json": {
|
|
99
|
+
"type": "boolean"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"sideEffects": "local-write",
|
|
103
|
+
"externalMutation": "With --submit: import references, send native Agent conversation, import a missing video, group selected elements in a Frame.",
|
|
104
|
+
"paid": "Only native webpage sending may charge credits."
|
|
105
|
+
},
|
|
106
|
+
"task get|wait|resume|sync": {
|
|
107
|
+
"options": {
|
|
108
|
+
"--timeout": {
|
|
109
|
+
"type": "string"
|
|
110
|
+
},
|
|
111
|
+
"--json": {
|
|
112
|
+
"type": "boolean"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"sideEffects": "local-write",
|
|
116
|
+
"externalMutation": "resume continues original task; sync reapplies existing video archival and grouping; never a second generation."
|
|
117
|
+
},
|
|
118
|
+
"frame group": {
|
|
119
|
+
"options": {
|
|
120
|
+
"--canvas": {
|
|
121
|
+
"type": "string"
|
|
122
|
+
},
|
|
123
|
+
"--elements": {
|
|
124
|
+
"type": "string"
|
|
125
|
+
},
|
|
126
|
+
"--frame-id": {
|
|
127
|
+
"type": "string"
|
|
128
|
+
},
|
|
129
|
+
"--name": {
|
|
130
|
+
"type": "string"
|
|
131
|
+
},
|
|
132
|
+
"--submit": {
|
|
133
|
+
"type": "boolean"
|
|
134
|
+
},
|
|
135
|
+
"--json": {
|
|
136
|
+
"type": "boolean"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
"sideEffects": "local-write",
|
|
140
|
+
"externalMutation": "With --submit: reparent existing nodes and preserve their identities."
|
|
141
|
+
},
|
|
142
|
+
"browser status": {
|
|
143
|
+
"options": {
|
|
144
|
+
"--json": {
|
|
145
|
+
"type": "boolean"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"sideEffects": "none",
|
|
149
|
+
"output": "Chrome availability and separate CDP runtime readiness"
|
|
150
|
+
},
|
|
151
|
+
"browser open": {
|
|
152
|
+
"options": {
|
|
153
|
+
"--json": {
|
|
154
|
+
"type": "boolean"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
"sideEffects": "local-write",
|
|
158
|
+
"externalMutation": "Launch or reuse system Chrome on loopback 9223 with the current-user dedicated profile; no generation."
|
|
159
|
+
},
|
|
160
|
+
"doctor": {
|
|
161
|
+
"options": {
|
|
162
|
+
"--canvas": {
|
|
163
|
+
"type": "string"
|
|
164
|
+
},
|
|
165
|
+
"--live": {
|
|
166
|
+
"type": "boolean"
|
|
167
|
+
},
|
|
168
|
+
"--json": {
|
|
169
|
+
"type": "boolean"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"sideEffects": "none"
|
|
173
|
+
},
|
|
174
|
+
"skill source": {
|
|
175
|
+
"options": {
|
|
176
|
+
"--json": {
|
|
177
|
+
"type": "boolean"
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
"sideEffects": "none"
|
|
181
|
+
},
|
|
182
|
+
"skill status|install|update": {
|
|
183
|
+
"options": {
|
|
184
|
+
"--agent": {
|
|
185
|
+
"type": "string"
|
|
186
|
+
},
|
|
187
|
+
"--path": {
|
|
188
|
+
"type": "string"
|
|
189
|
+
},
|
|
190
|
+
"--json": {
|
|
191
|
+
"type": "boolean"
|
|
192
|
+
}
|
|
193
|
+
},
|
|
194
|
+
"sideEffects": "local-write"
|
|
195
|
+
},
|
|
196
|
+
"update check|install": {
|
|
197
|
+
"options": {
|
|
198
|
+
"--yes": {
|
|
199
|
+
"type": "boolean"
|
|
200
|
+
},
|
|
201
|
+
"--agent": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
},
|
|
204
|
+
"--path": {
|
|
205
|
+
"type": "string"
|
|
206
|
+
},
|
|
207
|
+
"--json": {
|
|
208
|
+
"type": "boolean"
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"sideEffects": "package-and-skill-update"
|
|
212
|
+
},
|
|
213
|
+
"update auto": {
|
|
214
|
+
"options": {
|
|
215
|
+
"--json": {
|
|
216
|
+
"type": "boolean"
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
"sideEffects": "local-write"
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"automaticUpdates": {
|
|
223
|
+
"enabledByDefault": true,
|
|
224
|
+
"defaultMode": "notify",
|
|
225
|
+
"intervalHours": 24,
|
|
226
|
+
"timeoutMs": 1500,
|
|
227
|
+
"trigger": "ordinary video/frame/doctor/capabilities entry; excludes help/version/skill/task polling",
|
|
228
|
+
"configuration": "private update-policy.json; update auto on|off|status",
|
|
229
|
+
"disable": "INFINITE_CANVAS_AUTO_UPDATE=0",
|
|
230
|
+
"recovery": "offline backoff preserves installation; source checkout updates through Git; explicit install keeps a package backup",
|
|
231
|
+
"installation": "explicit --yes only; no automatic installation"
|
|
232
|
+
},
|
|
233
|
+
"limitations": [
|
|
234
|
+
"No service-side atomic canvas revision check; stop on detected user edits.",
|
|
235
|
+
"Frame grouping first version supports unrotated image/video nodes.",
|
|
236
|
+
"Do not submit another generation after unknown submission."
|
|
237
|
+
]
|
|
238
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@petercjl/infinite-canvas-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Drive native Infinite Canvas Agent conversations and organize reference images and results in a Frame.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"infinite-canvas": "bin/infinite-canvas.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"skills",
|
|
13
|
+
"capabilities.json",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test",
|
|
26
|
+
"check": "node --check bin/infinite-canvas.mjs && node --test"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"playwright-core": "1.62.1",
|
|
30
|
+
"image-size": "2.0.4"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/petercjl/infinite-canvas-cli.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/petercjl/infinite-canvas-cli/issues"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/petercjl/infinite-canvas-cli#readme"
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: infinite-canvas-video
|
|
3
|
+
description: "Use infinite-canvas to generate a video through an existing SealSeek Infinite Canvas Agent conversation UI, using a board ID, an unchanged user prompt, and ordered reference images; retrieve the video and group original image and video nodes in a Frame. Use when the user explicitly selects this canvas workflow."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Infinite Canvas Video
|
|
7
|
+
|
|
8
|
+
Execute an explicitly selected canvas video request with the installed `infinite-canvas` capability. The application or user owns creative choices, prompt, reference selection/order and acceptance. The CLI owns native submission, authentication reuse, canvas writes, idempotency and recovery.
|
|
9
|
+
|
|
10
|
+
## Input and output
|
|
11
|
+
|
|
12
|
+
Require a board file ID, 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. Inherit model settings from the webpage. An authorized generation request allows one paid native Agent submission. A planning or dry-run request allows validation only.
|
|
13
|
+
|
|
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
|
+
|
|
16
|
+
## Capability resolution
|
|
17
|
+
|
|
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
|
+
|
|
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.
|
|
21
|
+
|
|
22
|
+
## Main line
|
|
23
|
+
|
|
24
|
+
1. Validate required inputs and authorization. Run `doctor --json`: 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.
|
|
25
|
+
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.
|
|
26
|
+
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.
|
|
27
|
+
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.
|
|
28
|
+
5. On completion, inspect the returned artifact and report its local path. The worker deduplicates the generated video against the board and groups the already imported image nodes and video into one actual Frame. Use the same task ID for any unfinished download or grouping.
|
|
29
|
+
|
|
30
|
+
## Single-tab constraint and concurrency
|
|
31
|
+
|
|
32
|
+
**Each board file must be open in exactly one browser tab.** Multiple tabs editing the same board cause save conflicts and the website may suspend editing. Reuse that board's existing tab. A different board may have its own tab.
|
|
33
|
+
|
|
34
|
+
Run concurrent videos through separate native Agent conversations inside the board's single tab. Serialize the short UI operations: import references, create a fresh conversation, attach images, send once and capture session/run IDs. After submission, track each run independently while preparing another conversation in the same tab. Serialize canvas imports and Frame writes per board; remote generation waiting does not hold that queue. Keep the requested board file count unchanged when adding concurrent video tasks.
|
|
35
|
+
|
|
36
|
+
Start one `video generate --submit` task per requested video, record every returned ID, and poll all tasks together. Submit the remaining tasks while earlier runs generate. The CLI queues tab selection and composition per board, including simultaneous first use. Use the installed CLI's verified selectors and transport; ordinary runs require no new selector or endpoint exploration.
|
|
37
|
+
|
|
38
|
+
Conversation readiness means native session creation has completed before attaching references. Attachment readiness counts only the current composer and waits for uploaded HTTPS images. Other conversations may still stream during these steps. After canvas API writes, the CLI synchronizes the editor in place and verifies saved/live scene agreement. Page reload is excluded from this write path because the old scene's auto-save can replace newly imported nodes or Frames. Webpage contract changes return an adapter error for a separate repair cycle.
|
|
39
|
+
|
|
40
|
+
If duplicate tabs for a board are detected, stop new submissions and writes with `BOARD_SELECTION_REQUIRED`. Preserve all task/session/run IDs, resolve the duplicate tabs, then resume existing tasks. When the website displays its other-tab editing protection, restore editing in the single retained tab after duplicates are closed. Claim resumed generation only from the existing run; a tab conflict never authorizes another paid submission.
|
|
41
|
+
|
|
42
|
+
## Branches and recovery
|
|
43
|
+
|
|
44
|
+
- `CHROME_NOT_FOUND`: install Google Chrome or set `INFINITE_CANVAS_CHROME_BIN` to its actual executable; return to step 1.
|
|
45
|
+
- `BROWSER_PORT_CONFLICT` / `BROWSER_OWNERSHIP_UNVERIFIED`: preserve existing browser processes/profile files, resolve the 9223 ownership conflict, then return to step 1.
|
|
46
|
+
- `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.
|
|
47
|
+
- `waiting`: the monitoring window or a connection ended; use `task resume TASK_ID --json`, then return to step 4. Remote runs continue independently.
|
|
48
|
+
- `uncertain` / `SUBMISSION_UNCERTAIN`: preserve the task; resume inspects the captured session for exactly one run. If identity cannot be resolved, report the uncertainty and request inspection. Never start another paid task as an automatic retry.
|
|
49
|
+
- `canvas_pending`: return any downloaded video as a partial result, then resume the same task for import/grouping. It does not resubmit generation.
|
|
50
|
+
- For an explicitly requested repair of canvas archival after a completed task, use `task sync TASK_ID --json`. It reapplies the existing video's import/grouping with the original reference nodes and never generates another video. Preserve intentional user deletions unless restoration is authorized.
|
|
51
|
+
- `UI_LOCATOR_CHANGED`, `AGENT_MODE_REQUIRED`, `REFERENCE_ORDER_CHANGED`: stop and report the adapter issue. If sending may have happened, preserve the task and inspect its history before further action.
|
|
52
|
+
- `CANVAS_UNSAVED`, `CANVAS_CHANGED`, `UI_BUSY`: ask the user to finish active work and save; resume the same task after the board is settled.
|
|
53
|
+
- `PROVIDER_FAILURE`: report the existing run's failure. Another video attempt is a new paid action.
|
|
54
|
+
- File conflicts or unmanaged Skill/state: preserve existing files and report the exact conflict.
|
|
55
|
+
|
|
56
|
+
Read [runtime reference](references/runtime.md) only when command details or recovery diagnostics are needed, then return to the relevant main-line step. Do not infer completion from chat prose alone.
|
|
57
|
+
|
|
58
|
+
## QA and evolution
|
|
59
|
+
|
|
60
|
+
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. Report unavailable reference metadata or absent ffprobe verification 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.
|
|
61
|
+
|
|
62
|
+
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.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "portable-skill-adapter",
|
|
3
|
+
"platform": "codex",
|
|
4
|
+
"mappings": [
|
|
5
|
+
{
|
|
6
|
+
"capability_id": "canvas.video.execute",
|
|
7
|
+
"status": "implemented",
|
|
8
|
+
"features": [
|
|
9
|
+
"native-agent-send",
|
|
10
|
+
"new-conversation",
|
|
11
|
+
"ordered-reference-images",
|
|
12
|
+
"canvas-import",
|
|
13
|
+
"existing-node-frame-group",
|
|
14
|
+
"task-recovery",
|
|
15
|
+
"local-video-output"
|
|
16
|
+
],
|
|
17
|
+
"implementation": {
|
|
18
|
+
"kind": "cli",
|
|
19
|
+
"command": "infinite-canvas",
|
|
20
|
+
"instructions": "Resolve from PATH. Run version, capabilities and doctor. Requires Node.js >=22, installed Google Chrome and sealseek-media; Chrome discovery/startup is bundled for macOS and Windows. Use structured JSON. Codex/macOS end-to-end verified with six native Agent videos across two boards, one tab per board, downloads and saved Frame readback. Other hosts require their own runtime validation."
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "portable-skill-adapter",
|
|
3
|
+
"platform": "generic",
|
|
4
|
+
"mappings": [
|
|
5
|
+
{
|
|
6
|
+
"capability_id": "canvas.video.execute",
|
|
7
|
+
"status": "implemented",
|
|
8
|
+
"features": [
|
|
9
|
+
"native-agent-send",
|
|
10
|
+
"new-conversation",
|
|
11
|
+
"ordered-reference-images",
|
|
12
|
+
"canvas-import",
|
|
13
|
+
"existing-node-frame-group",
|
|
14
|
+
"task-recovery",
|
|
15
|
+
"local-video-output"
|
|
16
|
+
],
|
|
17
|
+
"implementation": {
|
|
18
|
+
"kind": "cli",
|
|
19
|
+
"command": "infinite-canvas",
|
|
20
|
+
"instructions": "Resolve from PATH. Run version, capabilities and doctor. Requires Node.js >=22, installed Google Chrome and sealseek-media; Chrome discovery/startup is bundled for macOS and Windows. Use structured JSON. Named-host runtime end-to-end verification is pending."
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "portable-skill-capabilities",
|
|
3
|
+
"schema_version": "1.0.0",
|
|
4
|
+
"skill": "infinite-canvas-video",
|
|
5
|
+
"target_platforms": [
|
|
6
|
+
"codex",
|
|
7
|
+
"generic"
|
|
8
|
+
],
|
|
9
|
+
"host_resolution": {
|
|
10
|
+
"fallback_adapter": "generic"
|
|
11
|
+
},
|
|
12
|
+
"capabilities": [
|
|
13
|
+
{
|
|
14
|
+
"id": "canvas.video.execute",
|
|
15
|
+
"purpose": "Execute native Agent video generation and verified canvas grouping",
|
|
16
|
+
"required": true,
|
|
17
|
+
"required_features": [
|
|
18
|
+
"native-agent-send",
|
|
19
|
+
"new-conversation",
|
|
20
|
+
"ordered-reference-images",
|
|
21
|
+
"canvas-import",
|
|
22
|
+
"existing-node-frame-group",
|
|
23
|
+
"task-recovery",
|
|
24
|
+
"local-video-output"
|
|
25
|
+
],
|
|
26
|
+
"input_fields": [
|
|
27
|
+
"canvas_id",
|
|
28
|
+
"prompt",
|
|
29
|
+
"references"
|
|
30
|
+
],
|
|
31
|
+
"output_fields": [
|
|
32
|
+
"task_id",
|
|
33
|
+
"status",
|
|
34
|
+
"session_id",
|
|
35
|
+
"run_id",
|
|
36
|
+
"frame_id",
|
|
37
|
+
"artifacts"
|
|
38
|
+
],
|
|
39
|
+
"side_effects": {
|
|
40
|
+
"external_mutation": true,
|
|
41
|
+
"may_cost_money": true,
|
|
42
|
+
"authorization": "An authorized video request permits one native generation; resume never resubmits."
|
|
43
|
+
},
|
|
44
|
+
"errors": [
|
|
45
|
+
"CAPABILITY_UNAVAILABLE",
|
|
46
|
+
"SUBMISSION_UNCERTAIN",
|
|
47
|
+
"CANVAS_SYNC_PENDING",
|
|
48
|
+
"OUTPUT_CONTRACT_FAILED"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Runtime command reference
|
|
2
|
+
|
|
3
|
+
These commands are for the executing Agent and developers.
|
|
4
|
+
|
|
5
|
+
Browser invariant: one board file, one tab. Reuse the existing tab for that canvas ID. Concurrent tasks use distinct native conversations and captured session/run IDs within that tab. `BOARD_SELECTION_REQUIRED` means resolve duplicate tabs before continuing; preserve the existing tasks. UI submission and canvas writes are queued per board, while remote runs can progress concurrently.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
infinite-canvas version --json
|
|
9
|
+
infinite-canvas capabilities --json
|
|
10
|
+
infinite-canvas doctor --json
|
|
11
|
+
infinite-canvas browser status --json
|
|
12
|
+
infinite-canvas browser open --json
|
|
13
|
+
infinite-canvas skill source --json
|
|
14
|
+
infinite-canvas skill status --agent AGENT_NAME --path RESOLVED_SKILL_DIR --json
|
|
15
|
+
infinite-canvas skill install --agent AGENT_NAME --path RESOLVED_SKILL_DIR --json
|
|
16
|
+
infinite-canvas skill update --agent AGENT_NAME --path RESOLVED_SKILL_DIR --json
|
|
17
|
+
infinite-canvas video generate --canvas BOARD_ID --prompt-file PROMPT_FILE --reference IMAGE_1 --reference IMAGE_2 --dry-run --json
|
|
18
|
+
infinite-canvas video generate --canvas BOARD_ID --prompt-file PROMPT_FILE --reference IMAGE_1 --reference IMAGE_2 --submit --json
|
|
19
|
+
infinite-canvas task get TASK_ID --json
|
|
20
|
+
infinite-canvas task wait TASK_ID --timeout 30 --json
|
|
21
|
+
infinite-canvas task resume TASK_ID --json
|
|
22
|
+
infinite-canvas task sync TASK_ID --json
|
|
23
|
+
infinite-canvas update auto off --json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`--output` is an output directory; the MP4 filename is the unique task ID. Existing different bytes are preserved. HTTPS reference downloads are bounded to 30 MiB, local images are validated, and redirecting media URLs are rejected. An interrupted task is resumed with its original inputs and captured run.
|
|
27
|
+
|
|
28
|
+
`board inspect --canvas ID --json` reads the board. `frame group --canvas ID --elements ELEMENT_IDS_JSON --frame-id FRAME_ID --json` previews grouping; `--submit` saves it. Only the selected existing unrotated image/video nodes are moved into the actual Frame. Layout uses a horizontal row with bounded media dimensions. Existing unrelated elements are preserved; conflicting Frame membership stops the operation.
|
|
29
|
+
|
|
30
|
+
State resides under the user's application-data directory (`INFINITE_CANVAS_STATE_DIR` can select another private location). It contains task state, reference copies, readback backups, download metadata and worker logs. Preserve it for recovery; treat media URLs and prompts as private. `INFINITE_CANVAS_AUTO_UPDATE=0` disables automatic registry detection. Explicit update installation requires `update install --yes`, refuses source checkouts and backs up the installed package. Unpublished development versions have no registry update available.
|
|
31
|
+
|
|
32
|
+
The CLI never prints browser credentials. Changing source, a live webpage deployment or a helper CLI version may require revalidation. JSON failures include `error.code` and `error.message`; keep the original task ID in user-facing recovery advice.
|
|
33
|
+
|
|
34
|
+
## Built-in Chrome runtime
|
|
35
|
+
|
|
36
|
+
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.
|
|
37
|
+
|
|
38
|
+
`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.
|