diffcat-cli 0.2.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.
Files changed (49) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +135 -0
  3. package/connectors/workbuddy/cli.json +11 -0
  4. package/connectors/workbuddy/connector-meta.json +22 -0
  5. package/connectors/workbuddy/icon.svg +11 -0
  6. package/connectors/workbuddy/skills/diffcat/SKILL.md +94 -0
  7. package/connectors/workbuddy/skills/diffcat/references/operations.md +64 -0
  8. package/connectors/workbuddy/skills/diffcat/references/safety.md +37 -0
  9. package/dist/client.d.ts +38 -0
  10. package/dist/client.js +199 -0
  11. package/dist/client.js.map +1 -0
  12. package/dist/config-store.d.ts +8 -0
  13. package/dist/config-store.js +100 -0
  14. package/dist/config-store.js.map +1 -0
  15. package/dist/credential-store.d.ts +4 -0
  16. package/dist/credential-store.js +37 -0
  17. package/dist/credential-store.js.map +1 -0
  18. package/dist/deployment.d.ts +5 -0
  19. package/dist/deployment.js +6 -0
  20. package/dist/deployment.js.map +1 -0
  21. package/dist/device-login.d.ts +6 -0
  22. package/dist/device-login.js +93 -0
  23. package/dist/device-login.js.map +1 -0
  24. package/dist/dpop.d.ts +12 -0
  25. package/dist/dpop.js +25 -0
  26. package/dist/dpop.js.map +1 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.js +296 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/json-input.d.ts +1 -0
  31. package/dist/json-input.js +28 -0
  32. package/dist/json-input.js.map +1 -0
  33. package/dist/output.d.ts +2 -0
  34. package/dist/output.js +7 -0
  35. package/dist/output.js.map +1 -0
  36. package/dist/types.d.ts +37 -0
  37. package/dist/types.js +2 -0
  38. package/dist/types.js.map +1 -0
  39. package/package.json +42 -0
  40. package/plugins/diffcat/.codex-plugin/plugin.json +24 -0
  41. package/plugins/diffcat/README.md +45 -0
  42. package/plugins/diffcat/skills/diffcat/SKILL.md +41 -0
  43. package/plugins/diffcat/skills/diffcat/agents/openai.yaml +6 -0
  44. package/plugins/diffcat/skills/diffcat/references/authentication.md +75 -0
  45. package/plugins/diffcat/skills/diffcat/references/capabilities.md +75 -0
  46. package/plugins/diffcat/skills/diffcat/references/drafts-and-approvals.md +58 -0
  47. package/plugins/diffcat/skills/diffcat/references/querying.md +74 -0
  48. package/plugins/diffcat/skills/diffcat/references/recovery.md +44 -0
  49. package/scripts/codex-plugin.mjs +116 -0
@@ -0,0 +1,8 @@
1
+ import type { CliConfig, CliProfileConfig } from './types.js';
2
+ export declare function loadConfig(): Promise<CliConfig>;
3
+ export declare function getProfileConfig(profile: string): Promise<CliProfileConfig | null>;
4
+ export declare function saveProfileConfig(profile: string, value: CliProfileConfig | null): Promise<void>;
5
+ export declare function normalizeServer(value: string): string;
6
+ export declare function buildServerUrl(server: string, path: string): string;
7
+ /** 同一配置的刷新令牌轮换必须跨进程串行,避免并发命令触发旧令牌复用撤销。 */
8
+ export declare function withProfileLock<T>(profile: string, action: () => Promise<T>): Promise<T>;
@@ -0,0 +1,100 @@
1
+ import { mkdir, open, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join } from 'node:path';
4
+ function configPath() {
5
+ const root = process.env.DIFFCAT_CONFIG_DIR?.trim() || join(homedir(), '.config', 'diffcat');
6
+ return join(root, 'config.json');
7
+ }
8
+ export async function loadConfig() {
9
+ try {
10
+ const parsed = JSON.parse(await readFile(configPath(), 'utf8'));
11
+ return parsed && typeof parsed === 'object' && parsed.profiles ? parsed : { profiles: {} };
12
+ }
13
+ catch (error) {
14
+ if (error.code === 'ENOENT')
15
+ return { profiles: {} };
16
+ throw new Error(`无法读取 Diffcat CLI 配置:${String(error)}`);
17
+ }
18
+ }
19
+ export async function getProfileConfig(profile) {
20
+ return (await loadConfig()).profiles[profile] ?? null;
21
+ }
22
+ export async function saveProfileConfig(profile, value) {
23
+ const config = await loadConfig();
24
+ if (value)
25
+ config.profiles[profile] = value;
26
+ else
27
+ delete config.profiles[profile];
28
+ const target = configPath();
29
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
30
+ const temporary = `${target}.${process.pid}.tmp`;
31
+ await writeFile(temporary, `${JSON.stringify(config, null, 2)}\n`, {
32
+ mode: 0o600,
33
+ });
34
+ await rename(temporary, target);
35
+ }
36
+ export function normalizeServer(value) {
37
+ let parsed;
38
+ try {
39
+ parsed = new URL(value);
40
+ }
41
+ catch {
42
+ throw new Error('服务地址必须是完整的 http(s) URL');
43
+ }
44
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
45
+ throw new Error('服务地址只允许 http 或 https');
46
+ }
47
+ parsed.pathname = parsed.pathname.replace(/\/$/, '');
48
+ parsed.search = '';
49
+ parsed.hash = '';
50
+ return parsed.toString().replace(/\/$/, '');
51
+ }
52
+ export function buildServerUrl(server, path) {
53
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
54
+ return `${normalizeServer(server)}${normalizedPath}`;
55
+ }
56
+ /** 同一配置的刷新令牌轮换必须跨进程串行,避免并发命令触发旧令牌复用撤销。 */
57
+ export async function withProfileLock(profile, action) {
58
+ const target = configPath();
59
+ await mkdir(dirname(target), { recursive: true, mode: 0o700 });
60
+ const safeProfile = encodeURIComponent(profile).slice(0, 128);
61
+ const lockPath = `${target}.${safeProfile}.lock`;
62
+ const deadline = Date.now() + 30_000;
63
+ while (Date.now() < deadline) {
64
+ let handle;
65
+ try {
66
+ handle = await open(lockPath, 'wx', 0o600);
67
+ }
68
+ catch (error) {
69
+ if (error.code !== 'EEXIST')
70
+ throw error;
71
+ await removeStaleLock(lockPath);
72
+ await delay(100);
73
+ continue;
74
+ }
75
+ try {
76
+ await handle.writeFile(`${process.pid}\n`);
77
+ return await action();
78
+ }
79
+ finally {
80
+ await handle.close();
81
+ await unlink(lockPath).catch(() => undefined);
82
+ }
83
+ }
84
+ throw new Error(`等待 Diffcat 配置 ${profile} 的进程锁超时`);
85
+ }
86
+ async function removeStaleLock(path) {
87
+ try {
88
+ const info = await stat(path);
89
+ if (Date.now() - info.mtimeMs > 5 * 60_000)
90
+ await unlink(path);
91
+ }
92
+ catch (error) {
93
+ if (error.code !== 'ENOENT')
94
+ throw error;
95
+ }
96
+ }
97
+ function delay(milliseconds) {
98
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
99
+ }
100
+ //# sourceMappingURL=config-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-store.js","sourceRoot":"","sources":["../src/config-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { StoredCredential } from './types.js';
2
+ export declare function loadCredential(account: string): StoredCredential | null;
3
+ export declare function saveCredential(account: string, credential: StoredCredential): void;
4
+ export declare function deleteCredential(account: string): void;
@@ -0,0 +1,37 @@
1
+ import { Entry } from '@napi-rs/keyring';
2
+ const SERVICE_NAME = 'diffcat-cli';
3
+ function assertInteractiveCredentialStore() {
4
+ if (process.env.CI && process.env.CI !== 'false') {
5
+ throw new Error('Diffcat CLI 首期不允许在 CI/容器中使用长期授权');
6
+ }
7
+ }
8
+ function entry(account) {
9
+ assertInteractiveCredentialStore();
10
+ return new Entry(SERVICE_NAME, account);
11
+ }
12
+ export function loadCredential(account) {
13
+ try {
14
+ const value = entry(account).getPassword();
15
+ return value ? JSON.parse(value) : null;
16
+ }
17
+ catch (error) {
18
+ throw new Error(`无法访问系统凭据库;Diffcat CLI 不会降级为明文令牌文件:${String(error)}`);
19
+ }
20
+ }
21
+ export function saveCredential(account, credential) {
22
+ try {
23
+ entry(account).setPassword(JSON.stringify(credential));
24
+ }
25
+ catch (error) {
26
+ throw new Error(`无法写入系统凭据库:${String(error)}`);
27
+ }
28
+ }
29
+ export function deleteCredential(account) {
30
+ try {
31
+ entry(account).deletePassword();
32
+ }
33
+ catch (error) {
34
+ throw new Error(`无法删除系统凭据:${String(error)}`);
35
+ }
36
+ }
37
+ //# sourceMappingURL=credential-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-store.js","sourceRoot":"","sources":["../src/credential-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGzC,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,SAAS,gCAAgC;IACvC,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC;AAED,SAAS,KAAK,CAAC,OAAe;IAC5B,gCAAgC,EAAE,CAAC;IACnC,OAAO,IAAI,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;QAC3C,OAAO,KAAK,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAsB,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,UAA4B;IAC1E,IAAI,CAAC;QACH,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe;IAC9C,IAAI,CAAC;QACH,KAAK,CAAC,OAAO,CAAC,CAAC,cAAc,EAAE,CAAC;IAClC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,YAAY,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const DIFFCAT_PRODUCTION_ENDPOINTS: {
2
+ readonly api: "https://diffcat-api.howcat.cn";
3
+ readonly web: "https://diffcat.howcat.cn";
4
+ readonly mobile: "https://diffcat-mobile.howcat.cn";
5
+ };
@@ -0,0 +1,6 @@
1
+ export const DIFFCAT_PRODUCTION_ENDPOINTS = {
2
+ api: 'https://diffcat-api.howcat.cn',
3
+ web: 'https://diffcat.howcat.cn',
4
+ mobile: 'https://diffcat-mobile.howcat.cn',
5
+ };
6
+ //# sourceMappingURL=deployment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployment.js","sourceRoot":"","sources":["../src/deployment.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,4BAA4B,GAAG;IAC1C,GAAG,EAAE,+BAA+B;IACpC,GAAG,EAAE,2BAA2B;IAChC,MAAM,EAAE,kCAAkC;CAClC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare function loginWithDeviceFlow(input: {
2
+ profile: string;
3
+ server: string;
4
+ capabilities: string[];
5
+ deviceName?: string;
6
+ }): Promise<unknown>;
@@ -0,0 +1,93 @@
1
+ import { hostname } from 'node:os';
2
+ import { buildServerUrl, saveProfileConfig, withProfileLock } from './config-store.js';
3
+ import { saveCredential } from './credential-store.js';
4
+ import { createDpopKeyPair, createDpopProof } from './dpop.js';
5
+ import { DiffcatApiError, errorMessage, parseResponse } from './client.js';
6
+ import { writeProgress } from './output.js';
7
+ export async function loginWithDeviceFlow(input) {
8
+ const keys = await createDpopKeyPair();
9
+ const path = '/agent/v1/oauth/device/code';
10
+ const deviceUrl = buildServerUrl(input.server, path);
11
+ const proof = await createDpopProof({
12
+ method: 'POST',
13
+ htu: deviceUrl,
14
+ ...keys,
15
+ });
16
+ const response = await fetch(deviceUrl, {
17
+ method: 'POST',
18
+ headers: { 'Content-Type': 'application/json', DPoP: proof },
19
+ body: JSON.stringify({
20
+ client_id: 'diffcat-cli',
21
+ device_name: input.deviceName?.trim() || `CLI · ${hostname()}`,
22
+ capabilities: [...new Set(input.capabilities)],
23
+ constraints: {},
24
+ }),
25
+ });
26
+ const payload = await parseResponse(response);
27
+ if (!response.ok) {
28
+ throw new DiffcatApiError(errorMessage(payload, response.status), response.status, payload);
29
+ }
30
+ const device = payload;
31
+ writeProgress(`请在浏览器中授权:${device.verification_uri_complete}`);
32
+ writeProgress(`授权码:${device.user_code}`);
33
+ const deadline = Date.now() + device.expires_in * 1000;
34
+ let intervalMs = Math.max(device.interval, 5) * 1000;
35
+ while (Date.now() < deadline) {
36
+ await delay(intervalMs);
37
+ const tokenPath = '/agent/v1/oauth/token';
38
+ const tokenUrl = buildServerUrl(input.server, tokenPath);
39
+ const tokenProof = await createDpopProof({
40
+ method: 'POST',
41
+ htu: tokenUrl,
42
+ ...keys,
43
+ });
44
+ const tokenResponse = await fetch(tokenUrl, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json', DPoP: tokenProof },
47
+ body: JSON.stringify({
48
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
49
+ client_id: 'diffcat-cli',
50
+ device_code: device.device_code,
51
+ }),
52
+ });
53
+ const tokenPayload = await parseResponse(tokenResponse);
54
+ if (tokenResponse.ok) {
55
+ const token = tokenPayload;
56
+ const keyringAccount = input.profile;
57
+ const credential = {
58
+ ...keys,
59
+ refreshToken: token.refresh_token,
60
+ accessToken: token.access_token,
61
+ accessExpiresAt: new Date(Date.now() + token.expires_in * 1000).toISOString(),
62
+ scope: token.scope.split(' ').filter(Boolean),
63
+ pendingOperations: {},
64
+ };
65
+ await withProfileLock(input.profile, async () => {
66
+ saveCredential(keyringAccount, credential);
67
+ await saveProfileConfig(input.profile, {
68
+ server: input.server,
69
+ keyringAccount,
70
+ });
71
+ });
72
+ return {
73
+ profile: input.profile,
74
+ server: input.server,
75
+ authenticated: true,
76
+ scope: credential.scope,
77
+ };
78
+ }
79
+ const oauthError = tokenPayload;
80
+ if (oauthError.error === 'authorization_pending')
81
+ continue;
82
+ if (oauthError.error === 'slow_down') {
83
+ intervalMs += 5_000;
84
+ continue;
85
+ }
86
+ throw new DiffcatApiError(errorMessage(tokenPayload, tokenResponse.status), tokenResponse.status, tokenPayload);
87
+ }
88
+ throw new Error('设备授权已超时,请重新运行 diffcat auth login');
89
+ }
90
+ function delay(milliseconds) {
91
+ return new Promise((resolve) => setTimeout(resolve, milliseconds));
92
+ }
93
+ //# sourceMappingURL=device-login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-login.js","sourceRoot":"","sources":["../src/device-login.ts"],"names":[],"mappings":"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"}
package/dist/dpop.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import { type JWK } from 'jose';
2
+ export declare function createDpopKeyPair(): Promise<{
3
+ privateJwk: JWK;
4
+ publicJwk: JWK;
5
+ }>;
6
+ export declare function createDpopProof(input: {
7
+ method: string;
8
+ htu: string;
9
+ privateJwk: JWK;
10
+ publicJwk: JWK;
11
+ accessToken?: string;
12
+ }): Promise<string>;
package/dist/dpop.js ADDED
@@ -0,0 +1,25 @@
1
+ import { SignJWT, exportJWK, generateKeyPair, importJWK } from 'jose';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ export async function createDpopKeyPair() {
4
+ const pair = await generateKeyPair('ES256', { extractable: true });
5
+ return {
6
+ privateJwk: await exportJWK(pair.privateKey),
7
+ publicJwk: await exportJWK(pair.publicKey),
8
+ };
9
+ }
10
+ export async function createDpopProof(input) {
11
+ const key = await importJWK(input.privateJwk, 'ES256');
12
+ const claims = {
13
+ htm: input.method.toUpperCase(),
14
+ htu: input.htu,
15
+ iat: Math.floor(Date.now() / 1000),
16
+ jti: randomUUID(),
17
+ };
18
+ if (input.accessToken) {
19
+ claims.ath = createHash('sha256').update(input.accessToken, 'utf8').digest('base64url');
20
+ }
21
+ return new SignJWT(claims)
22
+ .setProtectedHeader({ typ: 'dpop+jwt', alg: 'ES256', jwk: input.publicJwk })
23
+ .sign(key);
24
+ }
25
+ //# sourceMappingURL=dpop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dpop.js","sourceRoot":"","sources":["../src/dpop.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,SAAS,EAAY,MAAM,MAAM,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,iBAAiB;IAIrC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO;QACL,UAAU,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC;QAC5C,SAAS,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC;KAC3C,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,KAMrC;IACC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,KAAK,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IACvD,MAAM,MAAM,GAAoC;QAC9C,GAAG,EAAE,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE;QAC/B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAClC,GAAG,EAAE,UAAU,EAAE;KAClB,CAAC;IACF,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;SACvB,kBAAkB,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC;SAC3E,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,296 @@
1
+ #!/usr/bin/env node
2
+ import { Command, Option } from 'commander';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { randomUUID } from 'node:crypto';
5
+ import { readFileSync } from 'node:fs';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { buildServerUrl, normalizeServer, getProfileConfig, saveProfileConfig, withProfileLock, } from './config-store.js';
8
+ import { deleteCredential, loadCredential } from './credential-store.js';
9
+ import { DiffcatClient, errorMessage, parseResponse } from './client.js';
10
+ import { DIFFCAT_PRODUCTION_ENDPOINTS } from './deployment.js';
11
+ import { loginWithDeviceFlow } from './device-login.js';
12
+ import { parseJsonInput } from './json-input.js';
13
+ import { writeJson } from './output.js';
14
+ const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
15
+ const program = new Command()
16
+ .name('diffcat')
17
+ .description('以最小授权调用 Diffcat 已登记能力')
18
+ .version(packageVersion)
19
+ .option('-p, --profile <name>', '凭据配置名称', 'default')
20
+ .option('--pretty', '格式化 JSON 输出', false)
21
+ .showSuggestionAfterError();
22
+ function globals() {
23
+ return program.opts();
24
+ }
25
+ function collect(value, previous) {
26
+ return [...previous, value];
27
+ }
28
+ async function client() {
29
+ return DiffcatClient.load(globals().profile);
30
+ }
31
+ async function publicCatalog(server) {
32
+ const response = await fetch(buildServerUrl(server, '/agent/v1/oauth/capabilities'));
33
+ const payload = await parseResponse(response);
34
+ if (!response.ok)
35
+ throw new Error(errorMessage(payload, response.status));
36
+ return payload;
37
+ }
38
+ function output(value) {
39
+ writeJson(value, globals().pretty);
40
+ }
41
+ const setup = program.command('setup').description('安装或修复 AI 客户端集成');
42
+ setup
43
+ .command('codex')
44
+ .description('从当前 npm 包登记随附的 Codex Plugin 与 Companion Skill')
45
+ .action(() => {
46
+ const installer = fileURLToPath(new URL('../scripts/codex-plugin.mjs', import.meta.url));
47
+ const result = spawnSync(process.execPath, [installer, 'install', '--json'], {
48
+ stdio: 'inherit',
49
+ windowsHide: true,
50
+ });
51
+ if (result.error)
52
+ throw result.error;
53
+ if (result.status !== 0)
54
+ process.exitCode = result.status ?? 1;
55
+ });
56
+ const auth = program.command('auth').description('登录与凭据管理');
57
+ auth
58
+ .command('login')
59
+ .description('通过 OAuth Device Flow 请求用户授权')
60
+ .option('--server <url>', 'Diffcat API 根地址', DIFFCAT_PRODUCTION_ENDPOINTS.api)
61
+ .addOption(new Option('-c, --capability <id>', '请求的能力,可重复').argParser(collect).default([]))
62
+ .addOption(new Option('--preset <id>', '请求服务端能力预设,可重复').argParser(collect).default([]))
63
+ .option('--device-name <name>', '授权页显示的设备名称')
64
+ .action(async (options) => {
65
+ const server = normalizeServer(options.server);
66
+ const requested = new Set(options.capability);
67
+ if (options.preset.length > 0) {
68
+ const catalog = await publicCatalog(server);
69
+ for (const presetId of options.preset) {
70
+ const preset = catalog.presets.find((item) => item.id === presetId);
71
+ if (!preset)
72
+ throw new Error(`未知能力预设:${presetId}`);
73
+ for (const id of preset.capabilityIds)
74
+ requested.add(id);
75
+ }
76
+ }
77
+ if (requested.size === 0) {
78
+ throw new Error('至少提供一个 --capability 或 --preset;CLI 不会默认申请整组权限');
79
+ }
80
+ output(await loginWithDeviceFlow({
81
+ profile: globals().profile,
82
+ server,
83
+ capabilities: [...requested],
84
+ deviceName: options.deviceName,
85
+ }));
86
+ });
87
+ auth
88
+ .command('status')
89
+ .description('验证当前授权并显示能力范围')
90
+ .action(async () => {
91
+ const api = await client();
92
+ const capabilities = await api.post('/agent/v1/capabilities/list', {});
93
+ output({ ...api.getStatus(), capabilities: capabilities.list });
94
+ });
95
+ auth
96
+ .command('logout')
97
+ .description('撤销当前授权并删除系统凭据')
98
+ .action(async () => {
99
+ const profile = globals().profile;
100
+ const config = await getProfileConfig(profile);
101
+ if (!config) {
102
+ output({ profile, authenticated: false, revoked: false });
103
+ return;
104
+ }
105
+ const credential = loadCredential(config.keyringAccount);
106
+ if (credential) {
107
+ await withProfileLock(profile, async () => {
108
+ const latest = loadCredential(config.keyringAccount);
109
+ if (!latest)
110
+ return;
111
+ const response = await fetch(buildServerUrl(config.server, '/agent/v1/oauth/revoke'), {
112
+ method: 'POST',
113
+ headers: { 'Content-Type': 'application/json' },
114
+ body: JSON.stringify({
115
+ token: latest.refreshToken,
116
+ client_id: 'diffcat-cli',
117
+ }),
118
+ });
119
+ const payload = await parseResponse(response);
120
+ if (!response.ok)
121
+ throw new Error(errorMessage(payload, response.status));
122
+ deleteCredential(config.keyringAccount);
123
+ });
124
+ }
125
+ await saveProfileConfig(profile, null);
126
+ output({ profile, authenticated: false, revoked: true });
127
+ });
128
+ const grant = program.command('grant').description('查看或撤销当前 CLI 授权');
129
+ grant
130
+ .command('list')
131
+ .description('查看当前 DPoP 授权')
132
+ .action(async () => output(await (await client()).post('/agent/v1/grant/current', {})));
133
+ grant
134
+ .command('revoke')
135
+ .description('撤销当前 DPoP 授权')
136
+ .action(async () => {
137
+ const api = await client();
138
+ const result = await api.post('/agent/v1/grant/revoke', {});
139
+ const config = await getProfileConfig(globals().profile);
140
+ if (config) {
141
+ deleteCredential(config.keyringAccount);
142
+ await saveProfileConfig(globals().profile, null);
143
+ }
144
+ output(result);
145
+ });
146
+ const capability = program.command('capability').description('能力发现与通用调用');
147
+ capability
148
+ .command('list')
149
+ .action(async () => output(await (await client()).post('/agent/v1/capabilities/list', {})));
150
+ capability
151
+ .command('describe <id>')
152
+ .action(async (id) => output(await (await client()).get('/agent/v1/capabilities/detail', { id })));
153
+ capability
154
+ .command('invoke <id>')
155
+ .requiredOption('--input <json|@file|->', 'JSON 对象、@文件或 -(stdin)')
156
+ .option('--idempotency-key <key>', '显式幂等键')
157
+ .action(async (id, options) => {
158
+ output(await (await client()).invoke(id, await parseJsonInput(options.input), {
159
+ idempotencyKey: options.idempotencyKey,
160
+ }));
161
+ });
162
+ capability
163
+ .command('catalog')
164
+ .description('登录前查看服务端完整能力目录、分组与预设')
165
+ .option('--server <url>', 'Diffcat API 根地址', DIFFCAT_PRODUCTION_ENDPOINTS.api)
166
+ .action(async (options) => output(await publicCatalog(normalizeServer(options.server))));
167
+ const resource = program.command('resource').description('通用基础资料查询');
168
+ resource
169
+ .command('list <resource>')
170
+ .option('--query <json|@file|->', '列表筛选 JSON', '{}')
171
+ .action(async (resourceName, options) => output(await (await client()).invoke(`${resourceName}.read`, {
172
+ action: 'list',
173
+ query: await parseJsonInput(options.query),
174
+ })));
175
+ resource
176
+ .command('get <resource> <id>')
177
+ .addOption(new Option('--include <field>', '附加关系字段,可重复').argParser(collect).default([]))
178
+ .action(async (resourceName, id, options) => output(await (await client()).invoke(`${resourceName}.read`, {
179
+ action: 'get',
180
+ id,
181
+ ...(options.include.length ? { include: options.include } : {}),
182
+ })));
183
+ const document = program.command('document').description('通用业务单据查询与草稿新建');
184
+ document
185
+ .command('list <resource>')
186
+ .option('--query <json|@file|->', '列表筛选 JSON', '{}')
187
+ .action(async (resourceName, options) => output(await (await client()).invoke(`${resourceName}.read`, {
188
+ action: 'list',
189
+ query: await parseJsonInput(options.query),
190
+ })));
191
+ document
192
+ .command('get <resource> <id>')
193
+ .addOption(new Option('--include <field>', '附加关系字段,可重复').argParser(collect).default([]))
194
+ .action(async (resourceName, id, options) => output(await (await client()).invoke(`${resourceName}.read`, {
195
+ action: 'get',
196
+ id,
197
+ ...(options.include.length ? { include: options.include } : {}),
198
+ })));
199
+ document
200
+ .command('create-draft <resource>')
201
+ .requiredOption('--input <json|@file|->', '单据草稿 JSON')
202
+ .option('--idempotency-key <key>', '显式幂等键')
203
+ .action(async (resourceName, options) => output(await (await client()).invoke(`${resourceName}.create_draft`, await parseJsonInput(options.input), {
204
+ idempotencyKey: options.idempotencyKey,
205
+ })));
206
+ const project = program.command('project').description('项目查询');
207
+ project
208
+ .command('list')
209
+ .option('--query <json|@file|->', '列表筛选 JSON', '{}')
210
+ .action(async (options) => output(await (await client()).invoke('pm.projects.read', {
211
+ action: 'list',
212
+ query: await parseJsonInput(options.query),
213
+ })));
214
+ project
215
+ .command('get <id>')
216
+ .action(async (id) => output(await (await client()).invoke('pm.projects.read', { action: 'get', id })));
217
+ const salesOrder = program.command('sales-order').description('销售订单查询与草稿创建');
218
+ salesOrder
219
+ .command('list')
220
+ .option('--query <json|@file|->', '列表筛选 JSON', '{}')
221
+ .action(async (options) => output(await (await client()).invoke('sales.orders.read', {
222
+ action: 'list',
223
+ query: await parseJsonInput(options.query),
224
+ })));
225
+ salesOrder
226
+ .command('get <id>')
227
+ .action(async (id) => output(await (await client()).invoke('sales.orders.read', { action: 'get', id })));
228
+ salesOrder
229
+ .command('create-draft')
230
+ .requiredOption('--input <json|@file|->', '销售订单草稿 JSON')
231
+ .option('--idempotency-key <key>', '显式幂等键')
232
+ .action(async (options) => output(await (await client()).invoke('sales.orders.create_draft', await parseJsonInput(options.input), {
233
+ idempotencyKey: options.idempotencyKey,
234
+ })));
235
+ const stats = program.command('stats').description('数据中心统计查询');
236
+ stats
237
+ .command('list')
238
+ .option('--role-id <id>', '目标角色;缺省为当前角色')
239
+ .action(async (options) => output(await (await client()).invoke('data_center.stats.read', {
240
+ action: 'list',
241
+ ...(options.roleId ? { roleId: options.roleId } : {}),
242
+ })));
243
+ stats
244
+ .command('query')
245
+ .requiredOption('--input <json|@file|->', '{ roleId, widgets } JSON')
246
+ .action(async (options) => output(await (await client()).invoke('data_center.stats.read', {
247
+ action: 'query',
248
+ ...(await parseJsonInput(options.input)),
249
+ })));
250
+ const task = program.command('task').description('流程任务查询与审批');
251
+ task
252
+ .command('list')
253
+ .option('--query <json|@file|->', '任务筛选 JSON', '{}')
254
+ .action(async (options) => output(await (await client()).invoke('flow.tasks.read', {
255
+ action: 'list',
256
+ query: await parseJsonInput(options.query),
257
+ })));
258
+ task
259
+ .command('get <id>')
260
+ .action(async (id) => output(await (await client()).invoke('flow.tasks.read', { action: 'get', id })));
261
+ task
262
+ .command('approve <id>')
263
+ .option('--comment <text>', '审批意见')
264
+ .option('--idempotency-key <key>', '显式幂等键')
265
+ .action(async (id, options) => output(await (await client()).invoke('flow.tasks.approve', {
266
+ taskId: id,
267
+ ...(options.comment ? { comment: options.comment } : {}),
268
+ }, { idempotencyKey: options.idempotencyKey })));
269
+ const operation = program.command('operation').description('高风险操作恢复与状态查询');
270
+ operation
271
+ .command('status <id>')
272
+ .action(async (id) => output(await (await client()).get('/agent/v1/operations/status', { id })));
273
+ operation
274
+ .command('resume <id>')
275
+ .description('用户网页确认后继续执行本地凭据库中的待处理操作')
276
+ .action(async (id) => {
277
+ const api = await client();
278
+ const pending = api.getPendingOperation(id);
279
+ if (!pending)
280
+ throw new Error(`系统凭据库中没有操作 ${id} 的待执行输入`);
281
+ output(await api.invoke(pending.capabilityId, pending.input, {
282
+ operationId: id,
283
+ idempotencyKey: pending.idempotencyKey,
284
+ }));
285
+ });
286
+ program.addHelpText('after', `\n示例:\n diffcat auth login -c pm.projects.read\n diffcat project list --query '{"pageSize":20}'\n diffcat capability invoke flow.tasks.read --input '{"action":"list"}'\n`);
287
+ program.parseAsync().catch((error) => {
288
+ writeJson({
289
+ success: false,
290
+ error: error instanceof Error ? error.name : 'Error',
291
+ message: error instanceof Error ? error.message : String(error),
292
+ requestId: randomUUID(),
293
+ }, globals().pretty);
294
+ process.exitCode = 1;
295
+ });
296
+ //# sourceMappingURL=index.js.map