@forcome/ai-cli 0.1.1 → 0.1.5

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
@@ -8,7 +8,10 @@ Forcome AI 门户命令行——默认指向 https://ai.forcome.com,透传官
8
8
  ```bash
9
9
  npm i -g @forcome/ai-cli
10
10
  fai login # 浏览器钉钉扫码授权(首次)
11
+ fai connect --daemon # 让本机在 Web「执行设备」里保持在线
12
+ fai device status # 查看在线状态
11
13
  fai agent list # 列助理
14
+ fai agent run --agent-id <id> --device local -p "..." # 通过 Forcome agent gateway 调用本机
12
15
  fai gen text -m newapi/gpt-5.5 --stream "你好" # 生成文本(必须显式 newapi 模型 + --stream)
13
16
  ```
14
17
 
@@ -26,19 +29,30 @@ API key 模式仅支持 tRPC 命令(`agent` / `gen` / `provider` / `model`)
26
29
 
27
30
  ```bash
28
31
  LOBEHUB_SERVER=https://staging.forcome.com fai login
32
+ LOBEHUB_DEVICE_GATEWAY=https://staging.forcome.com/device-gateway fai connect
33
+ AGENT_GATEWAY_URL=https://staging.forcome.com/agent-gateway fai agent run --agent-id <id> --device local -p "..."
29
34
  ```
30
35
 
31
36
  ## 已知限制(phase 1)
32
37
 
33
38
  - `lh gen text` 默认模型 `openai/gpt-4o-mini` 在本门户不可用(`ENABLED_OPENAI=0`)——**必须 `-m newapi/<model>`**,且加 `--stream`(非 stream 模式 server 返 SSE 流致 JSON 解析失败)。可用模型见 `fai model list newapi`。
34
- - `fai agent run`(实时 agent 执行)、`fai connect`(设备配对)依赖未部署的 agent-gateway / device-gateway,暂不支持。
39
+ - `fai connect` / `fai status` 默认使用 `https://ai.forcome.com/device-gateway`。
40
+ - `fai agent run` 默认使用 `https://ai.forcome.com/agent-gateway`;否则官方 CLI 会回落到 `https://agent-gateway.lobehub.com`,自建站 token 会验签失败。
41
+ - Web 端通过 CLI 控制本机需要先运行 `fai connect --daemon`;仅 `fai login` 只会注册设备,不会让设备在线。
35
42
 
36
43
  ## 排障
37
44
 
38
45
  - `lh login` 的授权 URL 是 `http://192.168.16.157/...`(内网 IP)→ nginx `/oidc/` location 强制头丢了,见 [troubleshooting §8.109](../docs/troubleshooting.md)。
39
- - `fai: command not found` `未找到 lh 命令` `npm i -g @lobehub/cli`(`fai` 依赖它)。
46
+ - `fai: command not found` 确认 npm 全局 bin Windows 安装器写入的 `%LOCALAPPDATA%\ForcomeAI\cli\bin` 已进入当前终端 PATH。
40
47
  - `agent list` 报 procedure not found → CLI 与 server tRPC 版本漂移,pin 与当前 server 同源的 `@lobehub/cli`。
48
+ - **「接入助理」扫描失败 `Unknown tool API: <name>`** → 同一类版本漂移的另一个面:**工具 API** 漂移。
49
+ 该报错只由 `apps/cli/src/tools/index.ts`(CLI 自己的工具分发器)抛出,**与 device-gateway 无关**
50
+ (网关只按名字转发,不实现工具)。判据:把报错里的工具名拿去比对上游 `apps/cli/src/tools/`,
51
+ 看它是哪个版本引入的。实例:`scanHeterogeneousAgents` 由 #17794 引入、首发于 `@lobehub/cli 0.0.47`;
52
+ 服务端升到 2.2.14-canary.45 后网页会调它,而当时本包还 pin 在 `0.0.37` → 设备端不认识 → 扫描失败。
53
+ 修法 = 把 `package.json` 的 `@lobehub/cli` pin 提到与当前 `LOBEHUB_TAG` 同源的版本并重新发布。
54
+ - `agent run` 报 `Gateway auth failed: signature verification failed` → 确认走的是 `fai`,或显式设置 `AGENT_GATEWAY_URL=https://ai.forcome.com/agent-gateway`。
41
55
 
42
56
  ## 实现
43
57
 
44
- 不改 `lh` 源码——本包仅设置 `LOBEHUB_SERVER` 默认值后 `spawn('lh', argv)` 透传,`@lobehub/cli` 作为 `dependency` 自动跟随上游升级。
58
+ 不改 `lh` 源码——本包默认设置 `LOBEHUB_SERVER`、`LOBEHUB_DEVICE_GATEWAY`、`AGENT_GATEWAY_URL`,再用当前 Node 直接执行依赖内的 `@lobehub/cli` 入口;不依赖全局 PATH 上的 `lh`。
package/bin/fai.js CHANGED
@@ -4,15 +4,35 @@
4
4
  // 不用 isMain 守卫——本文件只作为 bin 运行(纯逻辑在 ../src/index.js)。
5
5
 
6
6
  import { spawn } from 'node:child_process';
7
- import { FORCOME_SERVER, resolveLhPath, resolveLhVersion, withLoginServer } from '../src/index.js';
7
+ import {
8
+ FORCOME_AGENT_GATEWAY,
9
+ FORCOME_DEVICE_GATEWAY,
10
+ FORCOME_SERVER,
11
+ resolveAgentGatewayUrl,
12
+ resolveGatewayUrl,
13
+ resolveLhPath,
14
+ resolveLhVersion,
15
+ withDeviceGateway,
16
+ withLoginServer,
17
+ } from '../src/index.js';
8
18
 
9
19
  // 默认指向 Forcome 门户(员工无需 --server);env LOBEHUB_SERVER 可覆盖(staging 等)
10
20
  if (!process.env.LOBEHUB_SERVER) {
11
21
  process.env.LOBEHUB_SERVER = FORCOME_SERVER;
12
22
  }
23
+ if (!process.env.LOBEHUB_DEVICE_GATEWAY) {
24
+ process.env.LOBEHUB_DEVICE_GATEWAY = FORCOME_DEVICE_GATEWAY;
25
+ }
26
+ if (!process.env.AGENT_GATEWAY_URL) {
27
+ process.env.AGENT_GATEWAY_URL = FORCOME_AGENT_GATEWAY;
28
+ }
13
29
 
14
30
  // lh 的 login 读 --server flag(不读 env,默认 app.lobehub.com)→ 必须显式注入,否则登错服务器
15
- const args = withLoginServer(process.argv.slice(2), process.env.LOBEHUB_SERVER);
31
+ const args = withDeviceGateway(
32
+ withLoginServer(process.argv.slice(2), process.env.LOBEHUB_SERVER),
33
+ resolveGatewayUrl(),
34
+ );
35
+ process.env.AGENT_GATEWAY_URL = resolveAgentGatewayUrl();
16
36
 
17
37
  // wrapper 自身状态(不透传给 lh)
18
38
  if (args[0] === '--forcome-version') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forcome/ai-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.5",
4
4
  "description": "Forcome AI 门户 CLI 薄壳——默认指向 ai.forcome.com,透传官方 @lobehub/cli (lh)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -38,7 +38,7 @@
38
38
  "smoke": "node bin/fai.js --forcome-version"
39
39
  },
40
40
  "dependencies": {
41
- "@lobehub/cli": "0.0.37"
41
+ "@lobehub/cli": "0.0.54"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=20"
package/src/index.js CHANGED
@@ -6,12 +6,23 @@ import { createRequire } from 'node:module';
6
6
  const require = createRequire(import.meta.url);
7
7
 
8
8
  export const FORCOME_SERVER = 'https://ai.forcome.com';
9
+ export const FORCOME_DEVICE_GATEWAY = 'https://ai.forcome.com/device-gateway';
10
+ export const FORCOME_AGENT_GATEWAY = 'https://ai.forcome.com/agent-gateway';
9
11
 
10
12
  /** 解析目标 server:env LOBEHUB_SERVER 优先,否则默认 Forcome 门户。 */
11
13
  export function resolveServerUrl() {
12
14
  return process.env.LOBEHUB_SERVER || FORCOME_SERVER;
13
15
  }
14
16
 
17
+ /** 解析 Forcome 自托管 device gateway;env 可覆盖到 staging。 */
18
+ export function resolveGatewayUrl() {
19
+ return process.env.LOBEHUB_DEVICE_GATEWAY || FORCOME_DEVICE_GATEWAY;
20
+ }
21
+
22
+ export function resolveAgentGatewayUrl() {
23
+ return process.env.AGENT_GATEWAY_URL || FORCOME_AGENT_GATEWAY;
24
+ }
25
+
15
26
  /** 从依赖解析官方 lh 入口(自包含,不依赖全局 PATH)。 */
16
27
  export function resolveLhPath() {
17
28
  return require.resolve('@lobehub/cli/dist/index.js');
@@ -38,3 +49,26 @@ export function withLoginServer(args, server) {
38
49
  }
39
50
  return args;
40
51
  }
52
+
53
+ export function withDeviceGateway(args, gateway) {
54
+ const hasGateway = args.some((arg) => arg === '--gateway' || arg.startsWith('--gateway='));
55
+ if (hasGateway) {
56
+ return args;
57
+ }
58
+
59
+ if (args[0] === 'status') {
60
+ return [args[0], '--gateway', gateway, ...args.slice(1)];
61
+ }
62
+
63
+ if (args[0] === 'connect') {
64
+ if (args[1] === 'restart') {
65
+ return [args[0], args[1], '--gateway', gateway, ...args.slice(2)];
66
+ }
67
+ if (args[1] === 'stop' || args[1] === 'status' || args[1] === 'logs') {
68
+ return args;
69
+ }
70
+ return [args[0], '--gateway', gateway, ...args.slice(1)];
71
+ }
72
+
73
+ return args;
74
+ }
package/src/index.test.js CHANGED
@@ -1,6 +1,15 @@
1
1
  import { test } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { resolveServerUrl, FORCOME_SERVER, withLoginServer } from './index.js';
3
+ import {
4
+ FORCOME_AGENT_GATEWAY,
5
+ FORCOME_DEVICE_GATEWAY,
6
+ FORCOME_SERVER,
7
+ resolveAgentGatewayUrl,
8
+ resolveGatewayUrl,
9
+ resolveServerUrl,
10
+ withDeviceGateway,
11
+ withLoginServer,
12
+ } from './index.js';
4
13
 
5
14
  test('resolveServerUrl 默认指向 Forcome 门户', () => {
6
15
  delete process.env.LOBEHUB_SERVER;
@@ -14,7 +23,32 @@ test('resolveServerUrl 受 LOBEHUB_SERVER env 覆盖(staging 等)', () => {
14
23
  delete process.env.LOBEHUB_SERVER;
15
24
  });
16
25
 
26
+ test('resolveGatewayUrl 默认指向 Forcome device gateway', () => {
27
+ delete process.env.LOBEHUB_DEVICE_GATEWAY;
28
+ assert.equal(resolveGatewayUrl(), FORCOME_DEVICE_GATEWAY);
29
+ assert.equal(resolveGatewayUrl(), 'https://ai.forcome.com/device-gateway');
30
+ });
31
+
32
+ test('resolveGatewayUrl 受 LOBEHUB_DEVICE_GATEWAY env 覆盖', () => {
33
+ process.env.LOBEHUB_DEVICE_GATEWAY = 'https://staging.forcome.com/device-gateway';
34
+ assert.equal(resolveGatewayUrl(), 'https://staging.forcome.com/device-gateway');
35
+ delete process.env.LOBEHUB_DEVICE_GATEWAY;
36
+ });
37
+
38
+ test('resolveAgentGatewayUrl 默认指向 Forcome agent gateway', () => {
39
+ delete process.env.AGENT_GATEWAY_URL;
40
+ assert.equal(resolveAgentGatewayUrl(), FORCOME_AGENT_GATEWAY);
41
+ assert.equal(resolveAgentGatewayUrl(), 'https://ai.forcome.com/agent-gateway');
42
+ });
43
+
44
+ test('resolveAgentGatewayUrl 受 AGENT_GATEWAY_URL env 覆盖', () => {
45
+ process.env.AGENT_GATEWAY_URL = 'https://staging.forcome.com/agent-gateway';
46
+ assert.equal(resolveAgentGatewayUrl(), 'https://staging.forcome.com/agent-gateway');
47
+ delete process.env.AGENT_GATEWAY_URL;
48
+ });
49
+
17
50
  const SRV = 'https://ai.forcome.com';
51
+ const GW = 'https://ai.forcome.com/device-gateway';
18
52
 
19
53
  test('withLoginServer: login 无 --server 时注入默认 server(避免登到 app.lobehub.com)', () => {
20
54
  assert.deepEqual(withLoginServer(['login'], SRV), ['login', '--server', SRV]);
@@ -31,3 +65,25 @@ test('withLoginServer: 非 login 命令不动(agent/gen/file 走 env)', () =
31
65
  assert.deepEqual(withLoginServer(['agent', 'list'], SRV), ['agent', 'list']);
32
66
  assert.deepEqual(withLoginServer(['gen', 'text', '-m', 'newapi/gpt-5.5'], SRV), ['gen', 'text', '-m', 'newapi/gpt-5.5']);
33
67
  });
68
+
69
+ test('withDeviceGateway: connect/status 无 --gateway 时注入默认 gateway', () => {
70
+ assert.deepEqual(withDeviceGateway(['connect', '--daemon'], GW), ['connect', '--gateway', GW, '--daemon']);
71
+ assert.deepEqual(withDeviceGateway(['status'], GW), ['status', '--gateway', GW]);
72
+ assert.deepEqual(withDeviceGateway(['connect', 'restart'], GW), ['connect', 'restart', '--gateway', GW]);
73
+ });
74
+
75
+ test('withDeviceGateway: 已带 --gateway 或非 gateway 命令不动', () => {
76
+ assert.deepEqual(
77
+ withDeviceGateway(['connect', '--gateway', 'https://staging.forcome.com/device-gateway'], GW),
78
+ ['connect', '--gateway', 'https://staging.forcome.com/device-gateway'],
79
+ );
80
+ assert.deepEqual(
81
+ withDeviceGateway(['connect', 'restart', '--gateway=https://staging.forcome.com/device-gateway'], GW),
82
+ ['connect', 'restart', '--gateway=https://staging.forcome.com/device-gateway'],
83
+ );
84
+ assert.deepEqual(withDeviceGateway(['connect', 'status'], GW), ['connect', 'status']);
85
+ assert.deepEqual(withDeviceGateway(['connect', 'stop'], GW), ['connect', 'stop']);
86
+ assert.deepEqual(withDeviceGateway(['connect', 'logs'], GW), ['connect', 'logs']);
87
+ assert.deepEqual(withDeviceGateway(['device', 'status', '--json'], GW), ['device', 'status', '--json']);
88
+ assert.deepEqual(withDeviceGateway(['agent', 'list'], GW), ['agent', 'list']);
89
+ });