@petercjl/ecom-cli 0.1.3 → 0.2.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ecom CLI
2
2
 
3
- Agent CLI and portable Skill for the Ecom desktop browser. The first release lists the names and stable IDs of existing containers without opening websites or switching tabs. Ecom must already be running in normal desktop mode. The browser transport currently supports macOS; Windows browser integration is not implemented.
3
+ Agent CLI and portable Skill for the Ecom desktop browser. List existing containers, inspect a selected account’s platform/homepage/login status, and perform explicitly authorized HTTPS JSON GET operations through that container’s native session, preserving the active tab. Ecom must already be running in normal desktop mode. The browser transport currently supports macOS; Windows browser integration is not implemented.
4
4
 
5
5
  ## Install
6
6
 
@@ -14,6 +14,12 @@ The Agent needs local Node.js 22+ and terminal/file access. Installation include
14
14
 
15
15
  > 用 Ecom CLI 列出我当前的浏览器容器。
16
16
 
17
+ > 用 Ecom CLI 查看指定账号的平台、首页和登录核验状态。
18
+
19
+ > 用 Ecom CLI,通过指定账号请求我提供的只读接口地址。
20
+
21
+ Provide the account name or stable container ID. For requests, supply an authorized read-only HTTPS endpoint or a trusted registered business operation. The browser permits only its explicitly enabled origin, rejects redirects/HTML and unsupported methods, and retains credentials in its native session. Native account readiness and identity checks remain authoritative. An HTTP 200 JSON response is transport success; the caller evaluates business success.
22
+
17
23
  Container names may identify accounts. Use a trusted Agent and share results intentionally. Installing the plugin does not authorize website operations, cookie export or account modification.
18
24
 
19
25
  ## Update
package/bin/ecom-cli.mjs CHANGED
@@ -4,6 +4,6 @@ try {
4
4
  const result = await main(process.argv.slice(2));
5
5
  process.stdout.write(JSON.stringify(result) + '\n');
6
6
  } catch (error) {
7
- process.stdout.write(JSON.stringify({ok:false,error:{code:error.code || 'CLI_ERROR',message:error.code ? error.message : 'Operation failed; inspect doctor and operation status',executed:error.executed ?? false,...(error.operationId ? {operationId:error.operationId} : {}),...(error.recovery ? {recovery:error.recovery} : {})}}) + '\n');
7
+ process.stdout.write(JSON.stringify({ok:false,error:{code:error.code || 'CLI_ERROR',message:error.code ? error.message : 'Operation failed; inspect doctor and operation status',executed:error.executed ?? false,...(error.operationId ? {operationId:error.operationId} : {}),...(error.httpStatus!==undefined?{httpStatus:error.httpStatus}:{}),...(error.recovery ? {recovery:error.recovery} : {})}}) + '\n');
8
8
  process.exitCode = error.code === 'INVALID_ARGUMENT' ? 2 : 1;
9
9
  }
package/capabilities.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@petercjl/ecom-cli",
4
- "version": "0.1.3",
4
+ "version": "0.2.0",
5
5
  "skills": [
6
6
  "ecom-cli"
7
7
  ],
@@ -159,6 +159,98 @@
159
159
  }
160
160
  },
161
161
  "sideEffects": "package-and-skill-update"
162
+ },
163
+ "containers inspect": {
164
+ "options": {
165
+ "--json": {
166
+ "type": "boolean"
167
+ },
168
+ "--profile": {
169
+ "type": "string"
170
+ },
171
+ "--instance": {
172
+ "type": "string"
173
+ },
174
+ "--container": {
175
+ "type": "string",
176
+ "required": true,
177
+ "description": "Stable container ID or unique exact name"
178
+ }
179
+ },
180
+ "sideEffects": "local-write",
181
+ "authorization": "Read selected account metadata; credentials excluded and homepage credential fields redacted. Automatic update detection only.",
182
+ "output": {
183
+ "container": "id, name, platform, platformName, homeUrl, icon, color, login",
184
+ "login": "state, service, authenticationState, verificationSupported, checking, checkedAt, identityBound, identityConfirmed, expiry"
185
+ },
186
+ "errors": [
187
+ "CONTAINER_NOT_FOUND",
188
+ "CONTAINER_AMBIGUOUS",
189
+ "PROTOCOL_MISMATCH"
190
+ ]
191
+ },
192
+ "session request": {
193
+ "options": {
194
+ "--json": {
195
+ "type": "boolean"
196
+ },
197
+ "--profile": {
198
+ "type": "string"
199
+ },
200
+ "--instance": {
201
+ "type": "string"
202
+ },
203
+ "--container": {
204
+ "type": "string",
205
+ "required": true,
206
+ "description": "Stable container ID or unique exact name"
207
+ },
208
+ "--url": {
209
+ "type": "string",
210
+ "required": true,
211
+ "description": "HTTPS URL in the origin explicitly enabled by Ecom; no credentials or fragment"
212
+ },
213
+ "--method": {
214
+ "type": "string",
215
+ "default": "GET",
216
+ "enum": [
217
+ "GET"
218
+ ]
219
+ }
220
+ },
221
+ "sideEffects": "local-write",
222
+ "authorization": "Explicit user-authorized read operation using selected persistent container; website credentials remain native. GET is a transport restriction, not proof of business read-only semantics.",
223
+ "output": {
224
+ "instance": "Pinned Ecom identity",
225
+ "container": "id and name",
226
+ "httpStatus": "200",
227
+ "data": "Native-redacted JSON response",
228
+ "businessStatus": "not-evaluated"
229
+ },
230
+ "limits": {
231
+ "clientTimeoutMs": 60000,
232
+ "nativeNetworkTimeoutMs": 20000,
233
+ "nativeResponseBytes": 2097152,
234
+ "redirects": "rejected",
235
+ "html": "rejected",
236
+ "concurrency": "native limit 2 requests"
237
+ },
238
+ "errors": [
239
+ "INVALID_URL",
240
+ "METHOD_UNSUPPORTED",
241
+ "CREDENTIAL_INPUT_REJECTED",
242
+ "ORIGIN_NOT_ALLOWED",
243
+ "CONTAINER_NOT_FOUND",
244
+ "CONTAINER_AMBIGUOUS",
245
+ "SHOP_NOT_READY",
246
+ "SHOP_VERIFICATION_FAILED",
247
+ "BUSY",
248
+ "REQUEST_FAILED",
249
+ "NON_JSON_RESPONSE",
250
+ "INSTANCE_CHANGED",
251
+ "PROTOCOL_MISMATCH",
252
+ "OPERATION_TIMEOUT"
253
+ ]
162
254
  }
163
255
  },
164
256
  "transport": {
@@ -317,7 +409,7 @@
317
409
  "enabledByDefault": true,
318
410
  "intervalHours": 24,
319
411
  "timeoutMs": 1500,
320
- "trigger": "containers list task entry",
412
+ "trigger": "containers list, containers inspect and session request task entry",
321
413
  "configuration": "Stable latest channel; detection only. Explicit update activates only the selected target runtime and Skill together.",
322
414
  "disable": "ECOM_CLI_UPDATE_CHECK=0",
323
415
  "recovery": "Offline checks retain installed runtime; local edit/ownership conflicts stop replacement; prior target kept as backup. Use recorded candidate and inspect target status before retrying.",
@@ -340,6 +432,17 @@
340
432
  "TARGET_REQUIRED",
341
433
  "LOCAL_EDITS",
342
434
  "UNMANAGED_TARGET",
343
- "TARGET_LOCKED"
435
+ "TARGET_LOCKED",
436
+ "INVALID_URL",
437
+ "METHOD_UNSUPPORTED",
438
+ "CREDENTIAL_INPUT_REJECTED",
439
+ "ORIGIN_NOT_ALLOWED",
440
+ "CONTAINER_NOT_FOUND",
441
+ "CONTAINER_AMBIGUOUS",
442
+ "SHOP_NOT_READY",
443
+ "SHOP_VERIFICATION_FAILED",
444
+ "BUSY",
445
+ "REQUEST_FAILED",
446
+ "NON_JSON_RESPONSE"
344
447
  ]
345
448
  }
package/lib/browser.mjs CHANGED
@@ -26,27 +26,85 @@ export function discover({profile,instance}={}) {
26
26
  if(candidates.length!==1) throw fail(candidates.length?'INSTANCE_AMBIGUOUS':'INSTANCE_NOT_RUNNING',candidates.length?'Multiple Ecom instances; specify --profile or --instance':'Open an Ecom build supporting this CLI in normal desktop mode');
27
27
  return candidates[0];
28
28
  }
29
+ const instanceOf=({endpoint,channel})=>({id:endpoint.instanceId,pid:endpoint.pid,channel});
30
+ const credentialKey=/token|cookie|csrf|password|authorization|secret|sessionid|api[-_]?key|access[-_]?key|credential/i;
31
+ function homeUrl(value) {
32
+ try {
33
+ const url=new URL(value);if(url.protocol!=='https:')return null;
34
+ url.username='';url.password='';
35
+ for(const [key,value]of url.searchParams)if(credentialKey.test(key)||credentialKey.test(value)&&/[=%]/.test(value))url.searchParams.set(key,'[REDACTED]');
36
+ if(credentialKey.test(url.hash))url.hash='[REDACTED]';
37
+ return url.href;
38
+ }catch{return null;}
39
+ }
40
+ export function resolveContainer(items,value) {
41
+ if(typeof value!=='string'||!value.trim())throw fail('INVALID_ARGUMENT','Specify --container with a stable ID or unique exact name');
42
+ const matches=items.filter(c=>c.id===value||c.name===value);
43
+ if(matches.length!==1)throw fail(matches.length?'CONTAINER_AMBIGUOUS':'CONTAINER_NOT_FOUND','Select an existing stable container ID or unique exact name');
44
+ return matches[0];
45
+ }
46
+ export function validateReadUrl(value,method='GET') {
47
+ if(method!=='GET')throw fail('METHOD_UNSUPPORTED','This version supports explicit read-only GET operations only');
48
+ let url;try{url=new URL(value);}catch{throw fail('INVALID_URL','Specify an absolute HTTPS URL');}
49
+ if(url.protocol!=='https:'||url.username||url.password||url.hash)throw fail('INVALID_URL','HTTPS URL must have no credentials or fragment');
50
+ for(const [key,value]of url.searchParams)if(credentialKey.test(key)||credentialKey.test(value)&&/[=%]/.test(value))throw fail('CREDENTIAL_INPUT_REJECTED','Credentials remain in the browser; credential query parameters are not accepted');
51
+ return url.href;
52
+ }
29
53
  export async function listContainers(options={}) {return readMailbox(discover(options));}
30
- export async function readMailbox({folder,endpoint,channel},timeoutMs=10000) {
54
+ export async function readMailbox(connection,timeoutMs=10000) {
55
+ const body=await callMailbox(connection,{method:'containers.list'},timeoutMs);
56
+ if(!Array.isArray(body.containers)||body.containers.some(c=>typeof c.id!=='string'||typeof c.name!=='string'))throw fail('PROTOCOL_MISMATCH','Invalid container list',{operationId:body.operationId,executed:'unknown'});
57
+ return {...body,instance:instanceOf(connection),containers:body.containers.map(({id,name})=>({id,name}))};
58
+ }
59
+ export async function inspectContainer(options={}) {
60
+ if(!options.container)throw fail('INVALID_ARGUMENT','containers inspect requires --container');
61
+ return inspectInConnection(discover(options),options.container);
62
+ }
63
+ export async function inspectInConnection(connection,selector) {
64
+ const body=await callMailbox(connection,{method:'shops.list'});
65
+ if(!Array.isArray(body.shops)||body.shops.some(c=>typeof c.id!=='string'||typeof c.name!=='string'))throw fail('PROTOCOL_MISMATCH','Invalid account metadata',{operationId:body.operationId,executed:'unknown'});
66
+ const row=resolveContainer(body.shops,selector);
67
+ const string=x=>typeof x==='string'?x:null;
68
+ const container={id:row.id,name:row.name,platform:string(row.platform),platformName:string(row.customPlatformName),homeUrl:homeUrl(row.homeUrl),icon:Number.isInteger(row.icon)?row.icon:null,color:string(row.color),
69
+ login:{state:string(row.state)??'unconfigured',service:string(row.authentication?.service),authenticationState:string(row.authentication?.state),verificationSupported:row.verificationSupported===true,checking:row.checking===true,checkedAt:string(row.checkedAt),identityBound:!!row.identity,identityConfirmed:row.state==='ready'&&!!row.identity,expiry:row.expiry?{kind:string(row.expiry.kind),cookieExpiresAt:string(row.expiry.cookieExpiresAt)}:null}};
70
+ return {ok:true,schemaVersion:1,operationId:body.operationId,instance:instanceOf(connection),container};
71
+ }
72
+ export async function requestSession(options={}) {
73
+ if(!options.container||!options.url)throw fail('INVALID_ARGUMENT','session request requires --container and --url');
74
+ const url=validateReadUrl(options.url,options.method??'GET');
75
+ return requestInConnection(discover(options),{...options,url});
76
+ }
77
+ export async function requestInConnection(connection,options) {
78
+ const url=validateReadUrl(options.url,options.method??'GET');
79
+ if(!connection.endpoint.allowedOrigin||new URL(url).origin!==connection.endpoint.allowedOrigin)throw fail('ORIGIN_NOT_ALLOWED','URL must match the HTTPS origin explicitly enabled by this Ecom instance');
80
+ const listed=await readMailbox(connection);
81
+ const container=resolveContainer(listed.containers,options.container);
82
+ const body=await callMailbox(connection,{method:'session.get',containerId:container.id,url},60000);
83
+ if(body.containerId!==container.id||body.httpStatus!==200||!Object.hasOwn(body,'data'))throw fail('PROTOCOL_MISMATCH','Session response does not match selected container or JSON response contract',{operationId:body.operationId,executed:'unknown'});
84
+ return {ok:true,schemaVersion:1,operationId:body.operationId,instance:instanceOf(connection),container,request:{method:'GET',url},httpStatus:body.httpStatus,data:body.data,businessStatus:'not-evaluated'};
85
+ }
86
+ export async function callMailbox({folder,endpoint},payload,timeoutMs=10000) {
31
87
  privatePath(folder,true);privatePath(path.join(folder,'endpoint.json'));
32
88
  const current=JSON.parse(fs.readFileSync(path.join(folder,'endpoint.json'),'utf8'));
33
89
  if(current.pid!==endpoint.pid || current.instanceId!==endpoint.instanceId || current.authToken!==endpoint.authToken) throw fail('INSTANCE_CHANGED','Instance changed before submission; discover it again');
34
90
  const id=crypto.randomUUID(), request=path.join(folder,id+'.request.json'), temporary=path.join(folder,id+'.request.tmp'), response=path.join(folder,id+'.response.json');
35
91
  if(exists(request)||exists(response)) throw fail('OPERATION_COLLISION','Request already exists');
36
- writeNew(temporary,{schemaVersion:1,instanceId:endpoint.instanceId,authToken:endpoint.authToken,method:'containers.list'});
92
+ writeNew(temporary,{...payload,schemaVersion:1,instanceId:endpoint.instanceId,authToken:endpoint.authToken});
37
93
  fs.renameSync(temporary,request);
38
94
  const end=Date.now()+timeoutMs;
39
95
  while(Date.now()<end) {
40
96
  if(exists(response)) {
41
97
  privatePath(response);
42
98
  const raw=fs.readFileSync(response,'utf8');
43
- if(Buffer.byteLength(raw)>1024*1024) throw fail('PROTOCOL_MISMATCH','Oversized response',{operationId:id,executed:'unknown'});
99
+ if(Buffer.byteLength(raw)>(payload.method==='session.get'?2*1024*1024+65536:1024*1024)) throw fail('PROTOCOL_MISMATCH','Oversized response',{operationId:id,executed:'unknown'});
44
100
  let body;try{body=JSON.parse(raw);}catch{await pause(25);continue;}
45
101
  if(body.schemaVersion!==1 || body.instanceId!==endpoint.instanceId || body.operationId!==id) throw fail('PROTOCOL_MISMATCH','Response does not match this operation',{operationId:id,executed:'unknown'});
46
102
  fs.unlinkSync(response);
47
- if(body.ok!==true) throw fail('BROWSER_ERROR','Browser refused read-only request',{operationId:id,executed:body.error?.executed??false});
48
- if(!Array.isArray(body.containers)||body.containers.some(c=>typeof c.id!=='string'||typeof c.name!=='string')) throw fail('PROTOCOL_MISMATCH','Invalid container list',{operationId:id,executed:'unknown'});
49
- return {ok:true,schemaVersion:1,operationId:id,instance:{id:endpoint.instanceId,pid:endpoint.pid,channel},containers:body.containers.map(({id,name})=>({id,name}))};
103
+ if(body.ok!==true) {
104
+ const code=/^[A-Z][A-Z0-9_]{0,63}$/.test(body.error?.code??'')?body.error.code:'BROWSER_ERROR';
105
+ throw fail(code,'Ecom refused the request: '+code,{operationId:id,executed:body.error?.executed??'unknown',...(Number.isInteger(body.error?.httpStatus)?{httpStatus:body.error.httpStatus}:{})});
106
+ }
107
+ return body;
50
108
  }
51
109
  await pause(25);
52
110
  }
package/lib/cli.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import {root,pkg,fail} from './common.mjs';
4
- import {discover,listContainers} from './browser.mjs';
4
+ import {discover,listContainers,inspectContainer,requestSession,validateReadUrl} from './browser.mjs';
5
5
  import {source,status,installSkill,checkUpdate,updateInstall} from './maintenance.mjs';
6
6
  export const capabilities=()=>JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
7
7
  export function parse(argv) {
@@ -13,7 +13,7 @@ export function parse(argv) {
13
13
  const key=word.slice(2);
14
14
  if(Object.hasOwn(options,key)) throw fail('INVALID_ARGUMENT','Duplicate option '+word);
15
15
  if(['json','help','yes'].includes(key))options[key]=true;
16
- else if(['agent','target','profile','instance','name','version'].includes(key)&&argv[i+1]&&!argv[i+1].startsWith('--')) options[key]=argv[++i];
16
+ else if(['agent','target','profile','instance','name','version','container','url','method'].includes(key)&&argv[i+1]&&!argv[i+1].startsWith('--')) options[key]=argv[++i];
17
17
  else throw fail('INVALID_ARGUMENT','Unknown option or missing value: '+word);
18
18
  }
19
19
  let command=positions.join(' ')||'help';
@@ -22,11 +22,16 @@ export function parse(argv) {
22
22
  if(!caps.commandContracts[command]) throw fail('INVALID_ARGUMENT','Unsupported command; use help --json');
23
23
  if(command!=='help')for(const key of Object.keys(options))if(!caps.commandContracts[command].options['--'+key])throw fail('INVALID_ARGUMENT','Option is not supported by this command: --'+key);
24
24
  if(options.name&&options.name!=='ecom-cli')throw fail('INVALID_ARGUMENT','Unknown Skill name');
25
+ if(command==='containers inspect'&&!options.container)throw fail('INVALID_ARGUMENT','containers inspect requires --container');
26
+ if(command==='session request') {
27
+ if(!options.container||!options.url)throw fail('INVALID_ARGUMENT','session request requires --container and --url');
28
+ options.url=validateReadUrl(options.url,options.method??'GET');
29
+ }
25
30
  return {command,options,requested};
26
31
  }
27
32
  export async function main(argv) {
28
33
  const {command,options,requested}=parse(argv);
29
- if(command==='help')return {ok:true,package:pkg.name,version:pkg.version,usage:'ecom-cli containers list --json | skill source|status|install|update | update check|install | doctor',...capabilities(),...(capabilities().commandContracts[requested]??{})};
34
+ if(command==='help')return {ok:true,package:pkg.name,version:pkg.version,usage:'ecom-cli containers list|inspect | session request | skill source|status|install|update | update check|install | doctor; use help --json for required options',...capabilities(),...(capabilities().commandContracts[requested]??{})};
30
35
  if(command==='version')return {ok:true,package:pkg.name,version:pkg.version,protocolVersion:1};
31
36
  if(command==='capabilities'||command==='schema')return {ok:true,...capabilities()};
32
37
  if(command==='skill source') {const item=source();return {...capabilities(),...item,complete:true,items:[item]};}
@@ -38,9 +43,11 @@ export async function main(argv) {
38
43
  let instance,error;try{const found=discover(options);instance={id:found.endpoint.instanceId,pid:found.endpoint.pid,channel:found.channel};}catch(e){error={code:e.code,message:e.message};}
39
44
  return {ok:!error,package:pkg.name,version:pkg.version,node:process.version,platform:process.platform,transport:'private-local-mailbox',debuggingRequired:false,instance,...(error?{error}:{}),skillSource:source().path};
40
45
  }
41
- if(command==='containers list') {
46
+ if(['containers list','containers inspect','session request'].includes(command)) {
42
47
  const update=await checkUpdate({automatic:true});
43
48
  if(update.available)process.stderr.write('Ecom CLI update available: '+update.latest+'; ask the current Agent to update the package and Skill.\n');
49
+ if(command==='containers inspect')return inspectContainer(options);
50
+ if(command==='session request')return requestSession(options);
44
51
  return listContainers(options);
45
52
  }
46
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petercjl/ecom-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Agent CLI and portable Skill for Ecom browser containers",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -1,23 +1,29 @@
1
1
  ---
2
2
  name: ecom-cli
3
- description: List isolated account containers in the running Ecom desktop browser, inspect its CLI connection, or install/update the Ecom CLI and companion Skill for the current Agent. Use when the user asks for Ecom browser containers or Ecom CLI setup. Website business queries and browser mutations are outside the first release.
3
+ description: List and inspect account containers in the running Ecom browser, send explicitly authorized read-only HTTPS JSON requests using a selected container session, diagnose connections, or install/update the Ecom CLI and companion Skill. Use for Ecom account metadata and native-session API access; application business logic stays with the calling workflow.
4
4
  ---
5
5
 
6
6
  # Ecom CLI
7
7
 
8
8
  ## Input → strategy → output
9
9
 
10
- Input is a request to list Ecom containers, diagnose the connection, or maintain this plugin for the current Agent. The current Agent needs local command execution, file access and Node.js 22+. Output is a verified container list (stable ID and name), connection diagnosis, or an installation receipt with a separate host discovery check. Container names may identify accounts; disclose only within the user's request.
10
+ Input is an Ecom container listing/detail request, an authorized read-only endpoint plus target container, a connection diagnosis, or selected-Agent maintenance intent. The current Agent needs local command execution, file access and Node.js 22+. Output is a container list, projected account metadata, native-redacted JSON response with pinned target identity, connection diagnosis, or installation receipt with a separate host discovery check. Container names may identify accounts; disclose only within the user's request.
11
11
 
12
12
  ## Main line
13
13
 
14
14
  1. Resolve this installed Skill's directory from the host's actual discovery record. Its bundled CLI is `.runtime/bin/ecom-cli.mjs` relative to that directory. Invoke it through an available Node.js 22+ runtime, checking host-managed runtime configuration before PATH. Canonical npm source has no `.runtime`; complete its managed installation using the packaged INSTALL.md first.
15
15
  2. Read [capabilities.json](capabilities.json) and use [the generic adapter](adapters/generic.json) for any host with the required capabilities. Probe the actual entry with `version --json` and `capabilities --json`; use only declared commands and flags. A missing runtime or required capability ends with CAPABILITY_UNAVAILABLE; do not modify adapters during a task.
16
- 3. For normal listing, invoke the resolved entry with `containers list --json`. Ecom must already be running in normal desktop mode. Discovery validates the live executable and protected endpoint. With multiple instances, ask which instance/profile the user intends and pass the declared selector. Never choose an account from a name guess.
17
- 4. Require `ok=true`, protocol version 1, a concrete instance identity and container ID/name pairs. Return the names and stable IDs as requested. This operation reads containers without navigating pages or changing the active tab.
16
+ 3. Choose the supported operation from the live schema. Use `containers list --json` to locate targets, then use a stable ID or unique exact name. Ecom must already be running in normal desktop mode; discovery validates its executable and protected endpoint. Multiple instances require an explicit intended instance/profile rather than a guess.
17
+ 4. For account details, run `containers inspect --container VALUE --json`. Report platform, saved homepage and timestamped service-login status. `identityBound` records a saved binding; `identityConfirmed` requires the native ready state. Cookie expiry is a reference, not proof of current service access. Account names are labels and do not grant or remove capabilities. Credential-like homepage fields are redacted.
18
+ 5. For a session request, require an explicit authorized read-only endpoint from the user or a trusted business capability’s registered operation. Business selection, endpoint semantics, date/metrics and interpretation belong to that caller. Run `session request --container VALUE --url HTTPS_URL --method GET --json`. GET alone does not prove an endpoint has no side effects; resolve unclear endpoint intent before submission. The CLI pins one instance and container across discovery and execution. Credentials remain native; header/body/credential inputs, redirects and HTML are unsupported. The enabled origin is controlled by Ecom, not expanded by the CLI.
19
+ 6. Require `ok=true`, protocol version 1, operation ID and matching instance/container IDs. For requests, require the JSON response contract and distinguish HTTP success from business success: `businessStatus=not-evaluated` requires the caller’s platform-specific check. Preserve structured errors and executed status. These operations do not navigate or activate tabs.
18
20
 
19
21
  ## Diagnosis and recovery
20
22
 
23
+ - SHOP_NOT_READY: inspect this exact account. Complete login and platform-identity verification through its Ecom account management page; a cookie expiry or account name does not satisfy readiness. If the native adapter cannot verify identity, report that gap and stop the data request. Return to step 5 only after readiness is established.
24
+ - SHOP_VERIFICATION_FAILED: stop; the browser’s pre-request identity check failed. Preserve the selected target and report the failure.
25
+ - ORIGIN_NOT_ALLOWED, CREDENTIAL_INPUT_REJECTED, METHOD_UNSUPPORTED or INVALID_URL: correct the intended input within the declared contract; do not broaden browser permissions.
26
+ - REQUEST_FAILED or NON_JSON_RESPONSE: preserve HTTP/operation/execute fields; handle login, permission or challenge through the original container. Do not infer business success.
21
27
  - INSTANCE_NOT_RUNNING: run `doctor --json`. Ask the user to open a compatible Ecom version. Do not enable remote debugging or substitute another browser.
22
28
  - INSTANCE_AMBIGUOUS: obtain the intended instance or profile, then return to step 3.
23
29
  - INSTANCE_CHANGED: no request was submitted; rediscover before returning to step 3.
@@ -24,13 +24,17 @@
24
24
  "output_fields": [
25
25
  "ok",
26
26
  "containers",
27
+ "container",
27
28
  "instance",
29
+ "httpStatus",
30
+ "data",
31
+ "businessStatus",
28
32
  "error"
29
33
  ],
30
34
  "side_effects": {
31
35
  "external_mutation": false,
32
36
  "may_cost_money": false,
33
- "authorization": "Read-only browser request authorized by user; selected-target writes require installation/update intent"
37
+ "authorization": "Container metadata reads and explicitly authorized read-only HTTPS operations; website Set-Cookie may refresh the native session. Selected-target maintenance requires installation/update intent."
34
38
  },
35
39
  "errors": [
36
40
  "CAPABILITY_UNAVAILABLE",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petercjl/ecom-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }