grix-connector 1.0.2
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 +149 -0
- package/dist/adapter/acp/acp-adapter.js +13 -0
- package/dist/adapter/acp/index.js +1 -0
- package/dist/adapter/acp/usage-parser.js +1 -0
- package/dist/adapter/claude/activity-status-manager.js +1 -0
- package/dist/adapter/claude/channel-notification.js +1 -0
- package/dist/adapter/claude/claude-adapter.js +15 -0
- package/dist/adapter/claude/claude-bridge-server.js +1 -0
- package/dist/adapter/claude/claude-tools.js +1 -0
- package/dist/adapter/claude/claude-worker-client.js +1 -0
- package/dist/adapter/claude/index.js +1 -0
- package/dist/adapter/claude/interaction-protocol.js +1 -0
- package/dist/adapter/claude/mcp-http-launcher.js +2 -0
- package/dist/adapter/claude/model-list.js +1 -0
- package/dist/adapter/claude/protocol-contract.js +1 -0
- package/dist/adapter/claude/result-timeout.js +1 -0
- package/dist/adapter/claude/skill-scanner.js +2 -0
- package/dist/adapter/claude/usage-parser.js +3 -0
- package/dist/adapter/codewhale/codewhale-adapter.js +6 -0
- package/dist/adapter/codewhale/index.js +1 -0
- package/dist/adapter/codex/codex-bridge.js +10 -0
- package/dist/adapter/codex/codex-trust.js +8 -0
- package/dist/adapter/codex/index.js +1 -0
- package/dist/adapter/codex/usage-parser.js +1 -0
- package/dist/adapter/cursor/cursor-adapter.js +8 -0
- package/dist/adapter/cursor/index.js +1 -0
- package/dist/adapter/deepseek/deepseek-adapter.js +6 -0
- package/dist/adapter/deepseek/index.js +1 -0
- package/dist/adapter/index.js +1 -0
- package/dist/adapter/opencode/index.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +8 -0
- package/dist/adapter/opencode/opencode-transport.js +5 -0
- package/dist/adapter/opencode/opencode-types.js +0 -0
- package/dist/adapter/openhuman/index.js +1 -0
- package/dist/adapter/openhuman/openhuman-adapter.js +7 -0
- package/dist/adapter/openhuman/openhuman-transport.js +1 -0
- package/dist/adapter/openhuman/openhuman-types.js +0 -0
- package/dist/adapter/pi/index.js +1 -0
- package/dist/adapter/pi/pi-adapter.js +10 -0
- package/dist/adapter/pi/pi-transport.js +4 -0
- package/dist/adapter/pi/pi-types.js +0 -0
- package/dist/adapter/pi/usage-parser.js +1 -0
- package/dist/adapter/qwen/index.js +1 -0
- package/dist/adapter/qwen/qwen-adapter.js +4 -0
- package/dist/adapter/types.js +1 -0
- package/dist/agent/index.js +1 -0
- package/dist/agent/process.js +2 -0
- package/dist/aibot/client.js +1 -0
- package/dist/aibot/index.js +1 -0
- package/dist/aibot/types.js +0 -0
- package/dist/bridge/adapter-pool.js +1 -0
- package/dist/bridge/bridge.js +10 -0
- package/dist/bridge/deferred-events.js +1 -0
- package/dist/bridge/event-queue.js +1 -0
- package/dist/bridge/index.js +1 -0
- package/dist/bridge/respawn-manager.js +1 -0
- package/dist/bridge/revoke-handler.js +1 -0
- package/dist/bridge/runtime-config.js +1 -0
- package/dist/bridge/send-controller.js +1 -0
- package/dist/bridge/session-controller.js +9 -0
- package/dist/bridge/tool-card-utils.js +1 -0
- package/dist/core/access/allowlist-gate.js +1 -0
- package/dist/core/access/allowlist-store.js +1 -0
- package/dist/core/access/index.js +1 -0
- package/dist/core/aibot/client.js +1 -0
- package/dist/core/aibot/connection-handle.js +1 -0
- package/dist/core/aibot/connection-manager.js +1 -0
- package/dist/core/aibot/event-lifecycle-types.js +0 -0
- package/dist/core/aibot/index.js +1 -0
- package/dist/core/aibot/types.js +0 -0
- package/dist/core/config/index.js +1 -0
- package/dist/core/config/paths.js +1 -0
- package/dist/core/context/channel-context-resolution.js +1 -0
- package/dist/core/context/channel-context-store.js +1 -0
- package/dist/core/context/index.js +1 -0
- package/dist/core/context/transcript-channel-context.js +1 -0
- package/dist/core/file-ops/handler.js +1 -0
- package/dist/core/file-ops/list-files.js +1 -0
- package/dist/core/file-ops/types.js +0 -0
- package/dist/core/files/create-folder.js +1 -0
- package/dist/core/files/index.js +1 -0
- package/dist/core/files/list-files.js +1 -0
- package/dist/core/files/list-handler.js +1 -0
- package/dist/core/files/types.js +0 -0
- package/dist/core/files/utils.js +1 -0
- package/dist/core/hooks/hook-signal-store.js +2 -0
- package/dist/core/hooks/index.js +1 -0
- package/dist/core/log/bridge-event-log.js +2 -0
- package/dist/core/log/conversation-log.js +3 -0
- package/dist/core/log/index.js +1 -0
- package/dist/core/log/logger.js +6 -0
- package/dist/core/log/packet-log.js +2 -0
- package/dist/core/log/rotation.js +2 -0
- package/dist/core/mcp/event-tool-executor.js +1 -0
- package/dist/core/mcp/index.js +1 -0
- package/dist/core/mcp/internal-api-server.js +1 -0
- package/dist/core/mcp/tool-schemas.js +1 -0
- package/dist/core/mcp/tools.js +1 -0
- package/dist/core/persistence/active-event-store.js +1 -0
- package/dist/core/persistence/agent-global-config-store.js +1 -0
- package/dist/core/persistence/elicitation-store.js +1 -0
- package/dist/core/persistence/event-results-store.js +1 -0
- package/dist/core/persistence/permission-store.js +1 -0
- package/dist/core/persistence/question-store.js +1 -0
- package/dist/core/persistence/session-binding-store.js +1 -0
- package/dist/core/protocol/agent-api-media.js +1 -0
- package/dist/core/protocol/attachment-file.js +1 -0
- package/dist/core/protocol/index.js +1 -0
- package/dist/core/protocol/interaction-parser.js +1 -0
- package/dist/core/protocol/message-metadata.js +2 -0
- package/dist/core/protocol/message-reference.js +2 -0
- package/dist/core/protocol/payload-parser.js +11 -0
- package/dist/core/protocol/protocol-descriptor.js +1 -0
- package/dist/core/protocol/protocol-text.js +1 -0
- package/dist/core/provider-quota/index.js +1 -0
- package/dist/core/provider-quota/kiro.js +1 -0
- package/dist/core/provider-quota/providers.js +1 -0
- package/dist/core/provider-quota/types.js +0 -0
- package/dist/core/runtime/health.js +1 -0
- package/dist/core/runtime/index.js +1 -0
- package/dist/core/runtime/pidfile.js +2 -0
- package/dist/core/runtime/spawn.js +1 -0
- package/dist/core/text-segmentation/index.js +1 -0
- package/dist/core/text-segmentation/safe-markdown-stream-segmenter.js +6 -0
- package/dist/core/transport/index.js +1 -0
- package/dist/core/transport/json-rpc.js +3 -0
- package/dist/core/upgrade/npm-upgrader.js +2 -0
- package/dist/core/upgrade/upgrade-checker.js +1 -0
- package/dist/core/util/client-version.js +1 -0
- package/dist/core/util/codex-output-policy.js +1 -0
- package/dist/core/util/event-buffer.js +1 -0
- package/dist/core/util/index.js +1 -0
- package/dist/core/util/json-file.js +2 -0
- package/dist/core/util/normalize-string.js +1 -0
- package/dist/core/util/quoted-message-stream.js +3 -0
- package/dist/grix.js +28 -0
- package/dist/index.js +1 -0
- package/dist/log.js +3 -0
- package/dist/main.js +31 -0
- package/dist/manager.js +1 -0
- package/dist/mcp/acp-mcp-server.js +5 -0
- package/dist/mcp/stdio/server.js +10 -0
- package/dist/mcp/stream-http/config.js +1 -0
- package/dist/mcp/stream-http/connection-binding.js +1 -0
- package/dist/mcp/stream-http/event-tool-executor.js +1 -0
- package/dist/mcp/stream-http/gateway.js +1 -0
- package/dist/mcp/stream-http/index.js +1 -0
- package/dist/mcp/stream-http/security.js +1 -0
- package/dist/mcp/stream-http/session-manager.js +1 -0
- package/dist/mcp/stream-http/tool-executor.js +1 -0
- package/dist/mcp/stream-http/tool-registry.js +1 -0
- package/dist/mcp/stream-http/tool-schemas.js +1 -0
- package/dist/protocol/acp-client.js +1 -0
- package/dist/protocol/event-mapper.js +5 -0
- package/dist/protocol/index.js +1 -0
- package/dist/runtime/daemon-lock.js +2 -0
- package/dist/runtime/service-state.js +2 -0
- package/dist/scripts/approve-plan-hook.js +2 -0
- package/dist/scripts/elicitation-hook.js +6 -0
- package/dist/scripts/lib/read-stdin.js +1 -0
- package/dist/scripts/lifecycle-hook.js +2 -0
- package/dist/scripts/notification-hook.js +4 -0
- package/dist/scripts/permission-hook.js +5 -0
- package/dist/scripts/status-line-forwarder.js +2 -0
- package/dist/scripts/user-prompt-submit-hook.js +2 -0
- package/dist/service/platform-adapter.js +45 -0
- package/dist/service/process-control.js +1 -0
- package/dist/service/service-install-store.js +1 -0
- package/dist/service/service-manager.js +1 -0
- package/dist/service/service-paths.js +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/manager.js +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/json-rpc.js +3 -0
- package/dist/types/events.js +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/protocol.js +0 -0
- package/dist/types/session-state.js +0 -0
- package/dist/types/usage.js +0 -0
- package/openclaw-plugin/index.js +11271 -0
- package/openclaw-plugin/skills/grix-admin/SKILL.md +202 -0
- package/openclaw-plugin/skills/grix-admin/references/api-contract.md +210 -0
- package/openclaw-plugin/skills/grix-egg/SKILL.md +81 -0
- package/openclaw-plugin/skills/grix-egg/references/api-contract.md +40 -0
- package/openclaw-plugin/skills/grix-group/SKILL.md +164 -0
- package/openclaw-plugin/skills/grix-group/references/api-contract.md +97 -0
- package/openclaw-plugin/skills/grix-query/SKILL.md +247 -0
- package/openclaw-plugin/skills/grix-register/SKILL.md +86 -0
- package/openclaw-plugin/skills/grix-register/references/api-contract.md +76 -0
- package/openclaw-plugin/skills/grix-register/references/grix-concepts.md +26 -0
- package/openclaw-plugin/skills/grix-register/references/handoff-contract.md +24 -0
- package/openclaw-plugin/skills/grix-register/references/openclaw-setup.md +6 -0
- package/openclaw-plugin/skills/grix-register/references/user-replies.md +25 -0
- package/openclaw-plugin/skills/grix-register/scripts/grix_auth.ts +599 -0
- package/openclaw-plugin/skills/grix-update/SKILL.md +310 -0
- package/openclaw-plugin/skills/grix-update/references/cron-setup.md +56 -0
- package/openclaw-plugin/skills/grix-update/references/update-contract.md +149 -0
- package/openclaw-plugin/skills/message-send/SKILL.md +197 -0
- package/openclaw-plugin/skills/message-unsend/SKILL.md +186 -0
- package/openclaw-plugin/skills/message-unsend/flowchart.mermaid +27 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/SKILL.md +282 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/case-study-macpro.md +52 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/references/host-readiness.md +147 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/bench_ollama_embeddings.ts +326 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/set_openclaw_memory_model.ts +385 -0
- package/openclaw-plugin/skills/openclaw-memory-setup/scripts/survey_host_readiness.ts +294 -0
- package/openclaw.plugin.json +24 -0
- package/package.json +114 -0
- package/scripts/install-guardian.mjs +30 -0
- package/scripts/install-guardian.sh +30 -0
- package/scripts/upgrade-guardian.sh +98 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Handoff Contract to grix-admin
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
`grix-register` 完成账号与首个 Agent 参数准备后,统一把本地配置工作交给 `grix-admin`。
|
|
6
|
+
|
|
7
|
+
## Required `grix_admin` Task
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
bind-local
|
|
11
|
+
agent_name=grix-main
|
|
12
|
+
agent_id=2029786829095440384
|
|
13
|
+
api_endpoint=wss://grix.dhf.pub/v1/agent-api/ws?agent_id=2029786829095440384
|
|
14
|
+
api_key=ak_xxx
|
|
15
|
+
do_not_create_remote_agent=true
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Rules
|
|
19
|
+
|
|
20
|
+
1. 入口固定走 `grix_admin` 的 `task` 参数,不直接把这组字段当成 typed params 调用。
|
|
21
|
+
2. 第一行固定为 `bind-local`。
|
|
22
|
+
3. `agent_name`、`agent_id`、`api_endpoint`、`api_key` 必填。
|
|
23
|
+
4. `grix-register` 只负责生成以上参数,不执行本地配置命令。
|
|
24
|
+
5. 本地写入、插件处理、工具权限、热加载校验都由 `grix-admin` 负责。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# User Replies
|
|
2
|
+
|
|
3
|
+
## One-liner Pitch
|
|
4
|
+
|
|
5
|
+
我会先帮你完成注册并拿到第一个 agent 参数,然后交给 `grix-admin` 完成本地配置。
|
|
6
|
+
|
|
7
|
+
## Short Intro
|
|
8
|
+
|
|
9
|
+
`grix-register` 只负责账号和云端参数准备,不改本地配置;本地配置由 `grix-admin` 接手。
|
|
10
|
+
|
|
11
|
+
## Ready Reply
|
|
12
|
+
|
|
13
|
+
账号和首个 agent 参数已经准备好,接下来我会把参数交给 `grix-admin` 做本地配置。
|
|
14
|
+
|
|
15
|
+
## Main Ready, Admin Pending Reply
|
|
16
|
+
|
|
17
|
+
`grix-register` 阶段已完成,我现在只做参数移交,后续本地配置请由 `grix-admin` 继续。
|
|
18
|
+
|
|
19
|
+
## Configured Now Reply
|
|
20
|
+
|
|
21
|
+
参数已交接给 `grix-admin`,接下来由它完成本地配置。
|
|
22
|
+
|
|
23
|
+
## Needs Setup Reply
|
|
24
|
+
|
|
25
|
+
当前仍在 `grix-register` 阶段,我会继续完成注册并拿到第一个 agent 参数。
|
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Grix public auth API helper (TypeScript).
|
|
4
|
+
*
|
|
5
|
+
* Notes:
|
|
6
|
+
* - Do not read any environment variables.
|
|
7
|
+
* - Emits JSON to stdout for success/error, except --help.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { writeFile } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
type JsonObject = Record<string, unknown>;
|
|
16
|
+
|
|
17
|
+
const DEFAULT_BASE_URL = "https://grix.dhf.pub";
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
19
|
+
|
|
20
|
+
class GrixAuthError extends Error {
|
|
21
|
+
status: number;
|
|
22
|
+
code: number;
|
|
23
|
+
payload: unknown;
|
|
24
|
+
|
|
25
|
+
constructor(message: string, status = 0, code = -1, payload: unknown = null) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "GrixAuthError";
|
|
28
|
+
this.status = status;
|
|
29
|
+
this.code = code;
|
|
30
|
+
this.payload = payload;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function printJson(payload: unknown): void {
|
|
35
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function usageText(): string {
|
|
39
|
+
return [
|
|
40
|
+
"Usage:",
|
|
41
|
+
" node scripts/grix_auth.ts [--base-url <url>] <action> [options]",
|
|
42
|
+
"",
|
|
43
|
+
"Actions:",
|
|
44
|
+
" fetch-captcha",
|
|
45
|
+
" send-email-code --email <email> --scene <register|reset|change_password> [--captcha-id <id>] [--captcha-value <value>]",
|
|
46
|
+
" register --email <email> --password <password> --email-code <code> [--device-id <id>] [--platform <platform>]",
|
|
47
|
+
" login (--account <account>|--email <email>) --password <password> [--device-id <id>] [--platform <platform>]",
|
|
48
|
+
" create-api-agent --access-token <token> --agent-name <name> [--avatar-url <url>] [--no-reuse-existing-agent] [--no-rotate-key-on-reuse]",
|
|
49
|
+
].join("\n");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function normalizeBaseUrl(rawBaseUrl: string): string {
|
|
53
|
+
const base = (rawBaseUrl ?? "").trim() || DEFAULT_BASE_URL;
|
|
54
|
+
let parsed: URL;
|
|
55
|
+
try {
|
|
56
|
+
parsed = new URL(base);
|
|
57
|
+
} catch {
|
|
58
|
+
throw new Error(`Invalid base URL: ${base}`);
|
|
59
|
+
}
|
|
60
|
+
if (!parsed.protocol || !parsed.host) {
|
|
61
|
+
throw new Error(`Invalid base URL: ${base}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let path = parsed.pathname.replace(/\/+$/, "");
|
|
65
|
+
if (!path) {
|
|
66
|
+
path = "/v1";
|
|
67
|
+
} else if (!path.endsWith("/v1")) {
|
|
68
|
+
path = `${path}/v1`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
parsed.pathname = path;
|
|
72
|
+
parsed.search = "";
|
|
73
|
+
parsed.hash = "";
|
|
74
|
+
|
|
75
|
+
return parsed.toString().replace(/\/$/, "");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function derivePortalUrl(rawBaseUrl: string): string {
|
|
79
|
+
const base = (rawBaseUrl ?? "").trim() || DEFAULT_BASE_URL;
|
|
80
|
+
let parsed: URL;
|
|
81
|
+
try {
|
|
82
|
+
parsed = new URL(base);
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error(`Invalid base URL: ${base}`);
|
|
85
|
+
}
|
|
86
|
+
if (!parsed.protocol || !parsed.host) {
|
|
87
|
+
throw new Error(`Invalid base URL: ${base}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let path = parsed.pathname.replace(/\/+$/, "");
|
|
91
|
+
if (path.endsWith("/v1")) {
|
|
92
|
+
path = path.slice(0, -"/v1".length);
|
|
93
|
+
}
|
|
94
|
+
path = path.replace(/\/+$/, "");
|
|
95
|
+
if (!path) {
|
|
96
|
+
path = "/";
|
|
97
|
+
}
|
|
98
|
+
parsed.pathname = path.endsWith("/") ? path : `${path}/`;
|
|
99
|
+
parsed.search = "";
|
|
100
|
+
parsed.hash = "";
|
|
101
|
+
return parsed.toString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function fetchWithTimeout(url: string, init: RequestInit, timeoutMs: number): Promise<Response> {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
107
|
+
try {
|
|
108
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
109
|
+
} finally {
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function requestJson(params: {
|
|
115
|
+
method: string;
|
|
116
|
+
path: string;
|
|
117
|
+
baseUrl: string;
|
|
118
|
+
body?: unknown;
|
|
119
|
+
headers?: Record<string, string>;
|
|
120
|
+
}): Promise<{ api_base_url: string; status: number; data: unknown; payload: unknown }> {
|
|
121
|
+
const apiBaseUrl = normalizeBaseUrl(params.baseUrl);
|
|
122
|
+
const normalizedPath = params.path.startsWith("/") ? params.path : `/${params.path}`;
|
|
123
|
+
const url = `${apiBaseUrl}${normalizedPath}`;
|
|
124
|
+
|
|
125
|
+
const headers: Record<string, string> = { ...(params.headers ?? {}) };
|
|
126
|
+
let body: string | undefined;
|
|
127
|
+
if (params.body !== undefined) {
|
|
128
|
+
body = JSON.stringify(params.body);
|
|
129
|
+
headers["Content-Type"] = "application/json";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let response: Response;
|
|
133
|
+
try {
|
|
134
|
+
response = await fetchWithTimeout(
|
|
135
|
+
url,
|
|
136
|
+
{
|
|
137
|
+
method: params.method,
|
|
138
|
+
headers,
|
|
139
|
+
body,
|
|
140
|
+
},
|
|
141
|
+
DEFAULT_TIMEOUT_MS,
|
|
142
|
+
);
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
throw new GrixAuthError(`network error: ${message}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const status = response.status;
|
|
149
|
+
const raw = await response.text();
|
|
150
|
+
|
|
151
|
+
let payload: unknown;
|
|
152
|
+
try {
|
|
153
|
+
payload = JSON.parse(raw);
|
|
154
|
+
} catch (err) {
|
|
155
|
+
const excerpt = raw.slice(0, 256);
|
|
156
|
+
throw new GrixAuthError(
|
|
157
|
+
`invalid json response: ${excerpt}`,
|
|
158
|
+
status,
|
|
159
|
+
-1,
|
|
160
|
+
err instanceof Error ? err.message : String(err),
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const obj = (payload ?? {}) as JsonObject;
|
|
165
|
+
const code = Number(obj.code ?? -1);
|
|
166
|
+
const msg = String(obj.msg ?? "").trim() || "unknown error";
|
|
167
|
+
if (status >= 400 || code !== 0) {
|
|
168
|
+
throw new GrixAuthError(msg, status, code, payload);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
api_base_url: apiBaseUrl,
|
|
173
|
+
status,
|
|
174
|
+
data: obj.data,
|
|
175
|
+
payload,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function maybeWriteCaptchaImage(b64s: string): Promise<string> {
|
|
180
|
+
const text = (b64s ?? "").trim();
|
|
181
|
+
if (!text.startsWith("data:image/")) {
|
|
182
|
+
return "";
|
|
183
|
+
}
|
|
184
|
+
const marker = ";base64,";
|
|
185
|
+
const idx = text.indexOf(marker);
|
|
186
|
+
if (idx < 0) {
|
|
187
|
+
return "";
|
|
188
|
+
}
|
|
189
|
+
const encoded = text.slice(idx + marker.length);
|
|
190
|
+
let content: Buffer;
|
|
191
|
+
try {
|
|
192
|
+
content = Buffer.from(encoded, "base64");
|
|
193
|
+
} catch {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const path = join(tmpdir(), `grix-captcha-${randomUUID()}.png`);
|
|
198
|
+
try {
|
|
199
|
+
await writeFile(path, content);
|
|
200
|
+
} catch {
|
|
201
|
+
return "";
|
|
202
|
+
}
|
|
203
|
+
return path;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function buildAuthResult(action: string, result: { api_base_url: string; data: unknown }, baseUrl: string) {
|
|
207
|
+
const data = (result.data ?? {}) as JsonObject;
|
|
208
|
+
const user = (data.user ?? {}) as JsonObject;
|
|
209
|
+
return {
|
|
210
|
+
ok: true,
|
|
211
|
+
action,
|
|
212
|
+
api_base_url: result.api_base_url,
|
|
213
|
+
access_token: String(data.access_token ?? ""),
|
|
214
|
+
refresh_token: String(data.refresh_token ?? ""),
|
|
215
|
+
expires_in: Number(data.expires_in ?? 0),
|
|
216
|
+
user_id: String(user.id ?? ""),
|
|
217
|
+
portal_url: derivePortalUrl(baseUrl),
|
|
218
|
+
data,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function buildAgentResult(action: string, result: { api_base_url: string; data: unknown }) {
|
|
223
|
+
const data = (result.data ?? {}) as JsonObject;
|
|
224
|
+
const agent_id = String(data.id ?? "").trim();
|
|
225
|
+
const api_endpoint = String(data.api_endpoint ?? "").trim();
|
|
226
|
+
const api_key = String(data.api_key ?? "").trim();
|
|
227
|
+
const agent_name = String(data.agent_name ?? "").trim();
|
|
228
|
+
|
|
229
|
+
const bind_local = {
|
|
230
|
+
agent_name,
|
|
231
|
+
agent_id,
|
|
232
|
+
api_endpoint,
|
|
233
|
+
api_key,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const task = [
|
|
237
|
+
"bind-local",
|
|
238
|
+
`agent_name=${agent_name}`,
|
|
239
|
+
`agent_id=${agent_id}`,
|
|
240
|
+
`api_endpoint=${api_endpoint}`,
|
|
241
|
+
`api_key=${api_key}`,
|
|
242
|
+
"do_not_create_remote_agent=true",
|
|
243
|
+
].join("\n");
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
action,
|
|
248
|
+
api_base_url: result.api_base_url,
|
|
249
|
+
agent_id,
|
|
250
|
+
agent_name,
|
|
251
|
+
provider_type: Number(data.provider_type ?? 0),
|
|
252
|
+
api_endpoint,
|
|
253
|
+
api_key,
|
|
254
|
+
api_key_hint: String(data.api_key_hint ?? ""),
|
|
255
|
+
session_id: String(data.session_id ?? ""),
|
|
256
|
+
handoff: {
|
|
257
|
+
target_tool: "grix_admin",
|
|
258
|
+
task,
|
|
259
|
+
bind_local,
|
|
260
|
+
},
|
|
261
|
+
data,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function defaultDeviceId(platform: string): string {
|
|
266
|
+
const normalized = (platform ?? "").trim() || "web";
|
|
267
|
+
return `${normalized}_${randomUUID()}`;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function loginWithCredentials(params: {
|
|
271
|
+
baseUrl: string;
|
|
272
|
+
account: string;
|
|
273
|
+
password: string;
|
|
274
|
+
deviceId: string;
|
|
275
|
+
platform: string;
|
|
276
|
+
}) {
|
|
277
|
+
const result = await requestJson({
|
|
278
|
+
method: "POST",
|
|
279
|
+
path: "/auth/login",
|
|
280
|
+
baseUrl: params.baseUrl,
|
|
281
|
+
body: {
|
|
282
|
+
account: params.account,
|
|
283
|
+
password: params.password,
|
|
284
|
+
device_id: params.deviceId,
|
|
285
|
+
platform: params.platform,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
return buildAuthResult("login", result, params.baseUrl);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function listAgents(baseUrl: string, accessToken: string): Promise<JsonObject[]> {
|
|
292
|
+
const result = await requestJson({
|
|
293
|
+
method: "GET",
|
|
294
|
+
path: "/agents/list",
|
|
295
|
+
baseUrl,
|
|
296
|
+
headers: {
|
|
297
|
+
Authorization: `Bearer ${accessToken.trim()}`,
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
const data = (result.data ?? {}) as JsonObject;
|
|
301
|
+
const items = data.list;
|
|
302
|
+
return Array.isArray(items) ? (items as JsonObject[]) : [];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function findExistingApiAgent(agents: JsonObject[], agentName: string): JsonObject | null {
|
|
306
|
+
const normalizedName = (agentName ?? "").trim();
|
|
307
|
+
if (!normalizedName) {
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
for (const item of agents) {
|
|
311
|
+
if (String(item.agent_name ?? "").trim() !== normalizedName) {
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (Number(item.provider_type ?? 0) !== 3) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (Number(item.status ?? 0) === 3) {
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
return item;
|
|
321
|
+
}
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function rotateApiAgentKey(baseUrl: string, accessToken: string, agentId: string) {
|
|
326
|
+
const result = await requestJson({
|
|
327
|
+
method: "POST",
|
|
328
|
+
path: `/agents/${agentId.trim()}/api/key/rotate`,
|
|
329
|
+
baseUrl,
|
|
330
|
+
body: {},
|
|
331
|
+
headers: {
|
|
332
|
+
Authorization: `Bearer ${accessToken.trim()}`,
|
|
333
|
+
},
|
|
334
|
+
});
|
|
335
|
+
return buildAgentResult("rotate-api-agent-key", result);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function createApiAgent(params: {
|
|
339
|
+
baseUrl: string;
|
|
340
|
+
accessToken: string;
|
|
341
|
+
agentName: string;
|
|
342
|
+
avatarUrl: string;
|
|
343
|
+
}) {
|
|
344
|
+
const body: JsonObject = {
|
|
345
|
+
agent_name: params.agentName.trim(),
|
|
346
|
+
provider_type: 3,
|
|
347
|
+
is_main: true,
|
|
348
|
+
};
|
|
349
|
+
const avatar = (params.avatarUrl ?? "").trim();
|
|
350
|
+
if (avatar) {
|
|
351
|
+
body.avatar_url = avatar;
|
|
352
|
+
}
|
|
353
|
+
const result = await requestJson({
|
|
354
|
+
method: "POST",
|
|
355
|
+
path: "/agents/create",
|
|
356
|
+
baseUrl: params.baseUrl,
|
|
357
|
+
body,
|
|
358
|
+
headers: {
|
|
359
|
+
Authorization: `Bearer ${params.accessToken.trim()}`,
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
return buildAgentResult("create-api-agent", result);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function createOrReuseApiAgent(params: {
|
|
366
|
+
baseUrl: string;
|
|
367
|
+
accessToken: string;
|
|
368
|
+
agentName: string;
|
|
369
|
+
avatarUrl: string;
|
|
370
|
+
preferExisting: boolean;
|
|
371
|
+
rotateOnReuse: boolean;
|
|
372
|
+
}): Promise<JsonObject> {
|
|
373
|
+
if (params.preferExisting) {
|
|
374
|
+
const agents = await listAgents(params.baseUrl, params.accessToken);
|
|
375
|
+
const existing = findExistingApiAgent(agents, params.agentName);
|
|
376
|
+
if (existing) {
|
|
377
|
+
if (!params.rotateOnReuse) {
|
|
378
|
+
throw new GrixAuthError(
|
|
379
|
+
"existing provider_type=3 agent found but rotate-on-reuse is disabled; cannot obtain api_key safely",
|
|
380
|
+
0,
|
|
381
|
+
-1,
|
|
382
|
+
{ existing_agent: existing },
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
const rotated = (await rotateApiAgentKey(
|
|
386
|
+
params.baseUrl,
|
|
387
|
+
params.accessToken,
|
|
388
|
+
String(existing.id ?? "").trim(),
|
|
389
|
+
)) as JsonObject;
|
|
390
|
+
rotated.source = "reused_existing_agent_with_rotated_key";
|
|
391
|
+
rotated.existing_agent = existing;
|
|
392
|
+
return rotated;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const created = (await createApiAgent({
|
|
397
|
+
baseUrl: params.baseUrl,
|
|
398
|
+
accessToken: params.accessToken,
|
|
399
|
+
agentName: params.agentName,
|
|
400
|
+
avatarUrl: params.avatarUrl,
|
|
401
|
+
})) as JsonObject;
|
|
402
|
+
created.source = "created_new_agent";
|
|
403
|
+
return created;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function takeOption(argv: string[], name: string): string | undefined {
|
|
407
|
+
const prefix = `--${name}=`;
|
|
408
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
409
|
+
const token = argv[index] ?? "";
|
|
410
|
+
if (token === `--${name}`) {
|
|
411
|
+
const value = argv[index + 1];
|
|
412
|
+
argv.splice(index, 2);
|
|
413
|
+
return value;
|
|
414
|
+
}
|
|
415
|
+
if (token.startsWith(prefix)) {
|
|
416
|
+
argv.splice(index, 1);
|
|
417
|
+
return token.slice(prefix.length);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function takeBool(argv: string[], name: string): boolean {
|
|
424
|
+
const token = `--${name}`;
|
|
425
|
+
const index = argv.indexOf(token);
|
|
426
|
+
if (index >= 0) {
|
|
427
|
+
argv.splice(index, 1);
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function requireOption(argv: string[], name: string): string {
|
|
434
|
+
const value = takeOption(argv, name);
|
|
435
|
+
if (value === undefined || String(value).trim() === "") {
|
|
436
|
+
throw new GrixAuthError(`missing required option: --${name}`);
|
|
437
|
+
}
|
|
438
|
+
return String(value);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function runAction(action: string, baseUrl: string, argv: string[]): Promise<void> {
|
|
442
|
+
if (action === "fetch-captcha") {
|
|
443
|
+
const result = await requestJson({ method: "GET", path: "/auth/captcha", baseUrl });
|
|
444
|
+
const data = (result.data ?? {}) as JsonObject;
|
|
445
|
+
const b64s = String(data.b64s ?? "");
|
|
446
|
+
const imagePath = await maybeWriteCaptchaImage(b64s);
|
|
447
|
+
const payload: JsonObject = {
|
|
448
|
+
ok: true,
|
|
449
|
+
action: "fetch-captcha",
|
|
450
|
+
api_base_url: result.api_base_url,
|
|
451
|
+
captcha_id: String(data.captcha_id ?? ""),
|
|
452
|
+
b64s,
|
|
453
|
+
};
|
|
454
|
+
if (imagePath) {
|
|
455
|
+
payload.captcha_image_path = imagePath;
|
|
456
|
+
}
|
|
457
|
+
printJson(payload);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
if (action === "send-email-code") {
|
|
462
|
+
const email = requireOption(argv, "email").trim();
|
|
463
|
+
const scene = requireOption(argv, "scene").trim();
|
|
464
|
+
if (!["register", "reset", "change_password"].includes(scene)) {
|
|
465
|
+
throw new GrixAuthError(`invalid scene: ${scene}`);
|
|
466
|
+
}
|
|
467
|
+
const payload: JsonObject = { email, scene };
|
|
468
|
+
const captchaId = String(takeOption(argv, "captcha-id") ?? "").trim();
|
|
469
|
+
const captchaValue = String(takeOption(argv, "captcha-value") ?? "").trim();
|
|
470
|
+
if ((scene === "reset" || scene === "change_password") && (!captchaId || !captchaValue)) {
|
|
471
|
+
throw new GrixAuthError("captcha-id and captcha-value are required for reset/change_password");
|
|
472
|
+
}
|
|
473
|
+
if (captchaId) payload.captcha_id = captchaId;
|
|
474
|
+
if (captchaValue) payload.captcha_value = captchaValue;
|
|
475
|
+
|
|
476
|
+
const result = await requestJson({
|
|
477
|
+
method: "POST",
|
|
478
|
+
path: "/auth/send-code",
|
|
479
|
+
baseUrl,
|
|
480
|
+
body: payload,
|
|
481
|
+
});
|
|
482
|
+
printJson({
|
|
483
|
+
ok: true,
|
|
484
|
+
action: "send-email-code",
|
|
485
|
+
api_base_url: result.api_base_url,
|
|
486
|
+
data: result.data,
|
|
487
|
+
});
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (action === "register") {
|
|
492
|
+
const email = requireOption(argv, "email").trim();
|
|
493
|
+
const password = requireOption(argv, "password").trim();
|
|
494
|
+
const emailCode = requireOption(argv, "email-code").trim();
|
|
495
|
+
const platform = String(takeOption(argv, "platform") ?? "").trim() || "web";
|
|
496
|
+
const deviceId = String(takeOption(argv, "device-id") ?? "").trim() || defaultDeviceId(platform);
|
|
497
|
+
|
|
498
|
+
const result = await requestJson({
|
|
499
|
+
method: "POST",
|
|
500
|
+
path: "/auth/register",
|
|
501
|
+
baseUrl,
|
|
502
|
+
body: {
|
|
503
|
+
email,
|
|
504
|
+
password,
|
|
505
|
+
email_code: emailCode,
|
|
506
|
+
device_id: deviceId,
|
|
507
|
+
platform,
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
printJson(buildAuthResult("register", result, baseUrl));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (action === "login") {
|
|
515
|
+
const account = String(takeOption(argv, "email") ?? takeOption(argv, "account") ?? "").trim();
|
|
516
|
+
if (!account) {
|
|
517
|
+
throw new GrixAuthError("either --email or --account is required");
|
|
518
|
+
}
|
|
519
|
+
const password = requireOption(argv, "password").trim();
|
|
520
|
+
const platform = String(takeOption(argv, "platform") ?? "").trim() || "web";
|
|
521
|
+
const deviceId = String(takeOption(argv, "device-id") ?? "").trim() || defaultDeviceId(platform);
|
|
522
|
+
|
|
523
|
+
const result = await loginWithCredentials({
|
|
524
|
+
baseUrl,
|
|
525
|
+
account,
|
|
526
|
+
password,
|
|
527
|
+
deviceId,
|
|
528
|
+
platform,
|
|
529
|
+
});
|
|
530
|
+
printJson(result);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (action === "create-api-agent") {
|
|
535
|
+
const accessToken = requireOption(argv, "access-token").trim();
|
|
536
|
+
const agentName = requireOption(argv, "agent-name").trim();
|
|
537
|
+
const avatarUrl = String(takeOption(argv, "avatar-url") ?? "").trim();
|
|
538
|
+
const noReuseExistingAgent = takeBool(argv, "no-reuse-existing-agent");
|
|
539
|
+
const noRotateKeyOnReuse = takeBool(argv, "no-rotate-key-on-reuse");
|
|
540
|
+
|
|
541
|
+
const result = await createOrReuseApiAgent({
|
|
542
|
+
baseUrl,
|
|
543
|
+
accessToken,
|
|
544
|
+
agentName,
|
|
545
|
+
avatarUrl,
|
|
546
|
+
preferExisting: !noReuseExistingAgent,
|
|
547
|
+
rotateOnReuse: !noRotateKeyOnReuse,
|
|
548
|
+
});
|
|
549
|
+
printJson(result);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
throw new GrixAuthError(`unsupported action: ${action}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
async function main(): Promise<number> {
|
|
557
|
+
const argv = process.argv.slice(2);
|
|
558
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
559
|
+
process.stdout.write(`${usageText()}\n`);
|
|
560
|
+
return 0;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
const baseUrl = String(takeOption(argv, "base-url") ?? DEFAULT_BASE_URL).trim() || DEFAULT_BASE_URL;
|
|
564
|
+
const action = String(argv.shift() ?? "").trim();
|
|
565
|
+
if (!action) {
|
|
566
|
+
process.stderr.write(`${usageText()}\n`);
|
|
567
|
+
return 1;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
try {
|
|
571
|
+
await runAction(action, baseUrl, argv);
|
|
572
|
+
return 0;
|
|
573
|
+
} catch (err) {
|
|
574
|
+
if (err instanceof GrixAuthError) {
|
|
575
|
+
printJson({
|
|
576
|
+
ok: false,
|
|
577
|
+
action,
|
|
578
|
+
status: err.status,
|
|
579
|
+
code: err.code,
|
|
580
|
+
error: err.message,
|
|
581
|
+
payload: err.payload,
|
|
582
|
+
});
|
|
583
|
+
return 1;
|
|
584
|
+
}
|
|
585
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
586
|
+
printJson({
|
|
587
|
+
ok: false,
|
|
588
|
+
action,
|
|
589
|
+
status: 0,
|
|
590
|
+
code: -1,
|
|
591
|
+
error: message,
|
|
592
|
+
});
|
|
593
|
+
return 1;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
void main().then((code) => {
|
|
598
|
+
process.exitCode = code;
|
|
599
|
+
});
|