@dsh-plus/actual 0.1.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/lib/client.js +400 -141
- package/lib/index.d.ts +8 -0
- package/lib/index.js +80 -20
- package/package.json +3 -3
- package/src/client/card.tsx +11 -19
- package/src/client/client.ts +41 -4
- package/src/client/i18n.ts +31 -12
- package/src/client/secrets-section.tsx +126 -0
- package/src/client/secrets.ts +132 -0
- package/src/client/styles.ts +3 -2
- package/src/config.ts +9 -9
- package/src/credentials.ts +77 -0
- package/src/refs.ts +23 -0
- package/src/service.ts +24 -2
package/src/config.ts
CHANGED
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
* loader 以活动引用原位提交热更新——消费端必须 `unwrapVolatile` 现取即热
|
|
10
10
|
* (见 service.ts 的 `current()`)。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
* `.role('secret')`,describe
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* **密钥不落 settings**:`password` / `sessionToken` / `encryptionPassword` 标
|
|
13
|
+
* `.role('secret')`,describe 视图剥除其值、写入用户层也会被遮蔽,因此不提供
|
|
14
|
+
* settings 编辑器。它们的落点有二:① 配置卡片的「凭据」小节(走官方
|
|
15
|
+
* credentials seam,写 `$DSH_HOME/.credentials.yaml`);② profile 行级注入
|
|
16
|
+
* (`!!js process.env.…`)。行级声明了任一种认证即整组生效,否则回落到 seam。
|
|
17
|
+
* 见《插件存储规范》与 credentials.ts。
|
|
16
18
|
* @module @dsh-plus/actual/config
|
|
17
19
|
*/
|
|
18
20
|
|
|
@@ -61,22 +63,20 @@ export const Config = z.object({
|
|
|
61
63
|
.string()
|
|
62
64
|
.role('secret')
|
|
63
65
|
.description(
|
|
64
|
-
'
|
|
66
|
+
'服务器口令(与 sessionToken 二选一)——不落 settings:卡片「凭据」小节写凭据文件,或在此行级注入',
|
|
65
67
|
)
|
|
66
68
|
.default('')
|
|
67
69
|
.volatile(),
|
|
68
70
|
sessionToken: z
|
|
69
71
|
.string()
|
|
70
72
|
.role('secret')
|
|
71
|
-
.description('会话令牌(优先于 password
|
|
73
|
+
.description('会话令牌(优先于 password)——同上,卡片的「凭据」小节或行级注入')
|
|
72
74
|
.default('')
|
|
73
75
|
.volatile(),
|
|
74
76
|
encryptionPassword: z
|
|
75
77
|
.string()
|
|
76
78
|
.role('secret')
|
|
77
|
-
.description(
|
|
78
|
-
'端到端加密口令(仅加密预算需要;空 = 用 ACTUAL_ENCRYPTION_PASSWORD)——仅 profile 行级注入',
|
|
79
|
-
)
|
|
79
|
+
.description('端到端加密口令(仅加密预算需要)——同上,卡片的「凭据」小节或行级注入')
|
|
80
80
|
.default('')
|
|
81
81
|
.volatile(),
|
|
82
82
|
dataDir: z
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 凭据解析:Actual 的三个密钥走官方 credentials seam
|
|
3
|
+
* (`$DSH_HOME/.credentials.yaml`,经 `ctx.credentials`),**每次操作即时
|
|
4
|
+
* resolve、不跨操作缓存**——在配置卡片里改口令后,下一次工具调用即生效。
|
|
5
|
+
*
|
|
6
|
+
* 引用名即官方 CLI 原生读取的环境变量名(见 refs.ts 与 `@dsh-plus/actual-mcp`
|
|
7
|
+
* 的 `SECRET_ENV`):`ACTUAL_` 前缀符合《插件存储规范》的 `<PLUGIN_ID>_` 大写
|
|
8
|
+
* 约定,同时让 seam 的继承环境层与 CLI 自己的环境读取指向同一处配置——
|
|
9
|
+
* 无论口令来自 `.credentials.yaml` 还是进程环境,两条路径都拿得到。
|
|
10
|
+
*
|
|
11
|
+
* 优先级:**行级 Config 声明了任一种认证即整组生效**。`role('secret')` 字段
|
|
12
|
+
* 是部署方在 profile 里的显式意图,不该被 seam 里的另一半悄悄改写;只有在
|
|
13
|
+
* 行级配置没声明认证时才去问 seam。端到端加密口令与认证正交,单独解析。
|
|
14
|
+
* @module @dsh-plus/actual/credentials
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { CliSecrets } from '@dsh-plus/actual-mcp'
|
|
18
|
+
|
|
19
|
+
import { REF_ENCRYPTION_PASSWORD, REF_PASSWORD, REF_SESSION_TOKEN } from './refs.ts'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* credentials seam 的最小面。
|
|
23
|
+
*
|
|
24
|
+
* 只声明用到的 `resolve`:值的写入发生在浏览器半(官方 `remote.credentials`
|
|
25
|
+
* 命名空间),宿主半不需要 set/unset。
|
|
26
|
+
*
|
|
27
|
+
* 引用名以 `unknown` 收、按普通字符串传:`CredentialRef` 是
|
|
28
|
+
* `string & { readonly [BRAND]: B }` 的**纯编译期**品牌(`BRAND` 只有类型声明,
|
|
29
|
+
* 无运行期取值,官方文档亦声明 `brandString` 不改动值),故传裸环境变量名与
|
|
30
|
+
* 经 `credentialRef()` 品牌化在运行期完全等价。这样本插件不必依赖
|
|
31
|
+
* `@deepseek-ai/dsh-credentials` 的包树,在未挂该 seam 的 profile 里也能 load。
|
|
32
|
+
*/
|
|
33
|
+
export interface CredentialsFace {
|
|
34
|
+
/** 解析一个引用;未配置返回 undefined。 */
|
|
35
|
+
resolve(ref: unknown): Promise<{ value: string; source: string } | undefined>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 行级 Config 声明的密钥(空串 = 未声明)。 */
|
|
39
|
+
export type DeclaredSecrets = CliSecrets
|
|
40
|
+
|
|
41
|
+
/** 解析单个引用;seam 缺席或未配置一律视为空串。 */
|
|
42
|
+
async function resolveOne(seam: CredentialsFace | null, ref: string): Promise<string> {
|
|
43
|
+
if (seam === null) return ''
|
|
44
|
+
const found = await seam.resolve(ref)
|
|
45
|
+
return found === undefined ? '' : found.value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 解析本次调用使用的密钥三元组。
|
|
50
|
+
* @param seam - 凭据 seam;缺席(profile 未挂 dsh-credentials)时只用行级配置。
|
|
51
|
+
* @param declared - 行级 Config 声明的密钥。
|
|
52
|
+
* @returns 已定妥优先级的密钥三元组。
|
|
53
|
+
*/
|
|
54
|
+
export async function resolveSecrets(
|
|
55
|
+
seam: CredentialsFace | null,
|
|
56
|
+
declared: DeclaredSecrets,
|
|
57
|
+
): Promise<CliSecrets> {
|
|
58
|
+
// 行级 Config 是显式意图:声明了口令或令牌就整组作数,不再混入 seam 的另一半。
|
|
59
|
+
const authDeclared = declared.password !== '' || declared.sessionToken !== ''
|
|
60
|
+
const password =
|
|
61
|
+
declared.password !== ''
|
|
62
|
+
? declared.password
|
|
63
|
+
: authDeclared
|
|
64
|
+
? ''
|
|
65
|
+
: await resolveOne(seam, REF_PASSWORD)
|
|
66
|
+
const sessionToken =
|
|
67
|
+
declared.sessionToken !== ''
|
|
68
|
+
? declared.sessionToken
|
|
69
|
+
: authDeclared
|
|
70
|
+
? ''
|
|
71
|
+
: await resolveOne(seam, REF_SESSION_TOKEN)
|
|
72
|
+
const encryptionPassword =
|
|
73
|
+
declared.encryptionPassword !== ''
|
|
74
|
+
? declared.encryptionPassword
|
|
75
|
+
: await resolveOne(seam, REF_ENCRYPTION_PASSWORD)
|
|
76
|
+
return { password, sessionToken, encryptionPassword }
|
|
77
|
+
}
|
package/src/refs.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 凭据引用名字面量(纯常量,零依赖)。
|
|
3
|
+
*
|
|
4
|
+
* 宿主半(credentials.ts 的 seam 解析)与浏览器半(配置卡片的
|
|
5
|
+
* `remote.credentials` 读写)共用同一份取值;浏览器包不得经此路径拖入
|
|
6
|
+
* `@dsh-plus/actual-mcp` 的 node 侧实现(child_process 等),故字面量独立成模块。
|
|
7
|
+
*
|
|
8
|
+
* **必须与 `@dsh-plus/actual-mcp` 的 `SECRET_ENV` 逐字一致**:引用名与官方 CLI
|
|
9
|
+
* 原生读取的环境变量名同形(`ACTUAL_` 前缀亦符合《插件存储规范》的
|
|
10
|
+
* `<PLUGIN_ID>_` 大写约定),凭据文件里的一处配置才能同时被 seam 解析、
|
|
11
|
+
* 又被 CLI 自身继承环境读到。该一致性由 `tests/refs.test.ts` 钉住。
|
|
12
|
+
* @module @dsh-plus/actual/refs
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** 服务器口令。 */
|
|
16
|
+
export const REF_PASSWORD = 'ACTUAL_PASSWORD'
|
|
17
|
+
/** 会话令牌(官方 CLI 优先于口令)。 */
|
|
18
|
+
export const REF_SESSION_TOKEN = 'ACTUAL_SESSION_TOKEN'
|
|
19
|
+
/** 端到端加密口令(仅加密预算需要)。 */
|
|
20
|
+
export const REF_ENCRYPTION_PASSWORD = 'ACTUAL_ENCRYPTION_PASSWORD'
|
|
21
|
+
|
|
22
|
+
/** 卡片展示与批量 describe 使用的引用清单(顺序即展示顺序)。 */
|
|
23
|
+
export const CREDENTIAL_REFS = [REF_PASSWORD, REF_SESSION_TOKEN, REF_ENCRYPTION_PASSWORD] as const
|
package/src/service.ts
CHANGED
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
* 后立即按新配置重新发现。纯展示/策略字段(name/description/order/persona 等)
|
|
9
9
|
* 不触发重建,它们在下一次预设注册或工具注册时自然生效。
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* 密钥解析每次操作即时进行:行级 Config 声明的优先,否则问官方 credentials
|
|
12
|
+
* seam(`$DSH_HOME/.credentials.yaml`,浏览器半经官方 `remote.credentials`
|
|
13
|
+
* 命名空间写入)——改口令后下一次工具调用即生效,无需 /reload 或重启。
|
|
14
|
+
* 日志与状态均经脱敏。数据目录默认落在插件数据
|
|
12
15
|
* 目录(与用户自有 CLI 的 `~/.actual-cli/data` 隔离,避免缓存与迁移互相踩)。
|
|
13
16
|
* @module @dsh-plus/actual/service
|
|
14
17
|
*/
|
|
@@ -19,11 +22,13 @@ import {
|
|
|
19
22
|
type ActualStatus,
|
|
20
23
|
type CapabilityEntry,
|
|
21
24
|
type CliConfig,
|
|
25
|
+
configSecretsOf,
|
|
22
26
|
createNodeDeps,
|
|
23
27
|
} from '@dsh-plus/actual-mcp'
|
|
24
28
|
import { pluginDataPath, unwrapVolatile } from '@dsh-plus/shared'
|
|
25
29
|
|
|
26
30
|
import type { ActualConfig, ActualConfigFields } from './config.ts'
|
|
31
|
+
import { type CredentialsFace, resolveSecrets } from './credentials.ts'
|
|
27
32
|
import { createInProcessSession } from './mcp-session.ts'
|
|
28
33
|
import {
|
|
29
34
|
ActualRuntime,
|
|
@@ -102,7 +107,12 @@ export class ActualService extends Service {
|
|
|
102
107
|
cliVersionPolicy: config.cliVersionPolicy,
|
|
103
108
|
toolCallTimeoutMs: config.toolCallTimeoutMs,
|
|
104
109
|
}
|
|
105
|
-
|
|
110
|
+
// 密钥按调用即时解析:`cli` 随签名变化换代,闭包始终指向当前那一代配置。
|
|
111
|
+
const deps: RuntimeDeps = {
|
|
112
|
+
...this.deps,
|
|
113
|
+
resolveSecrets: () => resolveSecrets(this.credentialsSeam(), configSecretsOf(cli)),
|
|
114
|
+
}
|
|
115
|
+
const runtime = new ActualRuntime(runtimeConfig, deps, this.logger)
|
|
106
116
|
const detach = runtime.onChange((manifest) => {
|
|
107
117
|
for (const listener of [...this.listeners]) listener(manifest)
|
|
108
118
|
})
|
|
@@ -158,6 +168,18 @@ export class ActualService extends Service {
|
|
|
158
168
|
return () => this.listeners.delete(cb)
|
|
159
169
|
}
|
|
160
170
|
|
|
171
|
+
/**
|
|
172
|
+
* credentials seam:**可选探测**而非硬 inject。
|
|
173
|
+
*
|
|
174
|
+
* 本插件在没挂 `dsh-credentials` 的 profile(headless/dev 精简组合)里也要能
|
|
175
|
+
* 起来,只是密钥退回「行级 Config + CLI 自身继承环境」;且探测发生在每次操作,
|
|
176
|
+
* seam 比本插件晚激活也能自然接上,不必声明时序。
|
|
177
|
+
*/
|
|
178
|
+
private credentialsSeam(): CredentialsFace | null {
|
|
179
|
+
const found = (this.ctx as unknown as { get(key: string): unknown }).get('credentials')
|
|
180
|
+
return found === undefined || found === null ? null : (found as CredentialsFace)
|
|
181
|
+
}
|
|
182
|
+
|
|
161
183
|
dispose(): void {
|
|
162
184
|
this.listeners.clear()
|
|
163
185
|
this.detach()
|