@encorearia/install 1.0.0 → 1.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/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
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import process from 'node:process';
3
+ import { readFileSync } from 'node:fs';
3
4
  import { request, ensureDeviceLogin } from '../lib/api.mjs';
4
5
  import { atomicInstall } from '../lib/atomic-install.mjs';
5
6
  import { loadCredentials } from '../lib/credentials.mjs';
@@ -9,53 +10,52 @@ import { loadTrustedKeys, verifyPackage } from '../lib/verify.mjs';
9
10
  const args = process.argv.slice(2);
10
11
  const baseURL = String(valueAfter('--api') || process.env.ENCOREARIA_API_BASE || 'https://encorearia.com').replace(/\/$/, '');
11
12
  const targetArg = valueAfter('--target');
12
- const installerVersion = '1.0.0';
13
+ const installerVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
13
14
 
14
15
  if (args.includes('--list')) {
15
16
  const target = (await detectTargets(targetArg ? targetArg.split(',') : []))[0];
16
17
  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
- }
18
+ if (!credentials?.access_token) credentials = await ensureDeviceLogin(baseURL, '', 'list', target);
22
19
  let services;
23
- try { services = await request(baseURL, '/api/agent/services', {}, credentials); }
24
- catch (error) {
25
- const loginService = valueAfter('--service');
26
- if (!loginService) throw new Error(`设备授权已失效;请使用 --service <id> 重新授权。${error.message}`);
27
- credentials = await ensureDeviceLogin(baseURL, loginService, 'list', target);
20
+ try {
21
+ services = await request(baseURL, '/api/agent/services', {}, credentials);
22
+ } catch {
23
+ credentials = await ensureDeviceLogin(baseURL, '', 'list', target);
28
24
  services = await request(baseURL, '/api/agent/services', {}, credentials);
29
25
  }
30
26
  for (const item of services) {
31
- process.stdout.write(`${item.service_public_id}\t${item.title}\t${item.currency} ${(item.price_cents / 100).toFixed(2)}\t${item.active_installs} installs\n`);
27
+ const serviceRef = item.slug || item.service_public_id;
28
+ const trial = item.trial_available ? '\ttrial' : '';
29
+ process.stdout.write(`${serviceRef}\t${item.title}\t${item.currency} ${(item.price_cents / 100).toFixed(2)}\t${item.summary || ''}${trial}\n`);
32
30
  }
33
31
  process.exit(0);
34
32
  }
35
33
 
36
- const serviceID = positionalServiceID();
37
- if (!serviceID) fail('Usage: npx @encorearia/install <service_public_id> [--target codex,claude-code] [--api URL]');
38
- const pkg = await request(baseURL, '/api/callable-services/' + encodeURIComponent(serviceID) + '/shell');
34
+ if (positionalArgument()) fail('用法:npx @encorearia/install [--target codex,claude-code] [--api URL]');
35
+ const pkg = await request(baseURL, '/api/agent/shell');
39
36
  const trust = await loadTrustedKeys(baseURL);
40
37
  verifyPackage(pkg, trust, installerVersion);
41
38
  const targets = await detectTargets(targetArg ? targetArg.split(',') : []);
42
39
  const primaryTarget = targets[0];
43
- const credentials = await ensureDeviceLogin(baseURL, serviceID, pkg.manifest.shell_version, primaryTarget);
40
+ await ensureDeviceLogin(baseURL, '', pkg.manifest.shell_version, primaryTarget);
41
+ const shellID = pkg.manifest.shell_id || 'encorearia';
44
42
  for (const target of targets) {
45
- const destination = await atomicInstall(targetRoot(target), serviceID, pkg.files, pkg.manifest);
46
- await request(baseURL, '/api/agent/installs', { method: 'POST', body: JSON.stringify({
47
- service_public_id: serviceID, shell_version: pkg.manifest.shell_version, client_target: target,
48
- }) }, credentials);
49
- process.stdout.write(`已安装到 ${target}: ${destination}\n`);
43
+ const destination = await atomicInstall(targetRoot(target), shellID, pkg.files, pkg.manifest);
44
+ process.stdout.write(`已安装到 ${displayTarget(target)}:${destination}\n`);
50
45
  }
46
+ process.stdout.write('\n安可集已就绪。你可以直接对 AI 说:\n');
47
+ process.stdout.write(' · 看看我的服务\n');
48
+ process.stdout.write(' · 帮我调用 <服务名>\n');
49
+ process.stdout.write(' · 查我的钱包余额\n');
50
+ process.stdout.write('浏览更多服务或充值:https://encorearia.com\n');
51
51
 
52
52
  function valueAfter(flag) {
53
53
  const index = args.indexOf(flag);
54
54
  return index >= 0 ? args[index + 1] : '';
55
55
  }
56
56
 
57
- function positionalServiceID() {
58
- const valuedFlags = new Set(['--api', '--target', '--service']);
57
+ function positionalArgument() {
58
+ const valuedFlags = new Set(['--api', '--target']);
59
59
  for (let index = 0; index < args.length; index += 1) {
60
60
  const value = args[index];
61
61
  if (valuedFlags.has(value)) { index += 1; continue; }
@@ -64,6 +64,12 @@ function positionalServiceID() {
64
64
  return '';
65
65
  }
66
66
 
67
+ function displayTarget(target) {
68
+ if (target === 'codex') return 'Codex';
69
+ if (target === 'claude-code') return 'Claude Code';
70
+ return target;
71
+ }
72
+
67
73
  function fail(message) {
68
74
  process.stderr.write(message + '\n');
69
75
  process.exit(1);
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 start = await request(baseURL, '/api/agent/device/start', { method: 'POST', body: JSON.stringify({
73
- service_public_id: serviceID, shell_version: shellVersion, client_name: os.hostname(), client_target: clientTarget,
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;
@@ -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, serviceID, files, manifest = null) {
5
- if (!/^[A-Za-z0-9_-]{1,100}$/.test(serviceID)) throw new Error('Unsafe service identifier.');
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, serviceID);
8
- const temporary = path.join(root, `.${serviceID}.tmp-${process.pid}-${Date.now()}`);
9
- const backup = path.join(root, `.${serviceID}.backup-${process.pid}-${Date.now()}`);
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
- if (current.service_public_id !== manifest.service_public_id) throw new Error('Installed service identity does not match the signed package.');
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]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@encorearia/install",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Verify and install signed Encore Aria call-shell skills for Codex and Claude Code.",
5
5
  "type": "module",
6
6
  "bin": {