@encorearia/install 1.0.0 → 1.1.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 +17 -0
- package/bin/install.mjs +17 -23
- package/lib/api.mjs +5 -3
- package/lib/atomic-install.mjs +9 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,23 @@ the trust anchor is empty instead of accepting an API-supplied key. It is the
|
|
|
33
33
|
expected repository state until the first production release key has been
|
|
34
34
|
created securely.
|
|
35
35
|
|
|
36
|
+
## Install the unified entry
|
|
37
|
+
|
|
38
|
+
Install one account-level Encore Aria shell. It can list the services available
|
|
39
|
+
on the platform and call any published service by its short slug or public id.
|
|
40
|
+
It does not embed a creator or service identifier in the installed files.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
npx @encorearia/install --target codex,claude-code
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The first run opens the device approval page. After approval, use `list` in the
|
|
47
|
+
installed shell to view service slugs, then invoke a service with its slug. The
|
|
48
|
+
installer stores one account-level credential at `~/.encorearia/credentials.json`.
|
|
49
|
+
|
|
50
|
+
Use `npx @encorearia/install --list` to authenticate if necessary and print the
|
|
51
|
+
currently available service list.
|
|
52
|
+
|
|
36
53
|
## Rotate a key
|
|
37
54
|
|
|
38
55
|
1. Generate a new key with a new `KEY_ID`.
|
package/bin/install.mjs
CHANGED
|
@@ -9,44 +9,38 @@ import { loadTrustedKeys, verifyPackage } from '../lib/verify.mjs';
|
|
|
9
9
|
const args = process.argv.slice(2);
|
|
10
10
|
const baseURL = String(valueAfter('--api') || process.env.ENCOREARIA_API_BASE || 'https://encorearia.com').replace(/\/$/, '');
|
|
11
11
|
const targetArg = valueAfter('--target');
|
|
12
|
-
const installerVersion = '1.
|
|
12
|
+
const installerVersion = '1.1.0';
|
|
13
13
|
|
|
14
14
|
if (args.includes('--list')) {
|
|
15
15
|
const target = (await detectTargets(targetArg ? targetArg.split(',') : []))[0];
|
|
16
16
|
let credentials = await loadCredentials();
|
|
17
|
-
if (!credentials?.access_token)
|
|
18
|
-
const loginService = valueAfter('--service');
|
|
19
|
-
if (!loginService) fail('--list 首次使用需要同时提供 --service <id>');
|
|
20
|
-
credentials = await ensureDeviceLogin(baseURL, loginService, 'list', target);
|
|
21
|
-
}
|
|
17
|
+
if (!credentials?.access_token) credentials = await ensureDeviceLogin(baseURL, '', 'list', target);
|
|
22
18
|
let services;
|
|
23
|
-
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
credentials = await ensureDeviceLogin(baseURL, loginService, 'list', target);
|
|
19
|
+
try {
|
|
20
|
+
services = await request(baseURL, '/api/agent/services', {}, credentials);
|
|
21
|
+
} catch {
|
|
22
|
+
credentials = await ensureDeviceLogin(baseURL, '', 'list', target);
|
|
28
23
|
services = await request(baseURL, '/api/agent/services', {}, credentials);
|
|
29
24
|
}
|
|
30
25
|
for (const item of services) {
|
|
31
|
-
|
|
26
|
+
const serviceRef = item.slug || item.service_public_id;
|
|
27
|
+
const trial = item.trial_available ? '\ttrial' : '';
|
|
28
|
+
process.stdout.write(`${serviceRef}\t${item.title}\t${item.currency} ${(item.price_cents / 100).toFixed(2)}\t${item.summary || ''}${trial}\n`);
|
|
32
29
|
}
|
|
33
30
|
process.exit(0);
|
|
34
31
|
}
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const pkg = await request(baseURL, '/api/callable-services/' + encodeURIComponent(serviceID) + '/shell');
|
|
33
|
+
if (positionalArgument()) fail('Usage: npx @encorearia/install [--target codex,claude-code] [--api URL]');
|
|
34
|
+
const pkg = await request(baseURL, '/api/agent/shell');
|
|
39
35
|
const trust = await loadTrustedKeys(baseURL);
|
|
40
36
|
verifyPackage(pkg, trust, installerVersion);
|
|
41
37
|
const targets = await detectTargets(targetArg ? targetArg.split(',') : []);
|
|
42
38
|
const primaryTarget = targets[0];
|
|
43
|
-
|
|
39
|
+
await ensureDeviceLogin(baseURL, '', pkg.manifest.shell_version, primaryTarget);
|
|
40
|
+
const shellID = pkg.manifest.shell_id || 'encorearia-unified';
|
|
44
41
|
for (const target of targets) {
|
|
45
|
-
const destination = await atomicInstall(targetRoot(target),
|
|
46
|
-
|
|
47
|
-
service_public_id: serviceID, shell_version: pkg.manifest.shell_version, client_target: target,
|
|
48
|
-
}) }, credentials);
|
|
49
|
-
process.stdout.write(`已安装到 ${target}: ${destination}\n`);
|
|
42
|
+
const destination = await atomicInstall(targetRoot(target), shellID, pkg.files, pkg.manifest);
|
|
43
|
+
process.stdout.write(`Installed to ${target}: ${destination}\n`);
|
|
50
44
|
}
|
|
51
45
|
|
|
52
46
|
function valueAfter(flag) {
|
|
@@ -54,8 +48,8 @@ function valueAfter(flag) {
|
|
|
54
48
|
return index >= 0 ? args[index + 1] : '';
|
|
55
49
|
}
|
|
56
50
|
|
|
57
|
-
function
|
|
58
|
-
const valuedFlags = new Set(['--api', '--target'
|
|
51
|
+
function positionalArgument() {
|
|
52
|
+
const valuedFlags = new Set(['--api', '--target']);
|
|
59
53
|
for (let index = 0; index < args.length; index += 1) {
|
|
60
54
|
const value = args[index];
|
|
61
55
|
if (valuedFlags.has(value)) { index += 1; continue; }
|
package/lib/api.mjs
CHANGED
|
@@ -69,9 +69,11 @@ export async function ensureDeviceLogin(baseURL, serviceID, shellVersion, client
|
|
|
69
69
|
if (credentials?.access_token) {
|
|
70
70
|
try { await request(baseURL, '/api/agent/me', {}, credentials); return credentials; } catch {}
|
|
71
71
|
}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
}
|
|
72
|
+
const payload = {
|
|
73
|
+
shell_version: shellVersion, client_name: os.hostname(), client_target: clientTarget,
|
|
74
|
+
};
|
|
75
|
+
if (serviceID) payload.service_public_id = serviceID;
|
|
76
|
+
const start = await request(baseURL, '/api/agent/device/start', { method: 'POST', body: JSON.stringify(payload) });
|
|
75
77
|
process.stdout.write(`请在浏览器确认设备:${start.verification_uri_complete}\n验证码:${start.user_code}\n`);
|
|
76
78
|
openBrowser(start.verification_uri_complete);
|
|
77
79
|
let interval = start.interval_seconds || 5;
|
package/lib/atomic-install.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
-
export async function atomicInstall(root,
|
|
5
|
-
if (!/^[A-Za-z0-9_-]{1,100}$/.test(
|
|
4
|
+
export async function atomicInstall(root, shellID, files, manifest = null) {
|
|
5
|
+
if (!/^[A-Za-z0-9_-]{1,100}$/.test(shellID)) throw new Error('Unsafe shell identifier.');
|
|
6
6
|
await fs.mkdir(root, { recursive: true, mode: 0o700 });
|
|
7
|
-
const destination = path.join(root,
|
|
8
|
-
const temporary = path.join(root, `.${
|
|
9
|
-
const backup = path.join(root, `.${
|
|
7
|
+
const destination = path.join(root, shellID);
|
|
8
|
+
const temporary = path.join(root, `.${shellID}.tmp-${process.pid}-${Date.now()}`);
|
|
9
|
+
const backup = path.join(root, `.${shellID}.backup-${process.pid}-${Date.now()}`);
|
|
10
10
|
if (manifest) await assertNoRollback(destination, manifest);
|
|
11
11
|
await fs.mkdir(temporary, { recursive: false, mode: 0o700 });
|
|
12
12
|
try {
|
|
@@ -18,6 +18,7 @@ export async function atomicInstall(root, serviceID, files, manifest = null) {
|
|
|
18
18
|
}
|
|
19
19
|
if (manifest) {
|
|
20
20
|
await fs.writeFile(path.join(temporary, '.encorearia-release.json'), JSON.stringify({
|
|
21
|
+
shell_id: manifest.shell_id || manifest.service_public_id,
|
|
21
22
|
service_public_id: manifest.service_public_id,
|
|
22
23
|
shell_version: manifest.shell_version,
|
|
23
24
|
signing_key_id: manifest.signing_key_id,
|
|
@@ -44,7 +45,9 @@ async function assertNoRollback(destination, manifest) {
|
|
|
44
45
|
let current;
|
|
45
46
|
try { current = JSON.parse(await fs.readFile(path.join(destination, '.encorearia-release.json'), 'utf8')); }
|
|
46
47
|
catch { return; }
|
|
47
|
-
|
|
48
|
+
const currentIdentity = current.shell_id || current.service_public_id;
|
|
49
|
+
const incomingIdentity = manifest.shell_id || manifest.service_public_id;
|
|
50
|
+
if (!currentIdentity || currentIdentity !== incomingIdentity) throw new Error('Installed shell identity does not match the signed package.');
|
|
48
51
|
const oldSequence = releaseSequence(current.shell_version);
|
|
49
52
|
const newSequence = releaseSequence(manifest.shell_version);
|
|
50
53
|
const olderSequence = newSequence[0] < oldSequence[0] || (newSequence[0] === oldSequence[0] && newSequence[1] < oldSequence[1]);
|