dsh-github-flow 0.1.3 → 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/lib/main.d.ts CHANGED
@@ -1,4 +1,22 @@
1
+ import { GhExecutor } from './executor.js';
2
+ import './github-service.js';
3
+ import type { PluginConfig } from './types.js';
1
4
  export declare const name = "dsh-github-flow";
2
5
  export declare const inject: readonly ['tools', 'commands', 'webServer', 'workspaceRegistry'];
3
- export declare function apply(ctx: any): void;
6
+ export type Config = PluginConfig;
7
+ /**
8
+ * 符合 Standard Schema / Cordis Loader 契约的 Config 运行时校验规范
9
+ */
10
+ export declare const Config: {
11
+ '~standard': {
12
+ version: number;
13
+ vendor: string;
14
+ validate(value: unknown): {
15
+ value: PluginConfig;
16
+ };
17
+ };
18
+ };
19
+ export declare function apply(ctx: any, config?: PluginConfig): void;
20
+ export type GitHubService = GhExecutor;
4
21
  export * from './types.js';
22
+ export * from './github-service.js';
package/lib/main.js CHANGED
@@ -6,15 +6,47 @@ import { createRepoTool } from './tools/repo.js';
6
6
  import { createApiTool } from './tools/api.js';
7
7
  import { registerGhCommand } from './commands/gh.js';
8
8
  import { registerApiRoutes } from './api/routes.js';
9
+ // 引用即生效:加载本模块以挂上 declare module '@deepseek-ai/cordis' 的 ctx.github 类型声明,
10
+ // 并随下面 `export * from './github-service.js'` 一起传给消费方。
11
+ import './github-service.js';
9
12
  export const name = 'dsh-github-flow';
10
13
  export const inject = ['tools', 'commands', 'webServer', 'workspaceRegistry'];
11
- export function apply(ctx) {
12
- const executor = new GhExecutor();
13
- // 1. 注册人类 Slash 命令 (/gh status, /gh help)
14
+ /**
15
+ * 符合 Standard Schema / Cordis Loader 契约的 Config 运行时校验规范
16
+ */
17
+ export const Config = {
18
+ '~standard': {
19
+ version: 1,
20
+ vendor: 'dsh',
21
+ validate(value) {
22
+ const cfg = (typeof value === 'object' && value !== null) ? value : {};
23
+ return {
24
+ value: {
25
+ ghPath: typeof cfg.ghPath === 'string' ? cfg.ghPath : 'gh',
26
+ defaultTimeoutMs: typeof cfg.defaultTimeoutMs === 'number' ? cfg.defaultTimeoutMs : 30_000,
27
+ maxOutputChars: typeof cfg.maxOutputChars === 'number' ? cfg.maxOutputChars : 24_000,
28
+ maxOutputBytes: typeof cfg.maxOutputBytes === 'number' ? cfg.maxOutputBytes : undefined,
29
+ cacheTtlMs: typeof cfg.cacheTtlMs === 'number' ? cfg.cacheTtlMs : 15_000,
30
+ defaultListLimit: typeof cfg.defaultListLimit === 'number' ? cfg.defaultListLimit : 20,
31
+ }
32
+ };
33
+ }
34
+ }
35
+ };
36
+ export function apply(ctx, config = {}) {
37
+ const executor = new GhExecutor(config);
38
+ // 1. 兑现生态服务契约:将 GitHub 执行引擎作为服务暴露给其他 DSH 插件 (ctx.github)
39
+ if (ctx.provide && typeof ctx.provide === 'function') {
40
+ ctx.provide('github', executor);
41
+ }
42
+ else {
43
+ ctx.github = executor;
44
+ }
45
+ // 2. 注册人类 Slash 命令 (/gh status, /gh help)
14
46
  if (ctx.commands) {
15
47
  registerGhCommand(ctx.commands, executor);
16
48
  }
17
- // 2. 注册 Agent 模型工具集(5 大核心领域工具)
49
+ // 3. 注册 Agent 模型工具集(5 大核心领域工具)
18
50
  if (ctx.tools) {
19
51
  ctx.tools.register(createPrTool(executor));
20
52
  ctx.tools.register(createIssueTool(executor));
@@ -22,9 +54,13 @@ export function apply(ctx) {
22
54
  ctx.tools.register(createRepoTool(executor));
23
55
  ctx.tools.register(createApiTool(executor));
24
56
  }
25
- // 3. 注册面向 Client 端的 WebServer HTTP 路由
57
+ // 4. 注册面向 Client 端的 WebServer HTTP 路由(如果当前环境启用了 Web UI 服务)
26
58
  if (ctx.webServer) {
27
- registerApiRoutes(ctx.webServer, executor, ctx.workspaceRegistry);
59
+ // 传入 ctx 作用域对象,使 routes 能够通过 ctx.effect 绑定生命周期并在插件卸载/重载时自动注销;
60
+ // cacheTtlMs 传给路由层以尊重用户的缓存 TTL 配置。
61
+ registerApiRoutes(ctx, executor, ctx.workspaceRegistry, config.cacheTtlMs || 15_000);
28
62
  }
29
63
  }
30
64
  export * from './types.js';
65
+ // 让 `ctx.github` 的类型声明随包一起被消费方加载(声明合并在 './github-service.js' 内)
66
+ export * from './github-service.js';
@@ -22,6 +22,10 @@ export declare function createApiTool(executor: GhExecutor): {
22
22
  type: string;
23
23
  description: string;
24
24
  };
25
+ raw: {
26
+ type: string;
27
+ description: string;
28
+ };
25
29
  };
26
30
  required: string[];
27
31
  };
package/lib/tools/api.js CHANGED
@@ -10,7 +10,7 @@ function normalizeOutput(data) {
10
10
  export function createApiTool(executor) {
11
11
  return {
12
12
  name: 'github_api',
13
- description: '通过 GitHub CLI 认证环境直接调用任意 GitHub REST 或 GraphQL API 端点(兜底逃生门)。支持 --jq 过滤。',
13
+ description: '通过 GitHub CLI 认证环境直接调用任意 GitHub REST 或 GraphQL API 端点(兜底逃生门)。支持 --jq 过滤;fields 一律按字面字符串传参,避免 gh 的 -F 类型转换改写请求体。',
14
14
  parameters: {
15
15
  type: 'object',
16
16
  properties: {
@@ -29,7 +29,11 @@ export function createApiTool(executor) {
29
29
  },
30
30
  fields: {
31
31
  type: 'object',
32
- description: '请求参数或请求体键值对。',
32
+ description: '请求参数或请求体键值对。所有值按字面字符串(gh -f)发送,不做布尔/数字类型转换;嵌套对象请先在端点里用 JSON 字段或改用 raw 模式。',
33
+ },
34
+ raw: {
35
+ type: 'boolean',
36
+ description: '是否以原始响应体模式调用(附加 Accept: application/vnd.github.raw 并强制纯文本返回)。默认 false。适用于读取仓库文件内容,避免拿到 base64 编码的一整行。',
33
37
  },
34
38
  },
35
39
  required: ['endpoint'],
@@ -57,12 +61,27 @@ export function createApiTool(executor) {
57
61
  if (args.jq) {
58
62
  cmd.push('--jq', args.jq);
59
63
  }
64
+ if (args.raw === true) {
65
+ // 让 GitHub 直接返回文件字节而不是 {"content":"<base64>"} 包装。
66
+ // 没有这个出口时,读任何仓库文件都只能拿到一整行 base64——既不可读,
67
+ // 又会在超过截断阈值时被从中间切断而彻底无法还原。
68
+ cmd.push('-H', 'Accept: application/vnd.github.raw');
69
+ }
60
70
  if (args.fields && typeof args.fields === 'object') {
61
71
  for (const [k, v] of Object.entries(args.fields)) {
62
- cmd.push('-F', `${k}=${typeof v === 'object' ? JSON.stringify(v) : v}`);
72
+ if (v === undefined || v === null)
73
+ continue;
74
+ if (typeof v === 'object') {
75
+ // 不把嵌套结构 JSON.stringify 后塞进 -F:gh 侧解析结果不可预期,且会静默产出错误请求体。
76
+ // 宁可显式报错,让调用方改用 raw 模式或把结构放进端点自身的 JSON 字段。
77
+ throw new Error(`github_api 的字段 "${k}" 是嵌套结构,无法作为表单参数发送。请改用 raw 模式传原始 JSON 请求体,或先把该结构序列化后作为字符串字段传入。`);
78
+ }
79
+ // 用 -f(字面字符串)而非 -F:-F 会做 "true"→布尔、"1"→数字的类型转换,
80
+ // 导致字符串请求体被悄悄改写,是跨 fork 提 PR 等场景失败的常见根因。
81
+ cmd.push('-f', `${k}=${String(v)}`);
63
82
  }
64
83
  }
65
- const res = await executor.run(cmd, { cwd });
84
+ const res = await executor.run(cmd, { cwd, rawText: args.raw === true });
66
85
  if (!res.ok)
67
86
  throw new Error(res.error);
68
87
  return normalizeOutput(res.data !== undefined ? res.data : res.rawOutput);
package/lib/tools/pr.d.ts CHANGED
@@ -30,6 +30,10 @@ export declare function createPrTool(executor: GhExecutor): {
30
30
  type: string;
31
31
  description: string;
32
32
  };
33
+ head: {
34
+ type: string;
35
+ description: string;
36
+ };
33
37
  draft: {
34
38
  type: string;
35
39
  description: string;
@@ -44,6 +48,14 @@ export declare function createPrTool(executor: GhExecutor): {
44
48
  enum: string[];
45
49
  description: string;
46
50
  };
51
+ delete_branch: {
52
+ type: string;
53
+ description: string;
54
+ };
55
+ dry_run: {
56
+ type: string;
57
+ description: string;
58
+ };
47
59
  };
48
60
  required: string[];
49
61
  };
package/lib/tools/pr.js CHANGED
@@ -39,6 +39,10 @@ export function createPrTool(executor) {
39
39
  type: 'string',
40
40
  description: '目标基准分支(默认主分支,如 main)。',
41
41
  },
42
+ head: {
43
+ type: 'string',
44
+ description: '源分支,跨 fork 提 PR 时必填,格式 forkOwner:branch(如 ItQianChen:fix-bug);同仓库分支可直接写分支名。',
45
+ },
42
46
  draft: {
43
47
  type: 'boolean',
44
48
  description: '是否创建为草稿 PR。',
@@ -53,6 +57,14 @@ export function createPrTool(executor) {
53
57
  enum: ['merge', 'squash', 'rebase'],
54
58
  description: '合并策略(merge 操作时使用,默认 squash)。',
55
59
  },
60
+ delete_branch: {
61
+ type: 'boolean',
62
+ description: '合并后是否删除源分支(merge 操作时使用,默认 false)。删除分支不可撤销,只在确认该分支已无用后再开启。',
63
+ },
64
+ dry_run: {
65
+ type: 'boolean',
66
+ description: '是否只预演不真正创建(create 操作时使用,默认 false)。透传 gh --dry-run:打印将要创建的 PR 详情而不落库,用于提交前确认 head/base/title 是否正确。',
67
+ },
56
68
  },
57
69
  required: ['action'],
58
70
  },
@@ -112,12 +124,22 @@ export function createPrTool(executor) {
112
124
  const createCmd = ['pr', 'create', ...repoArgs, '--title', args.title || '', '--body', args.body || ''];
113
125
  if (args.base)
114
126
  createCmd.push('--base', args.base);
127
+ // 跨 fork 提 PR 必须显式给出 --head(形如 forkOwner:branch)。
128
+ // 为什么不能省:当 -R 指向的仓库不是当前 origin 时,gh 无法推断拿哪个分支开 PR,
129
+ // 会逐级回退去问本地 git,最终报出与真实原因无关的 "not a git repository"。
130
+ if (args.head)
131
+ createCmd.push('--head', args.head);
115
132
  if (args.draft)
116
133
  createCmd.push('--draft');
134
+ const dryRun = args.dry_run === true;
135
+ if (dryRun)
136
+ createCmd.push('--dry-run');
117
137
  const res = await executor.run(createCmd, { cwd });
118
138
  if (!res.ok)
119
139
  throw new Error(res.error);
120
- return { message: 'PR 创建成功', url: res.rawOutput };
140
+ return dryRun
141
+ ? { message: 'PR 预演完成(未真正创建)', detail: res.rawOutput }
142
+ : { message: 'PR 创建成功', url: res.rawOutput };
121
143
  }
122
144
  case 'review': {
123
145
  if (!args.pr_number)
@@ -140,10 +162,15 @@ export function createPrTool(executor) {
140
162
  if (!args.pr_number)
141
163
  throw new Error('merge 操作必须提供 pr_number');
142
164
  const method = args.merge_method === 'merge' ? '--merge' : (args.merge_method === 'rebase' ? '--rebase' : '--squash');
143
- const res = await executor.run(['pr', 'merge', String(args.pr_number), ...repoArgs, method, '--delete-branch'], { cwd });
165
+ const mergeCmd = ['pr', 'merge', String(args.pr_number), ...repoArgs, method];
166
+ // 删分支默认关闭:合并他人仓库的 PR 时删掉源分支是不可撤销的破坏性动作,
167
+ // 而 gh 默认不删。替用户做掉它违背「可撤销的直接做、不可撤销的先问」。
168
+ if (args.delete_branch === true)
169
+ mergeCmd.push('--delete-branch');
170
+ const res = await executor.run(mergeCmd, { cwd });
144
171
  if (!res.ok)
145
172
  throw new Error(res.error);
146
- return { message: 'PR 合并完成并已清理分支', output: res.rawOutput };
173
+ return { message: 'PR 合并完成', output: res.rawOutput };
147
174
  }
148
175
  default:
149
176
  throw new Error(`未知的 PR 操作: ${args.action}`);
package/lib/tools/repo.js CHANGED
@@ -54,16 +54,22 @@ export function createRepoTool(executor) {
54
54
  switch (args.action) {
55
55
  case 'view': {
56
56
  let repoTarget = args.repo;
57
+ let meta = null;
57
58
  if (!repoTarget) {
58
- const meta = await executor.getRepoMetadata(cwd);
59
+ meta = await executor.getRepoMetadata(cwd);
59
60
  if (meta?.nameWithOwner) {
60
61
  repoTarget = meta.nameWithOwner;
61
62
  }
62
63
  }
63
64
  const repoArgs = repoTarget ? [repoTarget] : [];
64
65
  const res = await executor.run(['repo', 'view', ...repoArgs, '--json', 'nameWithOwner,description,defaultBranchRef,isPrivate,stargazerCount,forkCount,url'], { cwd });
65
- if (!res.ok)
66
+ if (!res.ok) {
67
+ // 若执行 gh 失败(如离线或环境限制),但本地已成功识别仓库元数据,进行优雅兜底返回
68
+ if (meta && meta.nameWithOwner) {
69
+ return normalizeOutput(meta);
70
+ }
66
71
  throw new Error(res.error);
72
+ }
67
73
  return normalizeOutput(res.data || res.rawOutput);
68
74
  }
69
75
  case 'search_code': {
package/lib/types.d.ts CHANGED
@@ -8,6 +8,8 @@ export interface PluginConfig {
8
8
  defaultTimeoutMs?: number;
9
9
  /** 单次安全输出字符数上限(防止 Token 溢出),默认 24,000 */
10
10
  maxOutputChars?: number;
11
+ /** 单次安全输出 UTF-8 字节数硬上限,默认 maxOutputChars × 4(防止中文按字符计数时实际放行字节数倍增) */
12
+ maxOutputBytes?: number;
11
13
  /** Web API 概览数据缓存时长(毫秒),默认 15,000ms */
12
14
  cacheTtlMs?: number;
13
15
  /** 查询列表默认限制条数,默认 20 */
@@ -151,4 +153,6 @@ export interface GlobalOverviewData {
151
153
  }>;
152
154
  lastUpdated: string;
153
155
  error?: string;
156
+ /** 工作区矩阵读取失败的原因。区分「没有工作区」与「读不到工作区」,避免把故障显示成空状态 */
157
+ workspaceError?: string;
154
158
  }
@@ -0,0 +1,43 @@
1
+ export interface WorkspaceRecord {
2
+ id: string;
3
+ title: string;
4
+ path: string;
5
+ sessionIds: string[];
6
+ updatedAt: string;
7
+ }
8
+ /** 仅供测试与配置变更后失效使用 */
9
+ export declare function resetWorkspaceCache(): void;
10
+ export declare function workspaceJsonPath(): string;
11
+ /**
12
+ * 读取全部工作区记录。
13
+ * 返回空数组表示「文件不存在」,抛错表示「文件存在但无法解析」——调用方必须区分这两种情况,
14
+ * 否则 workspace.json 损坏时会显示成「你没有工作区」,把故障伪装成正常空状态。
15
+ */
16
+ export declare function readWorkspaces(options?: {
17
+ bypassCache?: boolean;
18
+ }): WorkspaceRecord[];
19
+ /** 容错读取:读取失败时返回空数组并把原因交给调用方,绝不静默吞掉 */
20
+ export declare function tryReadWorkspaces(): {
21
+ workspaces: WorkspaceRecord[];
22
+ error?: string;
23
+ };
24
+ /**
25
+ * 按会话 ID 反查物理工作区路径。
26
+ * 命中要求路径真实存在——workspace.json 里可能残留已删除的目录。
27
+ */
28
+ export declare function findWorkspacePathBySession(sessionId: string): string | undefined;
29
+ /**
30
+ * 匹配最近活跃的工作区路径。
31
+ * 注意 updatedAt 可能缺失或非 ISO 格式,统一用时间戳比较,无效值视为 0 而非 NaN——
32
+ * 后者会让所有比较都返回 false,导致永远选不中任何工作区。
33
+ */
34
+ export declare function findMostRecentWorkspacePath(): string | undefined;
35
+ export declare function hasGitDir(dir: string): boolean;
36
+ /**
37
+ * 有界并发 map。
38
+ * 为什么必须有:每个工作区探测最多 spawn 2 个 gh 进程,而 Node 的 libuv 线程池默认只有 4 个
39
+ * 线程可用。工作区一多,无上限的 Promise.all 会把进程全部排进队列,单次 spawn 超时 10s × 排队
40
+ * 造成整体秒级到十几秒的延迟(实测 /api/github/global-overview 曾达 18.9s)。
41
+ * 这里显式限制在途数量,让延迟可预期,而不是随工作区数量线性劣化。
42
+ */
43
+ export declare function mapLimit<T, R>(items: readonly T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]>;
@@ -0,0 +1,136 @@
1
+ /**
2
+ * 工作区解析的单一数据源。
3
+ *
4
+ * 为什么独立成模块:`~/.dsh/storages/workspace.json` 的读取与「会话 ID → 物理路径」的
5
+ * 反查逻辑原先在 executor.ts 与 api/routes.ts 里各写了一份(routes.ts 内还写了两遍),
6
+ * 三份实现的字段兜底行为已有细微差异。同一份事实在多个地方各自解析,迟早会出现
7
+ * 「工具看到的工作区」与「面板显示的工作区」不一致,因此收敛到这里。
8
+ *
9
+ * 本模块刻意不依赖 GhExecutor,只做纯文件读取,便于单元测试与复用。
10
+ */
11
+ import fs from 'node:fs';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ /** 缓存时长:workspace.json 变更频率低,但面板会反复读取,缓存可显著降低磁盘 IO 与解析开销 */
15
+ const WORKSPACE_CACHE_TTL_MS = 5_000;
16
+ let cache = null;
17
+ /** 仅供测试与配置变更后失效使用 */
18
+ export function resetWorkspaceCache() {
19
+ cache = null;
20
+ }
21
+ export function workspaceJsonPath() {
22
+ return path.join(os.homedir(), '.dsh', 'storages', 'workspace.json');
23
+ }
24
+ /**
25
+ * 读取全部工作区记录。
26
+ * 返回空数组表示「文件不存在」,抛错表示「文件存在但无法解析」——调用方必须区分这两种情况,
27
+ * 否则 workspace.json 损坏时会显示成「你没有工作区」,把故障伪装成正常空状态。
28
+ */
29
+ export function readWorkspaces(options = {}) {
30
+ const now = Date.now();
31
+ if (!options.bypassCache && cache && now - cache.at < WORKSPACE_CACHE_TTL_MS) {
32
+ return cache.records;
33
+ }
34
+ const file = workspaceJsonPath();
35
+ if (!fs.existsSync(file)) {
36
+ cache = { at: now, records: [] };
37
+ return [];
38
+ }
39
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
40
+ const raw = parsed?.tables?.workspaces ?? {};
41
+ const records = Object.keys(raw).map((id) => {
42
+ const item = raw[id] ?? {};
43
+ return {
44
+ id,
45
+ title: typeof item.title === 'string' ? item.title : '',
46
+ path: typeof item.path === 'string' ? item.path : '',
47
+ sessionIds: Array.isArray(item.sessionIds) ? item.sessionIds : [],
48
+ updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : '',
49
+ };
50
+ });
51
+ cache = { at: now, records };
52
+ return records;
53
+ }
54
+ /** 容错读取:读取失败时返回空数组并把原因交给调用方,绝不静默吞掉 */
55
+ export function tryReadWorkspaces() {
56
+ try {
57
+ return { workspaces: readWorkspaces() };
58
+ }
59
+ catch (err) {
60
+ return { workspaces: [], error: err?.message || '工作区数据读取失败' };
61
+ }
62
+ }
63
+ /**
64
+ * 按会话 ID 反查物理工作区路径。
65
+ * 命中要求路径真实存在——workspace.json 里可能残留已删除的目录。
66
+ */
67
+ export function findWorkspacePathBySession(sessionId) {
68
+ try {
69
+ const { workspaces } = tryReadWorkspaces();
70
+ for (const ws of workspaces) {
71
+ if (ws.sessionIds.includes(sessionId) && ws.path && fs.existsSync(ws.path)) {
72
+ return ws.path;
73
+ }
74
+ }
75
+ }
76
+ catch {
77
+ // 会话反查是可选的尽力而为路径,失败时由调用方回退到其他策略
78
+ }
79
+ return undefined;
80
+ }
81
+ /**
82
+ * 匹配最近活跃的工作区路径。
83
+ * 注意 updatedAt 可能缺失或非 ISO 格式,统一用时间戳比较,无效值视为 0 而非 NaN——
84
+ * 后者会让所有比较都返回 false,导致永远选不中任何工作区。
85
+ */
86
+ export function findMostRecentWorkspacePath() {
87
+ try {
88
+ const { workspaces } = tryReadWorkspaces();
89
+ let best = null;
90
+ let bestAt = -1;
91
+ for (const ws of workspaces) {
92
+ const at = Date.parse(ws.updatedAt || '');
93
+ const score = Number.isFinite(at) ? at : 0;
94
+ if (score > bestAt && ws.path && fs.existsSync(ws.path)) {
95
+ best = ws;
96
+ bestAt = score;
97
+ }
98
+ }
99
+ return best?.path;
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ export function hasGitDir(dir) {
106
+ try {
107
+ return fs.existsSync(path.join(dir, '.git'));
108
+ }
109
+ catch {
110
+ return false;
111
+ }
112
+ }
113
+ /**
114
+ * 有界并发 map。
115
+ * 为什么必须有:每个工作区探测最多 spawn 2 个 gh 进程,而 Node 的 libuv 线程池默认只有 4 个
116
+ * 线程可用。工作区一多,无上限的 Promise.all 会把进程全部排进队列,单次 spawn 超时 10s × 排队
117
+ * 造成整体秒级到十几秒的延迟(实测 /api/github/global-overview 曾达 18.9s)。
118
+ * 这里显式限制在途数量,让延迟可预期,而不是随工作区数量线性劣化。
119
+ */
120
+ export async function mapLimit(items, limit, fn) {
121
+ if (items.length === 0)
122
+ return [];
123
+ const effectiveLimit = Math.max(1, Math.min(limit, items.length));
124
+ const results = new Array(items.length);
125
+ let cursor = 0;
126
+ const workers = Array.from({ length: effectiveLimit }, async () => {
127
+ for (;;) {
128
+ const index = cursor++;
129
+ if (index >= items.length)
130
+ return;
131
+ results[index] = await fn(items[index], index);
132
+ }
133
+ });
134
+ await Promise.all(workers);
135
+ return results;
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-github-flow",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "icon": "icon.svg",
5
5
  "description": "GitHub Flow: Native GitHub CLI (gh) integration for DeepSeek Harness — Agent Tools, Slash Commands, and Web UI Workspace",
6
6
  "type": "module",
@@ -55,6 +55,7 @@
55
55
  "registry": "https://registry.npmjs.org/"
56
56
  },
57
57
  "devDependencies": {
58
+ "@deepseek-ai/cordis": "^4.0.4",
58
59
  "@types/node": "^26.6.3",
59
60
  "typescript": "^7.0.2"
60
61
  }