@petercjl/ecom-cli 0.0.0-bootstrap.0 → 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/INSTALL.md ADDED
@@ -0,0 +1,13 @@
1
+ # Install for the current Agent
2
+
3
+ User prompt: “帮我安装 @petercjl/ecom-cli,并安装它的配套 Skill。”
4
+
5
+ ## Agent bootstrap contract
6
+
7
+ 1. Resolve an available Node.js 22+ runtime from the current Agent's environment/configuration or PATH. Resolve that Agent's exact Skill directory from its advertised host configuration. Arbitrary valid Agent names use the generic adapter; do not infer another Agent's directory.
8
+ 2. Acquire the requested public npm package using the supported package manager. `npm install -g @petercjl/ecom-cli` provides the bootstrap CLI. This is partial installation until the Skill is installed. With scripts disabled, follow the same explicit steps below; postinstall is only a notice.
9
+ 3. Discover `ecom-cli capabilities --json` and `ecom-cli skill source --json`. Use the declared exact-target contract: `ecom-cli skill install --agent <current-agent-name> --target <absolute-skill-directory> --json`. The directory is the Skill itself, not its parent. Source code and accounts are not copied from the browser.
10
+ 4. Verify `current=true`, `managed=true`, `edited=false`, SHA-256 verification, package version and returned CLI entry. Load the installed `SKILL.md` through the current Agent's own discovery mechanism; refresh discovery if the host requires it. Installation is complete only after both package integrity and host discovery succeed.
11
+ 5. Use the returned isolated `.runtime/bin/ecom-cli.mjs` with Node.js for subsequent calls. The bootstrap npm package may then be removed; this Skill and runtime remain functional. A system Node.js runtime and the separately installed Ecom browser are declared external dependencies.
12
+
13
+ Existing unmanaged or edited destinations are preserved and produce a structured conflict. Choose an unused destination or reconcile user edits with a backup. Never install other Agents merely because their directories exist. Runtime support is capability-based; current real browser validation is macOS. No prior published updater exists for this first release.
package/LICENSE ADDED
@@ -0,0 +1,373 @@
1
+ Mozilla Public License Version 2.0
2
+ ==================================
3
+
4
+ 1. Definitions
5
+ --------------
6
+
7
+ 1.1. "Contributor"
8
+ means each individual or legal entity that creates, contributes to
9
+ the creation of, or owns Covered Software.
10
+
11
+ 1.2. "Contributor Version"
12
+ means the combination of the Contributions of others (if any) used
13
+ by a Contributor and that particular Contributor's Contribution.
14
+
15
+ 1.3. "Contribution"
16
+ means Covered Software of a particular Contributor.
17
+
18
+ 1.4. "Covered Software"
19
+ means Source Code Form to which the initial Contributor has attached
20
+ the notice in Exhibit A, the Executable Form of such Source Code
21
+ Form, and Modifications of such Source Code Form, in each case
22
+ including portions thereof.
23
+
24
+ 1.5. "Incompatible With Secondary Licenses"
25
+ means
26
+
27
+ (a) that the initial Contributor has attached the notice described
28
+ in Exhibit B to the Covered Software; or
29
+
30
+ (b) that the Covered Software was made available under the terms of
31
+ version 1.1 or earlier of the License, but not also under the
32
+ terms of a Secondary License.
33
+
34
+ 1.6. "Executable Form"
35
+ means any form of the work other than Source Code Form.
36
+
37
+ 1.7. "Larger Work"
38
+ means a work that combines Covered Software with other material, in
39
+ a separate file or files, that is not Covered Software.
40
+
41
+ 1.8. "License"
42
+ means this document.
43
+
44
+ 1.9. "Licensable"
45
+ means having the right to grant, to the maximum extent possible,
46
+ whether at the time of the initial grant or subsequently, any and
47
+ all of the rights conveyed by this License.
48
+
49
+ 1.10. "Modifications"
50
+ means any of the following:
51
+
52
+ (a) any file in Source Code Form that results from an addition to,
53
+ deletion from, or modification of the contents of Covered
54
+ Software; or
55
+
56
+ (b) any new file in Source Code Form that contains any Covered
57
+ Software.
58
+
59
+ 1.11. "Patent Claims" of a Contributor
60
+ means any patent claim(s), including without limitation, method,
61
+ process, and apparatus claims, in any patent Licensable by such
62
+ Contributor that would be infringed, but for the grant of the
63
+ License, by the making, using, selling, offering for sale, having
64
+ made, import, or transfer of either its Contributions or its
65
+ Contributor Version.
66
+
67
+ 1.12. "Secondary License"
68
+ means either the GNU General Public License, Version 2.0, the GNU
69
+ Lesser General Public License, Version 2.1, the GNU Affero General
70
+ Public License, Version 3.0, or any later versions of those
71
+ licenses.
72
+
73
+ 1.13. "Source Code Form"
74
+ means the form of the work preferred for making modifications.
75
+
76
+ 1.14. "You" (or "Your")
77
+ means an individual or a legal entity exercising rights under this
78
+ License. For legal entities, "You" includes any entity that
79
+ controls, is controlled by, or is under common control with You. For
80
+ purposes of this definition, "control" means (a) the power, direct
81
+ or indirect, to cause the direction or management of such entity,
82
+ whether by contract or otherwise, or (b) ownership of more than
83
+ fifty percent (50%) of the outstanding shares or beneficial
84
+ ownership of such entity.
85
+
86
+ 2. License Grants and Conditions
87
+ --------------------------------
88
+
89
+ 2.1. Grants
90
+
91
+ Each Contributor hereby grants You a world-wide, royalty-free,
92
+ non-exclusive license:
93
+
94
+ (a) under intellectual property rights (other than patent or trademark)
95
+ Licensable by such Contributor to use, reproduce, make available,
96
+ modify, display, perform, distribute, and otherwise exploit its
97
+ Contributions, either on an unmodified basis, with Modifications, or
98
+ as part of a Larger Work; and
99
+
100
+ (b) under Patent Claims of such Contributor to make, use, sell, offer
101
+ for sale, have made, import, and otherwise transfer either its
102
+ Contributions or its Contributor Version.
103
+
104
+ 2.2. Effective Date
105
+
106
+ The licenses granted in Section 2.1 with respect to any Contribution
107
+ become effective for each Contribution on the date the Contributor first
108
+ distributes such Contribution.
109
+
110
+ 2.3. Limitations on Grant Scope
111
+
112
+ The licenses granted in this Section 2 are the only rights granted under
113
+ this License. No additional rights or licenses will be implied from the
114
+ distribution or licensing of Covered Software under this License.
115
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
116
+ Contributor:
117
+
118
+ (a) for any code that a Contributor has removed from Covered Software;
119
+ or
120
+
121
+ (b) for infringements caused by: (i) Your and any other third party's
122
+ modifications of Covered Software, or (ii) the combination of its
123
+ Contributions with other software (except as part of its Contributor
124
+ Version); or
125
+
126
+ (c) under Patent Claims infringed by Covered Software in the absence of
127
+ its Contributions.
128
+
129
+ This License does not grant any rights in the trademarks, service marks,
130
+ or logos of any Contributor (except as may be necessary to comply with
131
+ the notice requirements in Section 3.4).
132
+
133
+ 2.4. Subsequent Licenses
134
+
135
+ No Contributor makes additional grants as a result of Your choice to
136
+ distribute the Covered Software under a subsequent version of this
137
+ License (see Section 10.2) or under the terms of a Secondary License (if
138
+ permitted under the terms of Section 3.3).
139
+
140
+ 2.5. Representation
141
+
142
+ Each Contributor represents that the Contributor believes its
143
+ Contributions are its original creation(s) or it has sufficient rights
144
+ to grant the rights to its Contributions conveyed by this License.
145
+
146
+ 2.6. Fair Use
147
+
148
+ This License is not intended to limit any rights You have under
149
+ applicable copyright doctrines of fair use, fair dealing, or other
150
+ equivalents.
151
+
152
+ 2.7. Conditions
153
+
154
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155
+ in Section 2.1.
156
+
157
+ 3. Responsibilities
158
+ -------------------
159
+
160
+ 3.1. Distribution of Source Form
161
+
162
+ All distribution of Covered Software in Source Code Form, including any
163
+ Modifications that You create or to which You contribute, must be under
164
+ the terms of this License. You must inform recipients that the Source
165
+ Code Form of the Covered Software is governed by the terms of this
166
+ License, and how they can obtain a copy of this License. You may not
167
+ attempt to alter or restrict the recipients' rights in the Source Code
168
+ Form.
169
+
170
+ 3.2. Distribution of Executable Form
171
+
172
+ If You distribute Covered Software in Executable Form then:
173
+
174
+ (a) such Covered Software must also be made available in Source Code
175
+ Form, as described in Section 3.1, and You must inform recipients of
176
+ the Executable Form how they can obtain a copy of such Source Code
177
+ Form by reasonable means in a timely manner, at a charge no more
178
+ than the cost of distribution to the recipient; and
179
+
180
+ (b) You may distribute such Executable Form under the terms of this
181
+ License, or sublicense it under different terms, provided that the
182
+ license for the Executable Form does not attempt to limit or alter
183
+ the recipients' rights in the Source Code Form under this License.
184
+
185
+ 3.3. Distribution of a Larger Work
186
+
187
+ You may create and distribute a Larger Work under terms of Your choice,
188
+ provided that You also comply with the requirements of this License for
189
+ the Covered Software. If the Larger Work is a combination of Covered
190
+ Software with a work governed by one or more Secondary Licenses, and the
191
+ Covered Software is not Incompatible With Secondary Licenses, this
192
+ License permits You to additionally distribute such Covered Software
193
+ under the terms of such Secondary License(s), so that the recipient of
194
+ the Larger Work may, at their option, further distribute the Covered
195
+ Software under the terms of either this License or such Secondary
196
+ License(s).
197
+
198
+ 3.4. Notices
199
+
200
+ You may not remove or alter the substance of any license notices
201
+ (including copyright notices, patent notices, disclaimers of warranty,
202
+ or limitations of liability) contained within the Source Code Form of
203
+ the Covered Software, except that You may alter any license notices to
204
+ the extent required to remedy known factual inaccuracies.
205
+
206
+ 3.5. Application of Additional Terms
207
+
208
+ You may choose to offer, and to charge a fee for, warranty, support,
209
+ indemnity or liability obligations to one or more recipients of Covered
210
+ Software. However, You may do so only on Your own behalf, and not on
211
+ behalf of any Contributor. You must make it absolutely clear that any
212
+ such warranty, support, indemnity, or liability obligation is offered by
213
+ You alone, and You hereby agree to indemnify every Contributor for any
214
+ liability incurred by such Contributor as a result of warranty, support,
215
+ indemnity or liability terms You offer. You may include additional
216
+ disclaimers of warranty and limitations of liability specific to any
217
+ jurisdiction.
218
+
219
+ 4. Inability to Comply Due to Statute or Regulation
220
+ ---------------------------------------------------
221
+
222
+ If it is impossible for You to comply with any of the terms of this
223
+ License with respect to some or all of the Covered Software due to
224
+ statute, judicial order, or regulation then You must: (a) comply with
225
+ the terms of this License to the maximum extent possible; and (b)
226
+ describe the limitations and the code they affect. Such description must
227
+ be placed in a text file included with all distributions of the Covered
228
+ Software under this License. Except to the extent prohibited by statute
229
+ or regulation, such description must be sufficiently detailed for a
230
+ recipient of ordinary skill to be able to understand it.
231
+
232
+ 5. Termination
233
+ --------------
234
+
235
+ 5.1. The rights granted under this License will terminate automatically
236
+ if You fail to comply with any of its terms. However, if You become
237
+ compliant, then the rights granted under this License from a particular
238
+ Contributor are reinstated (a) provisionally, unless and until such
239
+ Contributor explicitly and finally terminates Your grants, and (b) on an
240
+ ongoing basis, if such Contributor fails to notify You of the
241
+ non-compliance by some reasonable means prior to 60 days after You have
242
+ come back into compliance. Moreover, Your grants from a particular
243
+ Contributor are reinstated on an ongoing basis if such Contributor
244
+ notifies You of the non-compliance by some reasonable means, this is the
245
+ first time You have received notice of non-compliance with this License
246
+ from such Contributor, and You become compliant prior to 30 days after
247
+ Your receipt of the notice.
248
+
249
+ 5.2. If You initiate litigation against any entity by asserting a patent
250
+ infringement claim (excluding declaratory judgment actions,
251
+ counter-claims, and cross-claims) alleging that a Contributor Version
252
+ directly or indirectly infringes any patent, then the rights granted to
253
+ You by any and all Contributors for the Covered Software under Section
254
+ 2.1 of this License shall terminate.
255
+
256
+ 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257
+ end user license agreements (excluding distributors and resellers) which
258
+ have been validly granted by You or Your distributors under this License
259
+ prior to termination shall survive termination.
260
+
261
+ ************************************************************************
262
+ * *
263
+ * 6. Disclaimer of Warranty *
264
+ * ------------------------- *
265
+ * *
266
+ * Covered Software is provided under this License on an "as is" *
267
+ * basis, without warranty of any kind, either expressed, implied, or *
268
+ * statutory, including, without limitation, warranties that the *
269
+ * Covered Software is free of defects, merchantable, fit for a *
270
+ * particular purpose or non-infringing. The entire risk as to the *
271
+ * quality and performance of the Covered Software is with You. *
272
+ * Should any Covered Software prove defective in any respect, You *
273
+ * (not any Contributor) assume the cost of any necessary servicing, *
274
+ * repair, or correction. This disclaimer of warranty constitutes an *
275
+ * essential part of this License. No use of any Covered Software is *
276
+ * authorized under this License except under this disclaimer. *
277
+ * *
278
+ ************************************************************************
279
+
280
+ ************************************************************************
281
+ * *
282
+ * 7. Limitation of Liability *
283
+ * -------------------------- *
284
+ * *
285
+ * Under no circumstances and under no legal theory, whether tort *
286
+ * (including negligence), contract, or otherwise, shall any *
287
+ * Contributor, or anyone who distributes Covered Software as *
288
+ * permitted above, be liable to You for any direct, indirect, *
289
+ * special, incidental, or consequential damages of any character *
290
+ * including, without limitation, damages for lost profits, loss of *
291
+ * goodwill, work stoppage, computer failure or malfunction, or any *
292
+ * and all other commercial damages or losses, even if such party *
293
+ * shall have been informed of the possibility of such damages. This *
294
+ * limitation of liability shall not apply to liability for death or *
295
+ * personal injury resulting from such party's negligence to the *
296
+ * extent applicable law prohibits such limitation. Some *
297
+ * jurisdictions do not allow the exclusion or limitation of *
298
+ * incidental or consequential damages, so this exclusion and *
299
+ * limitation may not apply to You. *
300
+ * *
301
+ ************************************************************************
302
+
303
+ 8. Litigation
304
+ -------------
305
+
306
+ Any litigation relating to this License may be brought only in the
307
+ courts of a jurisdiction where the defendant maintains its principal
308
+ place of business and such litigation shall be governed by laws of that
309
+ jurisdiction, without reference to its conflict-of-law provisions.
310
+ Nothing in this Section shall prevent a party's ability to bring
311
+ cross-claims or counter-claims.
312
+
313
+ 9. Miscellaneous
314
+ ----------------
315
+
316
+ This License represents the complete agreement concerning the subject
317
+ matter hereof. If any provision of this License is held to be
318
+ unenforceable, such provision shall be reformed only to the extent
319
+ necessary to make it enforceable. Any law or regulation which provides
320
+ that the language of a contract shall be construed against the drafter
321
+ shall not be used to construe this License against a Contributor.
322
+
323
+ 10. Versions of the License
324
+ ---------------------------
325
+
326
+ 10.1. New Versions
327
+
328
+ Mozilla Foundation is the license steward. Except as provided in Section
329
+ 10.3, no one other than the license steward has the right to modify or
330
+ publish new versions of this License. Each version will be given a
331
+ distinguishing version number.
332
+
333
+ 10.2. Effect of New Versions
334
+
335
+ You may distribute the Covered Software under the terms of the version
336
+ of the License under which You originally received the Covered Software,
337
+ or under the terms of any subsequent version published by the license
338
+ steward.
339
+
340
+ 10.3. Modified Versions
341
+
342
+ If you create software not governed by this License, and you want to
343
+ create a new license for such software, you may create and use a
344
+ modified version of this License if you rename the license and remove
345
+ any references to the name of the license steward (except to note that
346
+ such modified license differs from this License).
347
+
348
+ 10.4. Distributing Source Code Form that is Incompatible With Secondary
349
+ Licenses
350
+
351
+ If You choose to distribute Source Code Form that is Incompatible With
352
+ Secondary Licenses under the terms of this version of the License, the
353
+ notice described in Exhibit B of this License must be attached.
354
+
355
+ Exhibit A - Source Code Form License Notice
356
+ -------------------------------------------
357
+
358
+ This Source Code Form is subject to the terms of the Mozilla Public
359
+ License, v. 2.0. If a copy of the MPL was not distributed with this
360
+ file, You can obtain one at https://mozilla.org/MPL/2.0/.
361
+
362
+ If it is not possible or desirable to put the notice in a particular
363
+ file, then You may include the notice in a location (such as a LICENSE
364
+ file in a relevant directory) where a recipient would be likely to look
365
+ for such a notice.
366
+
367
+ You may add additional accurate notices of copyright ownership.
368
+
369
+ Exhibit B - "Incompatible With Secondary Licenses" Notice
370
+ ---------------------------------------------------------
371
+
372
+ This Source Code Form is "Incompatible With Secondary Licenses", as
373
+ defined by the Mozilla Public License, v. 2.0.
package/README.md CHANGED
@@ -1,3 +1,29 @@
1
- # Ecom CLI bootstrap
1
+ # Ecom CLI
2
2
 
3
- Minimal package reservation for Trusted Publishing setup. Functional stable releases are published separately through GitHub Actions.
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.
4
+
5
+ ## Install
6
+
7
+ Send to your current Agent:
8
+
9
+ > 帮我安装 @petercjl/ecom-cli,并安装它的配套 Skill。
10
+
11
+ The Agent needs local Node.js 22+ and terminal/file access. Installation includes the CLI and an isolated copy of the Skill in that Agent's own resolved Skill directory. See [INSTALL.md](INSTALL.md) for the bootstrap contract, including installation with npm scripts disabled.
12
+
13
+ ## Use
14
+
15
+ > 用 Ecom CLI 列出我当前的浏览器容器。
16
+
17
+ 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
+
19
+ ## Update
20
+
21
+ > 帮我更新 @petercjl/ecom-cli 和它的配套 Skill。
22
+
23
+ Task entry checks stable `latest` at most daily, with a 1.5 second bound. Detection only notifies; updating requires authorization. An offline check retains the installed runtime. Each selected Skill contains its own complete CLI runtime; activate the entry returned by the installer. Other Agents remain independent. See [UPDATE.md](UPDATE.md).
24
+
25
+ ## Developer contract
26
+
27
+ `ecom-cli help --json`, `version --json`, `capabilities --json`, `schema --json`, and `doctor --json` expose supported commands and versioned results. Errors use JSON with stable codes; exit code 2 means invalid arguments, 1 means operational failure. stdout contains results; stderr contains diagnostics. Multiple Ecom instances require explicit `--profile` or `--instance` selection. Connection authenticates through owner-only local mailbox files, verifies the live Ecom executable, and uses no remote debugging port.
28
+
29
+ The browser owns sessions, containers and authorization. This package owns discovery, read-only transport and managed installation. The Skill documents invocation, diagnosis and recovery. Additional browser capabilities will extend this same command/Skill contract.
package/UPDATE.md ADDED
@@ -0,0 +1,11 @@
1
+ # Update the selected Agent installation
2
+
3
+ User prompt: “帮我更新 @petercjl/ecom-cli 和它的配套 Skill。”
4
+
5
+ Resolve the currently selected Agent and exact Skill destination before mutation. Inspect the declared command contract and managed status. When current, reuse the verified installation; a same-version update repairs a missing or stale unedited Skill.
6
+
7
+ Run the selected Skill's isolated CLI with `update install --agent <name> --target <absolute-skill-directory> --yes --json`. It selects stable `latest` (or an explicit stable `--version`), acquires the candidate through npm with scripts disabled, stages the full runtime plus Skill, preserves the previous target as a uniquely named backup, activates and verifies hashes/version. Use the new receipt's entry. The optional global bootstrap package remains independently installed; it is not the selected Agent runtime. Refresh host discovery and load the resulting Skill before reporting completion.
8
+
9
+ Acquisition records a target-scoped operation journal and retains candidate location, requested version and previous version. For failure, inspect the journal and `skill status` before any new attempt. A directory lock, local edits or unmanaged target stops replacement. Failed rename restores the previous directory when possible and reports recovery evidence. Retain backups and candidates until an authorized cleanup. To roll back, verify a recorded backup, preserve the current target and restore that backup as an explicit maintenance action; do not overwrite user content.
10
+
11
+ Automatic checks occur only at container-list task entry, use `latest`, are throttled to 24 hours and bounded to 1500 ms. Set `ECOM_CLI_UPDATE_CHECK=0` to disable checks. An unavailable registry does not block browser reads or replace an installation. Updates affect only the explicit target; other Agents' isolated copies continue independently. External Node.js and Ecom installation remain unchanged.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import {main} from '../lib/cli.mjs';
3
+ try {
4
+ const result = await main(process.argv.slice(2));
5
+ process.stdout.write(JSON.stringify(result) + '\n');
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');
8
+ process.exitCode = error.code === 'INVALID_ARGUMENT' ? 2 : 1;
9
+ }
@@ -0,0 +1,345 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "package": "@petercjl/ecom-cli",
4
+ "version": "0.1.1",
5
+ "skills": [
6
+ "ecom-cli"
7
+ ],
8
+ "commandContracts": {
9
+ "help": {
10
+ "options": {
11
+ "--json": {
12
+ "type": "boolean"
13
+ }
14
+ },
15
+ "sideEffects": "none"
16
+ },
17
+ "version": {
18
+ "options": {
19
+ "--json": {
20
+ "type": "boolean"
21
+ }
22
+ },
23
+ "sideEffects": "none"
24
+ },
25
+ "capabilities": {
26
+ "options": {
27
+ "--json": {
28
+ "type": "boolean"
29
+ }
30
+ },
31
+ "sideEffects": "none"
32
+ },
33
+ "schema": {
34
+ "options": {
35
+ "--json": {
36
+ "type": "boolean"
37
+ }
38
+ },
39
+ "sideEffects": "none"
40
+ },
41
+ "doctor": {
42
+ "options": {
43
+ "--json": {
44
+ "type": "boolean"
45
+ },
46
+ "--profile": {
47
+ "type": "string"
48
+ },
49
+ "--instance": {
50
+ "type": "string"
51
+ }
52
+ },
53
+ "sideEffects": "none"
54
+ },
55
+ "containers list": {
56
+ "options": {
57
+ "--json": {
58
+ "type": "boolean"
59
+ },
60
+ "--profile": {
61
+ "type": "string"
62
+ },
63
+ "--instance": {
64
+ "type": "string"
65
+ }
66
+ },
67
+ "sideEffects": "local-write",
68
+ "authorization": "Read container IDs and names; authenticated local request and throttled update cache only"
69
+ },
70
+ "skill source": {
71
+ "options": {
72
+ "--json": {
73
+ "type": "boolean"
74
+ },
75
+ "--name": {
76
+ "type": "string"
77
+ }
78
+ },
79
+ "sideEffects": "none"
80
+ },
81
+ "skill status": {
82
+ "options": {
83
+ "--json": {
84
+ "type": "boolean"
85
+ },
86
+ "--agent": {
87
+ "type": "string"
88
+ },
89
+ "--target": {
90
+ "type": "string"
91
+ },
92
+ "--name": {
93
+ "type": "string"
94
+ }
95
+ },
96
+ "sideEffects": "none"
97
+ },
98
+ "skill install": {
99
+ "options": {
100
+ "--json": {
101
+ "type": "boolean"
102
+ },
103
+ "--agent": {
104
+ "type": "string"
105
+ },
106
+ "--target": {
107
+ "type": "string"
108
+ },
109
+ "--name": {
110
+ "type": "string"
111
+ }
112
+ },
113
+ "sideEffects": "local-write"
114
+ },
115
+ "skill update": {
116
+ "options": {
117
+ "--json": {
118
+ "type": "boolean"
119
+ },
120
+ "--agent": {
121
+ "type": "string"
122
+ },
123
+ "--target": {
124
+ "type": "string"
125
+ },
126
+ "--name": {
127
+ "type": "string"
128
+ }
129
+ },
130
+ "sideEffects": "local-write"
131
+ },
132
+ "update check": {
133
+ "options": {
134
+ "--json": {
135
+ "type": "boolean"
136
+ }
137
+ },
138
+ "sideEffects": "none"
139
+ },
140
+ "update install": {
141
+ "options": {
142
+ "--json": {
143
+ "type": "boolean"
144
+ },
145
+ "--agent": {
146
+ "type": "string"
147
+ },
148
+ "--target": {
149
+ "type": "string"
150
+ },
151
+ "--name": {
152
+ "type": "string"
153
+ },
154
+ "--version": {
155
+ "type": "string"
156
+ },
157
+ "--yes": {
158
+ "type": "boolean"
159
+ }
160
+ },
161
+ "sideEffects": "package-and-skill-update"
162
+ }
163
+ },
164
+ "transport": {
165
+ "kind": "private-local-mailbox",
166
+ "protocolVersion": 1,
167
+ "debuggingRequired": false,
168
+ "platforms": {
169
+ "macos": "implemented",
170
+ "windows": "unsupported"
171
+ }
172
+ },
173
+ "discoveryContract": {
174
+ "schemaVersion": 1,
175
+ "commandsField": "commandContracts",
176
+ "entryPoints": [
177
+ {
178
+ "argv": [
179
+ "capabilities",
180
+ "--json"
181
+ ],
182
+ "result": "manifest"
183
+ }
184
+ ],
185
+ "version": {
186
+ "command": "version",
187
+ "argv": [
188
+ "version",
189
+ "--json"
190
+ ],
191
+ "fields": {
192
+ "package": {
193
+ "field": "package",
194
+ "type": "string"
195
+ },
196
+ "version": {
197
+ "field": "version",
198
+ "type": "string"
199
+ }
200
+ }
201
+ },
202
+ "skillCollection": {
203
+ "namesField": "skills",
204
+ "default": "ecom-cli",
205
+ "selector": {
206
+ "option": "--name",
207
+ "type": "string"
208
+ },
209
+ "enumeration": {
210
+ "argv": [
211
+ "skill",
212
+ "source",
213
+ "--json"
214
+ ],
215
+ "itemsField": "items",
216
+ "completeField": "complete"
217
+ },
218
+ "namedSource": [
219
+ "skill",
220
+ "source",
221
+ "--name",
222
+ "{skill}",
223
+ "--json"
224
+ ]
225
+ }
226
+ },
227
+ "maintenanceContract": {
228
+ "schemaVersion": 1,
229
+ "agentEligibility": "valid-name-with-generic-fallback",
230
+ "hostDiscoveryAuthority": "host",
231
+ "targetOptions": {
232
+ "--target": {
233
+ "kind": "skill-directory"
234
+ }
235
+ },
236
+ "source": {
237
+ "name": {
238
+ "field": "name",
239
+ "type": "string"
240
+ },
241
+ "package": {
242
+ "field": "package",
243
+ "type": "string"
244
+ },
245
+ "version": {
246
+ "field": "version",
247
+ "type": "string"
248
+ },
249
+ "path": {
250
+ "field": "path",
251
+ "type": "string"
252
+ }
253
+ },
254
+ "status": {
255
+ "name": {
256
+ "field": "name",
257
+ "type": "string"
258
+ },
259
+ "package": {
260
+ "field": "package",
261
+ "type": "string"
262
+ },
263
+ "version": {
264
+ "field": "version",
265
+ "type": "string"
266
+ },
267
+ "target": {
268
+ "field": "target",
269
+ "type": "string"
270
+ },
271
+ "installed": {
272
+ "field": "installed",
273
+ "type": "boolean"
274
+ },
275
+ "managed": {
276
+ "field": "managed",
277
+ "type": "boolean"
278
+ },
279
+ "current": {
280
+ "field": "current",
281
+ "type": "boolean"
282
+ },
283
+ "edited": {
284
+ "field": "edited",
285
+ "type": "boolean"
286
+ },
287
+ "verification": {
288
+ "field": "verification",
289
+ "type": "object"
290
+ },
291
+ "recovery": {
292
+ "field": "recovery",
293
+ "type": "object"
294
+ }
295
+ }
296
+ },
297
+ "installationLifecycle": {
298
+ "skillRequired": true,
299
+ "updateRequiresSkillSync": true,
300
+ "required_components": [
301
+ "cli",
302
+ "skill"
303
+ ],
304
+ "agentNeutral": true,
305
+ "targetRequired": true,
306
+ "hostRegistrationRequired": true,
307
+ "installationMode": "copy",
308
+ "sharedLinksAllowed": false,
309
+ "isolatedRuntimeRequired": true,
310
+ "installGuide": "INSTALL.md",
311
+ "updateGuide": "UPDATE.md",
312
+ "install": "Resolve current Agent exact Skill target; run skill install --agent NAME --target PATH; load Skill through host discovery.",
313
+ "update": "Run selected Skill runtime update install --agent NAME --target PATH --yes; verify returned activated entry and refresh host discovery.",
314
+ "verification": "Verify CLI and Skill versions, managed hashes and current=true; host loads installed SKILL.md independently."
315
+ },
316
+ "automaticUpdates": {
317
+ "enabledByDefault": true,
318
+ "intervalHours": 24,
319
+ "timeoutMs": 1500,
320
+ "trigger": "containers list task entry",
321
+ "configuration": "Stable latest channel; detection only. Explicit update activates only the selected target runtime and Skill together.",
322
+ "disable": "ECOM_CLI_UPDATE_CHECK=0",
323
+ "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.",
324
+ "defaultMode": "notify"
325
+ },
326
+ "compatibility": {
327
+ "agents": "Any Agent with terminal, Node.js and Skill discovery capabilities; generic adapter",
328
+ "macos": "Runtime validation pending",
329
+ "windows": "Package mechanics only; browser transport unsupported",
330
+ "historicalUpdater": "First release; registry package absent; no prior updater exists"
331
+ },
332
+ "errors": [
333
+ "INSTANCE_NOT_RUNNING",
334
+ "INSTANCE_AMBIGUOUS",
335
+ "INSTANCE_CHANGED",
336
+ "UNSAFE_ENDPOINT",
337
+ "OPERATION_TIMEOUT",
338
+ "PROTOCOL_MISMATCH",
339
+ "PLATFORM_UNSUPPORTED",
340
+ "TARGET_REQUIRED",
341
+ "LOCAL_EDITS",
342
+ "UNMANAGED_TARGET",
343
+ "TARGET_LOCKED"
344
+ ]
345
+ }
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import {execFileSync} from 'node:child_process';
5
+ import {setTimeout as pause} from 'node:timers/promises';
6
+ import {fail,privatePath,writeNew,exists} from './common.mjs';
7
+ const ps=(args)=>execFileSync('/bin/ps',args,{encoding:'utf8',timeout:3000,maxBuffer:4*1024*1024});
8
+ export function discover({profile,instance}={}) {
9
+ if(process.platform!=='darwin') throw fail('PLATFORM_UNSUPPORTED','Browser transport currently supports macOS; no debugging fallback is available');
10
+ const candidates=[];
11
+ for(const line of ps(['-axo','pid=,uid=,comm=']).split('\n')) {
12
+ const m=line.trim().match(/^(\d+)\s+(\d+)\s+(.+)$/);
13
+ if(!m || Number(m[2])!==process.getuid() || !/\/(Ecom Dev|Ecom)\.app\/Contents\/MacOS\/\1$/.test(m[3])) continue;
14
+ let command; try {command=ps(['-ww','-p',m[1],'-o','command=']).trim();}catch{continue;}
15
+ if(/--(?:remote-debugging|headless|enable-automation)/.test(command)) continue;
16
+ const selected=command.match(/(?:^| )--user-data-dir=(.*?)(?= --|$)/)?.[1];
17
+ if(!selected || !path.isAbsolute(selected) || (profile && path.resolve(profile)!==path.resolve(selected))) continue;
18
+ const folder=path.join(selected,'Default','.ecom-native-cli'), file=path.join(folder,'endpoint.json');
19
+ if(!exists(file)) continue;
20
+ privatePath(folder,true);privatePath(file);
21
+ const endpoint=JSON.parse(fs.readFileSync(file,'utf8'));
22
+ if(endpoint.product!=='Ecom' || endpoint.schemaVersion!==1 || endpoint.pid!==Number(m[1]) || endpoint.executable!==m[3] || endpoint.userDataDir!==selected || !/^[\da-f-]{36}$/.test(endpoint.instanceId) || typeof endpoint.authToken!=='string') continue;
23
+ if(instance && endpoint.instanceId!==instance) continue;
24
+ candidates.push({folder,endpoint,channel:m[3].includes('/Ecom Dev.app/')?'dev':'stable'});
25
+ }
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
+ return candidates[0];
28
+ }
29
+ export async function listContainers(options={}) {return readMailbox(discover(options));}
30
+ export async function readMailbox({folder,endpoint,channel},timeoutMs=10000) {
31
+ privatePath(folder,true);privatePath(path.join(folder,'endpoint.json'));
32
+ const current=JSON.parse(fs.readFileSync(path.join(folder,'endpoint.json'),'utf8'));
33
+ 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
+ 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
+ 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'});
37
+ fs.renameSync(temporary,request);
38
+ const end=Date.now()+timeoutMs;
39
+ while(Date.now()<end) {
40
+ if(exists(response)) {
41
+ privatePath(response);
42
+ const raw=fs.readFileSync(response,'utf8');
43
+ if(Buffer.byteLength(raw)>1024*1024) throw fail('PROTOCOL_MISMATCH','Oversized response',{operationId:id,executed:'unknown'});
44
+ let body;try{body=JSON.parse(raw);}catch{await pause(25);continue;}
45
+ 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
+ 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}))};
50
+ }
51
+ await pause(25);
52
+ }
53
+ throw fail('OPERATION_TIMEOUT','Read timed out; retain operation ID and inspect instance before retrying',{operationId:id,executed:'unknown'});
54
+ }
package/lib/cli.mjs ADDED
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import {root,pkg,fail} from './common.mjs';
4
+ import {discover,listContainers} from './browser.mjs';
5
+ import {source,status,installSkill,checkUpdate,updateInstall} from './maintenance.mjs';
6
+ export const capabilities=()=>JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
7
+ export function parse(argv) {
8
+ const positions=[],options={}, caps=capabilities();
9
+ for(let i=0;i<argv.length;i++) {
10
+ const word=argv[i];
11
+ if(!word.startsWith('--')) {positions.push(word);continue;}
12
+ if(word==='--version'&&argv.length===1){positions.push('version');continue;}
13
+ const key=word.slice(2);
14
+ if(Object.hasOwn(options,key)) throw fail('INVALID_ARGUMENT','Duplicate option '+word);
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];
17
+ else throw fail('INVALID_ARGUMENT','Unknown option or missing value: '+word);
18
+ }
19
+ let command=positions.join(' ')||'help';
20
+ const requested=command;
21
+ if(options.help) command='help';
22
+ if(!caps.commandContracts[command]) throw fail('INVALID_ARGUMENT','Unsupported command; use help --json');
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
+ if(options.name&&options.name!=='ecom-cli')throw fail('INVALID_ARGUMENT','Unknown Skill name');
25
+ return {command,options,requested};
26
+ }
27
+ export async function main(argv) {
28
+ 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]??{})};
30
+ if(command==='version')return {ok:true,package:pkg.name,version:pkg.version,protocolVersion:1};
31
+ if(command==='capabilities'||command==='schema')return {ok:true,...capabilities()};
32
+ if(command==='skill source') {const item=source();return {...capabilities(),...item,complete:true,items:[item]};}
33
+ if(command==='skill status')return status(options);
34
+ if(command==='skill install'||command==='skill update')return installSkill(options);
35
+ if(command==='update check')return checkUpdate();
36
+ if(command==='update install')return updateInstall(options);
37
+ if(command==='doctor') {
38
+ 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
+ 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
+ }
41
+ if(command==='containers list') {
42
+ const update=await checkUpdate({automatic:true});
43
+ if(update.available)process.stderr.write('Ecom CLI update available: '+update.latest+'; ask the current Agent to update the package and Skill.\n');
44
+ return listContainers(options);
45
+ }
46
+ }
package/lib/common.mjs ADDED
@@ -0,0 +1,24 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import {fileURLToPath} from 'node:url';
5
+ export const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
6
+ export const pkg = JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8'));
7
+ export const fail = (code,message,extra={}) => Object.assign(new Error(message),{code,...extra});
8
+ export function exists(p) { try { fs.lstatSync(p); return true; } catch(e) { if(e.code==='ENOENT') return false; throw e; } }
9
+ export function tree(dir, prefix='') {
10
+ const out={};
11
+ for(const item of fs.readdirSync(dir,{withFileTypes:true}).sort((a,b)=>a.name.localeCompare(b.name))) {
12
+ if(item.name==='.ecom-receipt.json' || item.name==='.ecom-operation.json') continue;
13
+ const rel=prefix+item.name, p=path.join(dir,item.name), st=fs.lstatSync(p);
14
+ if(st.isSymbolicLink() || (!st.isDirectory() && (!st.isFile() || st.nlink!==1))) throw fail('UNSAFE_PATH','Shared links and special files are not supported');
15
+ if(st.isDirectory()) Object.assign(out,tree(p,rel+'/'));
16
+ else out[rel]=crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex');
17
+ }
18
+ return out;
19
+ }
20
+ export function writeNew(p,data) { fs.writeFileSync(p,JSON.stringify(data,null,2)+'\n',{flag:'wx',mode:0o600}); }
21
+ export function privatePath(p,directory=false) {
22
+ const s=fs.lstatSync(p);
23
+ if(s.uid!==process.getuid?.() || (s.mode&0o777)!==(directory?0o700:0o600) || (directory?!s.isDirectory():!s.isFile()||s.nlink!==1)) throw fail('UNSAFE_ENDPOINT','Endpoint ownership, permissions or file type is invalid');
24
+ }
@@ -0,0 +1,102 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import crypto from 'node:crypto';
5
+ import {spawnSync} from 'node:child_process';
6
+ import {root,pkg,fail,exists,tree,writeNew} from './common.mjs';
7
+ const receiptName='.ecom-receipt.json';
8
+ export const source=(base=root)=>({ok:true,name:'ecom-cli',package:pkg.name,version:JSON.parse(fs.readFileSync(path.join(base,'package.json'),'utf8')).version,path:path.join(base,'skills/ecom-cli'),provenance:{ownership:'npm-package',registryContentVerified:false}});
9
+ export function targetOf(options) {
10
+ if(!options.agent || !/^[a-z][a-z0-9-]{0,63}$/.test(options.agent)) throw fail('INVALID_ARGUMENT','Supply a valid --agent name; unnamed hosts use the generic adapter');
11
+ if(!options.target || !path.isAbsolute(options.target)) throw fail('TARGET_REQUIRED','Current Agent must resolve and supply an absolute --target Skill directory');
12
+ const target=path.resolve(options.target);
13
+ for(let p=target;p!==path.dirname(p);p=path.dirname(p)) if(exists(p)&&fs.lstatSync(p).isSymbolicLink()) {
14
+ const systemAlias=process.platform==='darwin'&&['/var','/tmp'].includes(p)&&fs.lstatSync(p).uid===0;
15
+ if(!systemAlias)throw fail('UNSAFE_PATH','Skill destination must use physical directories');
16
+ }
17
+ const ownTarget=path.basename(root)==='.runtime'&&path.dirname(root)===target;
18
+ if(!ownTarget&&(target===root || root.startsWith(target+path.sep) || target.startsWith(root+path.sep))) throw fail('TARGET_CONFLICT','Skill target must be independent of the package source');
19
+ return target;
20
+ }
21
+ export function status(options,base=root) {
22
+ const target=targetOf(options), meta=path.join(target,receiptName), installed=exists(target);
23
+ let receipt;try{if(exists(meta)&&fs.lstatSync(meta).isFile()) receipt=JSON.parse(fs.readFileSync(meta,'utf8'));}catch{}
24
+ const managed=receipt?.package===pkg.name && receipt?.target===target && receipt?.agent===options.agent;
25
+ const files=installed?tree(target):{}, old=managed?receipt.files:{}, edited=managed && JSON.stringify(files)!==JSON.stringify(old);
26
+ const identity=source(base), canonical=tree(identity.path);
27
+ const canonicalCurrent=managed && Object.entries(canonical).every(([name,hash])=>files[name]===hash) && Object.keys(files).filter(n=>!n.startsWith('.runtime/')).length===Object.keys(canonical).length;
28
+ let runtimeVersion;try{runtimeVersion=JSON.parse(fs.readFileSync(path.join(target,'.runtime/package.json'),'utf8')).version;}catch{}
29
+ const current=!!(managed&&!edited&&canonicalCurrent&&runtimeVersion===identity.version&&receipt.version===identity.version);
30
+ return {...identity,target,installed,managed,current,edited:!!edited,adapter:'generic',agent:options.agent,entry:path.join(target,'.runtime/bin/ecom-cli.mjs'),verification:{verified:current,algorithm:'sha256'},recovery:{required:installed&&!current,action:edited?'Preserve local edits; reconcile them before update':'Run skill update for this exact target'},hostDiscovery:{authority:'host',verified:false},...(managed?{installedVersion:receipt.version,backup:receipt.backup??null}:{})};
31
+ }
32
+ export function installSkill(options,base=root) {
33
+ const before=status(options,base), target=before.target;
34
+ if(before.current) return {...before,changed:false};
35
+ if(before.installed&&!before.managed) throw fail('UNMANAGED_TARGET','Existing Skill is user-owned; choose a fresh destination');
36
+ if(before.edited) throw fail('LOCAL_EDITS','Managed Skill has local edits; preserve and reconcile them before update');
37
+ const parent=path.dirname(target);fs.mkdirSync(parent,{recursive:true});
38
+ const lock=target+'.ecom-lock';let fd;
39
+ try {fd=fs.openSync(lock,'wx',0o600);}catch{throw fail('TARGET_LOCKED','Another maintenance operation holds the target lock');}
40
+ const operationId=crypto.randomUUID(), staging=target+'.ecom-stage-'+operationId;
41
+ const backup=before.installed?target+'.ecom-backup-'+operationId:null;
42
+ let activated=false;
43
+ try {
44
+ tree(base);
45
+ fs.cpSync(source(base).path,staging,{recursive:true,errorOnExist:true,force:false});
46
+ const runtime=path.join(staging,'.runtime');fs.mkdirSync(runtime);
47
+ for(const name of ['package.json','capabilities.json','bin','lib','scripts','skills','README.md','INSTALL.md','UPDATE.md','LICENSE']) {
48
+ fs.cpSync(path.join(base,name),path.join(runtime,name),{recursive:true,errorOnExist:true,force:false});
49
+ }
50
+ const manifest=source(base);
51
+ writeNew(path.join(staging,receiptName),{schemaVersion:1,package:pkg.name,name:'ecom-cli',version:manifest.version,target,agent:options.agent,operationId,backup,files:tree(staging)});
52
+ // Recheck edits after staging, before replacement.
53
+ const again=status(options,base);
54
+ if(again.edited || again.installed!==before.installed) throw fail('TARGET_CHANGED','Skill changed during preparation');
55
+ if(backup) fs.renameSync(target,backup);
56
+ try {fs.renameSync(staging,target);activated=true;}catch(e){if(backup&&!exists(target))fs.renameSync(backup,target);throw e;}
57
+ const result=status(options,base);
58
+ if(!result.current) throw fail('VERIFY_FAILED','Installation integrity verification failed');
59
+ return {...result,changed:true,operationId,backup,completion:{cli:true,skill:true,hostDiscovery:false},remainingActions:['Load installed SKILL.md through the current Agent discovery mechanism']};
60
+ } catch(e) {
61
+ if(!activated&&exists(staging)) fs.rmSync(staging,{recursive:true});
62
+ throw fail(e.code||'INSTALL_FAILED',e.code?e.message:'Skill activation failed; the previous installation is preserved',{operationId,recovery:{backup,target,activated,action:'Inspect skill status before retrying'}});
63
+ } finally {fs.closeSync(fd);fs.unlinkSync(lock);}
64
+ }
65
+ const cacheRoot=()=>path.join(os.homedir(),'.cache','ecom-cli');
66
+ export async function checkUpdate({automatic=false}={}) {
67
+ if(automatic&&process.env.ECOM_CLI_UPDATE_CHECK==='0') return {ok:true,disabled:true};
68
+ const cache=path.join(cacheRoot(),'update.json');
69
+ let old;try {old=JSON.parse(fs.readFileSync(cache,'utf8'));}catch{}
70
+ if(automatic&&old?.version===pkg.version&&Date.now()-old.checkedAt<86400000) return {...old,cached:true};
71
+ let result;
72
+ try {
73
+ const response=await fetch('https://registry.npmjs.org/@petercjl%2fecom-cli/latest',{signal:AbortSignal.timeout(1500)});
74
+ if(!response.ok) throw Error('registry');
75
+ const data=await response.json();if(data.name!==pkg.name||!/^\d+\.\d+\.\d+$/.test(data.version))throw Error('shape');
76
+ result={ok:true,version:pkg.version,latest:data.version,available:data.version!==pkg.version,checkedAt:Date.now(),channel:'latest'};
77
+ }catch{result={ok:false,code:'REGISTRY_UNAVAILABLE',version:pkg.version,checkedAt:Date.now(),retained:true};}
78
+ if(automatic) {
79
+ try {fs.mkdirSync(cacheRoot(),{recursive:true,mode:0o700});if(!exists(cache))writeNew(cache,result);else {const archive=cache+'.previous-'+crypto.randomUUID();fs.renameSync(cache,archive);writeNew(cache,result);}}catch{/* Detection must not block read-only work. */}
80
+ }
81
+ return result;
82
+ }
83
+ export async function updateInstall(options) {
84
+ const before=status(options);
85
+ if(before.installed&&(!before.managed||before.edited)) throw fail('LOCAL_EDITS','Resolve target ownership and edits before updating');
86
+ if(!options.yes) throw fail('CONFIRMATION_REQUIRED','Explicit update authorization requires --yes');
87
+ const selected=options.version??(await checkUpdate()).latest;
88
+ if(!/^\d+\.\d+\.\d+$/.test(selected??'')) throw fail('REGISTRY_UNAVAILABLE','Cannot resolve a stable version; installed components retained');
89
+ if(selected===pkg.version) return installSkill(options);
90
+ const directory=fs.mkdtempSync(path.join(os.tmpdir(),'ecom-cli-candidate-'));
91
+ const operationId=crypto.randomUUID();
92
+ const journal=options.target+'.ecom-update-'+operationId+'.json';
93
+ fs.mkdirSync(path.dirname(journal),{recursive:true});
94
+ writeNew(journal,{operationId,target:before.target,previousVersion:pkg.version,candidateVersion:selected,stage:'acquisition',candidate:directory});
95
+ const npm=process.platform==='win32'?'npm.cmd':'npm';
96
+ const acquired=spawnSync(npm,['install','--prefix',directory,'--ignore-scripts','--no-audit','--no-fund',pkg.name+'@'+selected],{encoding:'utf8',timeout:120000,shell:process.platform==='win32',env:{...process.env,npm_config_dry_run:'false',npm_config_json:'false'}});
97
+ if(acquired.status!==0) throw fail('PACKAGE_ACQUISITION_FAILED','Candidate installation failed; existing runtime and Skill retained',{operationId,recovery:{journal,stage:'acquisition',action:'Inspect candidate and operation record before retrying'}});
98
+ const candidate=path.join(directory,'node_modules/@petercjl/ecom-cli');
99
+ if(source(candidate).version!==selected) throw fail('VERSION_MISMATCH','Acquired candidate version differs from requested version',{operationId,recovery:{journal}});
100
+ const result=installSkill(options,candidate);
101
+ return {...result,operationId,journal,selectedVersion:selected,bootstrapRetained:true,activation:'Use the entry in this receipt; the selected Skill contains its own CLI runtime'};
102
+ }
package/package.json CHANGED
@@ -1 +1,37 @@
1
- {"name": "@petercjl/ecom-cli", "version": "0.0.0-bootstrap.0", "description": "Reserved Ecom CLI package for GitHub Actions Trusted Publishing setup", "license": "MPL-2.0", "files": ["README.md"], "publishConfig": {"access": "public"}, "repository": {"type": "git", "url": "git+https://github.com/petercjl/ecom.git"}}
1
+ {
2
+ "name": "@petercjl/ecom-cli",
3
+ "version": "0.1.1",
4
+ "description": "Agent CLI and portable Skill for Ecom browser containers",
5
+ "type": "module",
6
+ "license": "MPL-2.0",
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
10
+ "bin": {
11
+ "ecom-cli": "bin/ecom-cli.mjs"
12
+ },
13
+ "files": [
14
+ "bin",
15
+ "lib",
16
+ "skills",
17
+ "scripts",
18
+ "capabilities.json",
19
+ "INSTALL.md",
20
+ "UPDATE.md",
21
+ "LICENSE"
22
+ ],
23
+ "scripts": {
24
+ "test": "node --test test/*.test.mjs",
25
+ "postinstall": "node scripts/install-notice.mjs",
26
+ "prepack": "node scripts/validate.mjs",
27
+ "version": "node scripts/sync-version.mjs"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/petercjl/ecom.git",
35
+ "directory": "packages/ecom-cli"
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ process.stderr.write('Ecom CLI bootstrap installed. Complete CLI + Skill installation for your current Agent: discover ecom-cli skill source --json, resolve the exact Skill directory, run skill install --agent NAME --target PATH, then verify host discovery. See INSTALL.md.\n');
@@ -0,0 +1,13 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import crypto from 'node:crypto';
4
+ import os from 'node:os';
5
+ import {root,pkg} from '../lib/common.mjs';
6
+ for(const name of ['capabilities.json','skills/ecom-cli/package.json']) {
7
+ const file=path.join(root,name), info=fs.lstatSync(file);
8
+ if(!info.isFile()||info.isSymbolicLink())throw Error('Unsafe version metadata');
9
+ const value=JSON.parse(fs.readFileSync(file,'utf8'));
10
+ const backup=path.join(os.tmpdir(),'ecom-version-backup-'+crypto.randomUUID());fs.copyFileSync(file,backup,fs.constants.COPYFILE_EXCL);
11
+ value.version=pkg.version;
12
+ fs.writeFileSync(file,JSON.stringify(value,null,2)+'\n');
13
+ }
@@ -0,0 +1,10 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import assert from 'node:assert/strict';
4
+ import {root,pkg,tree} from '../lib/common.mjs';
5
+ const caps=JSON.parse(fs.readFileSync(path.join(root,'capabilities.json'),'utf8'));
6
+ const skill=JSON.parse(fs.readFileSync(path.join(root,'skills/ecom-cli/package.json'),'utf8'));
7
+ assert.equal(caps.version,pkg.version);assert.equal(skill.version,pkg.version);
8
+ assert.equal(skill.name,pkg.name);assert.ok(fs.statSync(path.join(root,'skills/ecom-cli/SKILL.md')).size>100);
9
+ tree(path.join(root,'skills')); // Reject shared links before packing.
10
+ console.error('Version, Skill identity and physical resource checks passed: '+pkg.version);
@@ -0,0 +1,38 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # Ecom CLI
7
+
8
+ ## Input → strategy → output
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.
11
+
12
+ ## Main line
13
+
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
+ 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.
18
+
19
+ ## Diagnosis and recovery
20
+
21
+ - 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
+ - INSTANCE_AMBIGUOUS: obtain the intended instance or profile, then return to step 3.
23
+ - INSTANCE_CHANGED: no request was submitted; rediscover before returning to step 3.
24
+ - UNSAFE_ENDPOINT, PROTOCOL_MISMATCH or BROWSER_ERROR: stop and report the exact code. Preserve browser state and request identity.
25
+ - OPERATION_TIMEOUT: retain the operation ID and report unknown completion. Inspect the instance and operation files under the protected endpoint before any retry; never blindly repeat an unknown operation.
26
+ - PLATFORM_UNSUPPORTED: report the limitation. Browser integration currently supports macOS. Windows package mechanics and generic host format are separate from actual browser support.
27
+
28
+ ## Installation and update branch
29
+
30
+ Enter this branch before business calls when the user requests installation or updates. Read the selected package's INSTALL.md / UPDATE.md from its discovered source, then return to main-line step 1 after maintenance.
31
+
32
+ Resolve the current Agent's exact Skill destination from host configuration; use its actual name with the generic adapter. `skill source --json` identifies canonical bytes. `skill status --agent NAME --target PATH --json` verifies the selected target without writes. Use `skill install` or `skill update` with those declared flags for physical isolated copies; installed copies contain their own complete runtime. Installation covers both CLI and Skill; npm bootstrap success alone is partial.
33
+
34
+ For an authorized package update, invoke the installed entry's `update install --agent NAME --target PATH --yes --json`. It acquires stable latest and activates CLI plus Skill together. Use the new entry from its receipt, verify CLI/Skill versions and SHA-256 `current=true`, then refresh and load this exact Skill through host discovery. Preserve receipts and backups. Local edits and unmanaged destinations require reconciliation before replacement; other Agent installations remain untouched. Automatic task-entry checks only notify; offline checks retain the installed runtime.
35
+
36
+ ## QA and evolution
37
+
38
+ Success requires actual structured CLI output, not inferred browser state. Maintenance additionally requires integrity plus native host discovery. Runtime checks do not claim clean-context regression or other Agents/OS tests. Add new commands to the browser capability layer, CLI schema, tests and this canonical Skill together in an authorized development cycle. This simple deterministic Skill uses direct internalization of user-confirmed scope and verified CLI contracts; private knowledge bases are not runtime dependencies.
@@ -0,0 +1,25 @@
1
+ {
2
+ "platform": "generic",
3
+ "status": "implemented",
4
+ "mappings": [
5
+ {
6
+ "implementation": {
7
+ "kind": "cli",
8
+ "contract": "Current host terminal executes available Node.js with the discovered Skill-root .runtime/bin/ecom-cli.mjs"
9
+ },
10
+ "status": "implemented",
11
+ "discovery": "Current host resolves exact Skill directory and Node.js runtime; probe CLI version and capabilities",
12
+ "normalization": "Preserve ok, containers, instance and error from JSON; attach exit status to metadata",
13
+ "permissions": "Local execution; explicit selected-target maintenance authorization",
14
+ "limitations": "Browser transport currently macOS only; host discovery evidence is separate",
15
+ "capability_id": "command.execute",
16
+ "features": [
17
+ "local-node-runtime",
18
+ "structured-stdout",
19
+ "exit-code"
20
+ ]
21
+ }
22
+ ],
23
+ "schema": "portable-skill-adapter",
24
+ "schema_version": "1.0.0"
25
+ }
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Ecom CLI"
3
+ short_description: "List isolated Ecom browser account containers"
4
+ default_prompt: "List my Ecom browser containers."
@@ -0,0 +1,44 @@
1
+ {
2
+ "schema_version": "1.0.0",
3
+ "target_platforms": [
4
+ "generic"
5
+ ],
6
+ "host_resolution": {
7
+ "fallback_adapter": "generic",
8
+ "authority": "host"
9
+ },
10
+ "capabilities": [
11
+ {
12
+ "id": "command.execute",
13
+ "purpose": "Run the isolated Ecom CLI",
14
+ "required": true,
15
+ "required_features": [
16
+ "local-node-runtime",
17
+ "structured-stdout",
18
+ "exit-code"
19
+ ],
20
+ "input_fields": [
21
+ "argv",
22
+ "skill_root"
23
+ ],
24
+ "output_fields": [
25
+ "ok",
26
+ "containers",
27
+ "instance",
28
+ "error"
29
+ ],
30
+ "side_effects": {
31
+ "external_mutation": false,
32
+ "may_cost_money": false,
33
+ "authorization": "Read-only browser request authorized by user; selected-target writes require installation/update intent"
34
+ },
35
+ "errors": [
36
+ "CAPABILITY_UNAVAILABLE",
37
+ "PLATFORM_UNSUPPORTED",
38
+ "INSTANCE_NOT_RUNNING"
39
+ ]
40
+ }
41
+ ],
42
+ "schema": "portable-skill-capabilities",
43
+ "skill": "ecom-cli"
44
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@petercjl/ecom-cli",
3
+ "version": "0.1.1",
4
+ "private": true,
5
+ "type": "module"
6
+ }