shennian 0.2.43 → 0.2.47

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/dist/src/agents/claude.d.ts +5 -0
  2. package/dist/src/agents/claude.js +17 -0
  3. package/dist/src/agents/codex.d.ts +5 -0
  4. package/dist/src/agents/codex.js +12 -1
  5. package/dist/src/agents/command-spec.js +74 -3
  6. package/dist/src/agents/detect.js +12 -2
  7. package/dist/src/agents/manager.d.ts +16 -0
  8. package/dist/src/agents/manager.js +145 -0
  9. package/dist/src/agents/model-registry/discovery.js +6 -0
  10. package/dist/src/agents/pi.d.ts +1 -0
  11. package/dist/src/agents/pi.js +18 -2
  12. package/dist/src/channels/base.d.ts +62 -0
  13. package/dist/src/channels/base.js +3 -0
  14. package/dist/src/channels/registry.d.ts +7 -0
  15. package/dist/src/channels/registry.js +30 -0
  16. package/dist/src/channels/runtime.d.ts +60 -0
  17. package/dist/src/channels/runtime.js +158 -0
  18. package/dist/src/channels/secret-registry.d.ts +17 -0
  19. package/dist/src/channels/secret-registry.js +44 -0
  20. package/dist/src/channels/websocket.d.ts +48 -0
  21. package/dist/src/channels/websocket.js +314 -0
  22. package/dist/src/channels/wecom.d.ts +48 -0
  23. package/dist/src/channels/wecom.js +315 -0
  24. package/dist/src/commands/manager.d.ts +2 -0
  25. package/dist/src/commands/manager.js +168 -0
  26. package/dist/src/config/index.js +5 -1
  27. package/dist/src/index.js +3 -1
  28. package/dist/src/manager/prompt.d.ts +2 -0
  29. package/dist/src/manager/prompt.js +46 -0
  30. package/dist/src/manager/registry.d.ts +92 -0
  31. package/dist/src/manager/registry.js +267 -0
  32. package/dist/src/manager/runtime.d.ts +59 -0
  33. package/dist/src/manager/runtime.js +534 -0
  34. package/dist/src/native-fusion/parsers.js +6 -0
  35. package/dist/src/native-fusion/service.d.ts +1 -0
  36. package/dist/src/native-fusion/service.js +5 -3
  37. package/dist/src/native-fusion/types.d.ts +2 -1
  38. package/dist/src/region.js +7 -19
  39. package/dist/src/session/handlers/agents.js +1 -1
  40. package/dist/src/session/handlers/chat.js +121 -2
  41. package/dist/src/session/handlers/control.js +1 -1
  42. package/dist/src/session/manager.d.ts +2 -0
  43. package/dist/src/session/manager.js +16 -0
  44. package/dist/src/session/projection.d.ts +3 -0
  45. package/dist/src/session/projection.js +54 -0
  46. package/dist/src/session/store.d.ts +24 -1
  47. package/dist/src/session/store.js +66 -3
  48. package/dist/src/session/types.d.ts +2 -0
  49. package/package.json +5 -5
@@ -4,11 +4,65 @@ import os from 'node:os';
4
4
  import { createAgent } from '../../agents/adapter.js';
5
5
  import { reportLog } from '../../log-reporter.js';
6
6
  import { lookupClaudeTranscriptCwd } from '../../native-fusion/parsers.js';
7
+ import { appendMessage, recordSession } from '../store.js';
8
+ import { mergeProjectedSessions } from '../projection.js';
7
9
  function extractSummary(text) {
8
10
  const newline = text.indexOf('\n');
9
11
  const end = newline > 0 ? Math.min(newline, 80) : Math.min(text.length, 80);
10
12
  return text.slice(0, end);
11
13
  }
14
+ function getNativeSourceAgentType(agentType, modelId) {
15
+ if (agentType !== 'manager')
16
+ return agentType;
17
+ return modelId === 'claude' ? 'claude' : 'codex';
18
+ }
19
+ function sendSessionMessageEvent(runtime, envelope, session) {
20
+ runtime.client.sendEvent({
21
+ type: 'event',
22
+ event: 'session.message',
23
+ payload: {
24
+ sessionId: envelope.sessionId,
25
+ message: envelope,
26
+ session: {
27
+ id: envelope.sessionId,
28
+ agentType: session.agentType,
29
+ agentSessionId: session.agentSessionId ?? null,
30
+ modelId: session.modelId ?? null,
31
+ workDir: session.workDir,
32
+ status: 'active',
33
+ ...(session.agentType === 'manager'
34
+ ? { externalChannel: runtime.managerRuntime?.getExternalChannelStatus(envelope.sessionId) ?? null }
35
+ : {}),
36
+ },
37
+ },
38
+ });
39
+ }
40
+ function sendSessionUpdateEvent(runtime, input) {
41
+ recordSession({
42
+ sessionId: input.sessionId,
43
+ agentType: input.agentType,
44
+ workDir: input.workDir,
45
+ agentSessionId: input.agentSessionId ?? null,
46
+ modelId: input.modelId ?? null,
47
+ });
48
+ runtime.client.sendEvent({
49
+ type: 'event',
50
+ event: 'session.update',
51
+ payload: {
52
+ session: {
53
+ id: input.sessionId,
54
+ agentType: input.agentType,
55
+ agentSessionId: input.agentSessionId ?? null,
56
+ modelId: input.modelId ?? null,
57
+ workDir: input.workDir,
58
+ status: 'active',
59
+ ...(input.agentType === 'manager'
60
+ ? { externalChannel: runtime.managerRuntime?.getExternalChannelStatus(input.sessionId) ?? null }
61
+ : {}),
62
+ },
63
+ },
64
+ });
65
+ }
12
66
  function maybeResolveClaudeImportedWorkDir(agentType, workDir, agentSessionId) {
13
67
  if (agentType !== 'claude')
14
68
  return workDir;
@@ -52,6 +106,7 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
52
106
  if (event.agentSessionId)
53
107
  activeSession.agentSessionId = event.agentSessionId;
54
108
  }
109
+ runtime.managerRuntime?.noteAgentEvent(sessionId, event);
55
110
  if (event.state !== 'delta') {
56
111
  reportLog({
57
112
  level: 'info',
@@ -61,13 +116,54 @@ function bindAdapterEvents(runtime, sessionId, agentType, adapter) {
61
116
  metadata: { runId: event.runId, seq: event.seq, agentType },
62
117
  });
63
118
  }
119
+ if (event.state === 'delta' && event.text && !event.thinking) {
120
+ appendMessage(sessionId, {
121
+ id: `agent-${event.runId}-${event.seq}`,
122
+ sessionId,
123
+ role: 'agent',
124
+ ts: Date.now(),
125
+ payload: event.text,
126
+ });
127
+ }
128
+ else if (event.state === 'tool-call' || event.state === 'tool-result') {
129
+ appendMessage(sessionId, {
130
+ id: `agent-${event.runId}-${event.seq}`,
131
+ sessionId,
132
+ role: 'agent',
133
+ ts: Date.now(),
134
+ payload: JSON.stringify({
135
+ v: 1,
136
+ type: event.state === 'tool-call' ? 'tool_use' : 'tool_result',
137
+ name: event.name,
138
+ args: event.args,
139
+ result: event.result,
140
+ }),
141
+ });
142
+ }
143
+ else if ((event.state === 'error' || event.state === 'aborted') && event.message) {
144
+ appendMessage(sessionId, {
145
+ id: `agent-${event.runId || 'run'}-${event.seq}`,
146
+ sessionId,
147
+ role: 'agent',
148
+ ts: Date.now(),
149
+ payload: event.message,
150
+ });
151
+ }
64
152
  const runKey = `${sessionId}:${event.runId}`;
65
153
  if (event.state === 'delta' && !event.thinking && event.text) {
66
154
  runtime.runTextAcc.set(runKey, (runtime.runTextAcc.get(runKey) ?? '') + event.text);
67
155
  }
68
156
  if (event.agentSessionId && event.agentSessionId !== emittedAgentSessionId) {
69
157
  emittedAgentSessionId = event.agentSessionId;
70
- runtime.nativeFusion?.noteManagedSourceSession(sessionId, agentType, event.agentSessionId);
158
+ runtime.nativeFusion?.noteManagedSourceSession(sessionId, getNativeSourceAgentType(agentType, event.source), event.agentSessionId);
159
+ if (activeSession) {
160
+ recordSession({
161
+ sessionId,
162
+ agentType,
163
+ workDir: activeSession.workDir,
164
+ agentSessionId: event.agentSessionId,
165
+ });
166
+ }
71
167
  runtime.client.sendEvent({
72
168
  type: 'event',
73
169
  event: 'session.update',
@@ -186,7 +282,8 @@ export async function handleChatSend(runtime, req) {
186
282
  return;
187
283
  }
188
284
  rememberProcessedReqId(runtime, req.id);
189
- const { sessionId, text, agentType, workDir, agentSessionId: incomingAgentSid, modelId, clientMessageId } = req.params;
285
+ const { sessionId, text, agentType, workDir, agentSessionId: incomingAgentSid, modelId, clientMessageId, sessionListProjection } = req.params;
286
+ mergeProjectedSessions(sessionListProjection);
190
287
  if (!sessionId || !text) {
191
288
  runtime.processedReqIds.delete(req.id);
192
289
  runtime.client.sendRes({ type: 'res', id: req.id, ok: false, error: 'sessionId and text are required' });
@@ -256,15 +353,37 @@ export async function handleChatSend(runtime, req) {
256
353
  return;
257
354
  }
258
355
  }
356
+ sendSessionUpdateEvent(runtime, {
357
+ sessionId,
358
+ agentType: requestedAgentType,
359
+ workDir: resolvedWorkDir,
360
+ agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
361
+ modelId,
362
+ });
259
363
  session.currentRunId = null;
260
364
  session.nextEventSeq = 0;
261
365
  runtime.nativeFusion?.registerManagedSend({
262
366
  sessionId,
263
367
  agentType: requestedAgentType,
368
+ sourceAgentType: getNativeSourceAgentType(requestedAgentType, modelId),
264
369
  canonicalMessageId: clientMessageId ?? null,
265
370
  sourceSessionKey: session.agentSessionId ?? incomingAgentSid ?? null,
266
371
  text,
267
372
  });
373
+ const userEnvelope = {
374
+ id: clientMessageId ?? `user-${req.id}`,
375
+ sessionId,
376
+ role: 'user',
377
+ ts: Date.now(),
378
+ payload: text,
379
+ };
380
+ appendMessage(sessionId, userEnvelope);
381
+ sendSessionMessageEvent(runtime, userEnvelope, {
382
+ agentType: requestedAgentType,
383
+ workDir: resolvedWorkDir,
384
+ agentSessionId: session.agentSessionId ?? incomingAgentSid ?? null,
385
+ modelId,
386
+ });
268
387
  reportLog({
269
388
  level: 'info',
270
389
  sessionId,
@@ -4,7 +4,7 @@ import { SERVERS } from '../../region.js';
4
4
  import { loadConfig, saveConfig } from '../../config/index.js';
5
5
  export async function handleRegionProbe(runtime, req) {
6
6
  const config = loadConfig();
7
- const currentUrl = config.serverUrl ?? SERVERS.global.url;
7
+ const currentUrl = config.serverUrl ?? SERVERS.cn.url;
8
8
  const currentRegion = currentUrl.includes('shennian.net') ? 'cn' : 'global';
9
9
  const results = await Promise.all(Object.keys(SERVERS).map(async (region) => {
10
10
  const { url, label } = SERVERS[region];
@@ -7,6 +7,7 @@ import '../agents/gemini.js';
7
7
  import '../agents/cursor.js';
8
8
  import '../agents/opencode.js';
9
9
  import '../agents/pi.js';
10
+ import '../agents/manager.js';
10
11
  export declare function resolveSessionWorkDir(input: string): string;
11
12
  export declare class SessionManager {
12
13
  private client;
@@ -19,6 +20,7 @@ export declare class SessionManager {
19
20
  private runTextAcc;
20
21
  /** In-flight chunked uploads: transferId → metadata */
21
22
  private pendingTransfers;
23
+ private managerRuntime;
22
24
  constructor(client: CliRelayClient, nativeFusion?: NativeSessionFusionService | null, cliVersion?: string | undefined);
23
25
  private getRuntime;
24
26
  private reloadCustomAgents;
@@ -10,6 +10,7 @@ import { handleAgentsRefresh, handleModelsRefresh } from './handlers/agents.js';
10
10
  import { handleChatAbort, handleChatSend } from './handlers/chat.js';
11
11
  import { cleanupPendingTransfers, handleFsLs, handleFsRead, handleFsTransfer, handleFsTransferAbort, handleFsTransferChunk, handleFsTransferFinish, handleFsTransferStart, } from './handlers/fs.js';
12
12
  import { handleRegionProbe, handleRegionSwitch, handleUpgradeSetPolicy } from './handlers/control.js';
13
+ import { ManagerRuntimeService, setManagerRuntimeService } from '../manager/runtime.js';
13
14
  // Side-effect imports to register built-in agent adapters.
14
15
  import '../agents/claude.js';
15
16
  import '../agents/codex.js';
@@ -19,6 +20,7 @@ import '../agents/cursor.js';
19
20
  // import '../agents/openclaw.js'
20
21
  import '../agents/opencode.js';
21
22
  import '../agents/pi.js';
23
+ import '../agents/manager.js';
22
24
  import { registerCustomAgent } from '../agents/custom.js';
23
25
  const MAX_SESSIONS = 50;
24
26
  function isWindowsAbsolutePath(input) {
@@ -58,10 +60,17 @@ export class SessionManager {
58
60
  runTextAcc = new Map();
59
61
  /** In-flight chunked uploads: transferId → metadata */
60
62
  pendingTransfers = new Map();
63
+ managerRuntime;
61
64
  constructor(client, nativeFusion = null, cliVersion) {
62
65
  this.client = client;
63
66
  this.nativeFusion = nativeFusion;
64
67
  this.cliVersion = cliVersion;
68
+ this.managerRuntime = new ManagerRuntimeService({
69
+ getRuntime: () => this.getRuntime(),
70
+ dispatchReq: (req) => this.handleReq(req),
71
+ });
72
+ setManagerRuntimeService(this.managerRuntime);
73
+ void this.managerRuntime.start();
65
74
  this.reloadCustomAgents();
66
75
  }
67
76
  getRuntime() {
@@ -75,6 +84,7 @@ export class SessionManager {
75
84
  sessions: this.sessions,
76
85
  evictIdleSessions: () => this.evictIdleSessions(),
77
86
  nativeFusion: this.nativeFusion,
87
+ managerRuntime: this.managerRuntime,
78
88
  };
79
89
  }
80
90
  reloadCustomAgents() {
@@ -142,6 +152,10 @@ export class SessionManager {
142
152
  case 'models.refresh':
143
153
  await handleModelsRefresh(runtime, req);
144
154
  break;
155
+ case 'manager.channel.get':
156
+ case 'manager.channel.upsert':
157
+ await runtime.managerRuntime?.handleAppReq(req);
158
+ break;
145
159
  default:
146
160
  this.client.sendRes({ type: 'res', id: req.id, ok: false, error: `Unknown method: ${req.method}` });
147
161
  }
@@ -180,5 +194,7 @@ export class SessionManager {
180
194
  this.sessions.clear();
181
195
  this.runTextAcc.clear();
182
196
  cleanupPendingTransfers(this.getRuntime());
197
+ void this.managerRuntime.stop();
198
+ setManagerRuntimeService(null);
183
199
  }
184
200
  }
@@ -0,0 +1,3 @@
1
+ import type { SessionListProjection } from '@shennian/wire';
2
+ export declare function listProjectedSessions(): SessionListProjection[];
3
+ export declare function mergeProjectedSessions(sessions: SessionListProjection[] | undefined): void;
@@ -0,0 +1,54 @@
1
+ // @arch docs/features/manager-agent.md
2
+ // @test src/__tests__/manager-runtime.test.ts
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { resolveShennianPath } from '../config/index.js';
6
+ const PROJECTION_FILE = resolveShennianPath('session-list-projection.json');
7
+ function emptyProjection() {
8
+ return { updatedAt: new Date(0).toISOString(), sessions: {} };
9
+ }
10
+ function readProjectionFile() {
11
+ try {
12
+ const parsed = JSON.parse(fs.readFileSync(PROJECTION_FILE, 'utf-8'));
13
+ return {
14
+ updatedAt: parsed.updatedAt ?? new Date(0).toISOString(),
15
+ sessions: parsed.sessions ?? {},
16
+ };
17
+ }
18
+ catch {
19
+ return emptyProjection();
20
+ }
21
+ }
22
+ function writeProjectionFile(file) {
23
+ fs.mkdirSync(path.dirname(PROJECTION_FILE), { recursive: true });
24
+ fs.writeFileSync(PROJECTION_FILE, JSON.stringify(file, null, 2));
25
+ }
26
+ export function listProjectedSessions() {
27
+ return Object.values(readProjectionFile().sessions);
28
+ }
29
+ export function mergeProjectedSessions(sessions) {
30
+ if (!sessions?.length)
31
+ return;
32
+ try {
33
+ const file = readProjectionFile();
34
+ for (const session of sessions) {
35
+ if (!session?.id)
36
+ continue;
37
+ if (session.deletedAt) {
38
+ delete file.sessions[session.id];
39
+ continue;
40
+ }
41
+ const existing = file.sessions[session.id];
42
+ const existingMs = Date.parse(existing?.updatedAt ?? existing?.lastActivityAt ?? '');
43
+ const incomingMs = Date.parse(session.updatedAt ?? session.lastActivityAt ?? '');
44
+ if (!existing || !Number.isFinite(existingMs) || !Number.isFinite(incomingMs) || incomingMs >= existingMs) {
45
+ file.sessions[session.id] = session;
46
+ }
47
+ }
48
+ file.updatedAt = new Date().toISOString();
49
+ writeProjectionFile(file);
50
+ }
51
+ catch {
52
+ // Projection is a local cache. Losing it should not break chat delivery.
53
+ }
54
+ }
@@ -1,4 +1,27 @@
1
- import type { Envelope } from '@shennian/wire';
1
+ import type { AgentType, Envelope } from '@shennian/wire';
2
+ export type LocalSessionRecord = {
3
+ sessionId: string;
4
+ agentType: AgentType;
5
+ workDir: string;
6
+ agentSessionId?: string | null;
7
+ modelId?: string | null;
8
+ status?: 'active' | 'completed' | 'failed';
9
+ createdAt: string;
10
+ updatedAt: string;
11
+ lastActivityAt: string;
12
+ lastMessagePreview?: string | null;
13
+ };
14
+ export declare function recordSession(input: {
15
+ sessionId: string;
16
+ agentType: AgentType;
17
+ workDir: string;
18
+ agentSessionId?: string | null;
19
+ modelId?: string | null;
20
+ status?: 'active' | 'completed' | 'failed';
21
+ lastActivityAt?: string;
22
+ lastMessagePreview?: string | null;
23
+ }): void;
24
+ export declare function listSessionRecords(): LocalSessionRecord[];
2
25
  export declare function appendMessage(sessionId: string, envelope: Envelope): void;
3
26
  export declare function readMessages(sessionId: string, opts?: {
4
27
  limit?: number;
@@ -2,16 +2,79 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { resolveShennianPath } from '../config/index.js';
4
4
  const SESSIONS_DIR = resolveShennianPath('sessions');
5
+ const SESSIONS_INDEX_FILE = resolveShennianPath('sessions-index.json');
5
6
  function ensureSessionsDir() {
6
7
  fs.mkdirSync(SESSIONS_DIR, { recursive: true });
7
8
  }
8
9
  function sessionFile(sessionId) {
9
10
  return path.join(SESSIONS_DIR, `${sessionId}.jsonl`);
10
11
  }
12
+ function readSessionIndex() {
13
+ try {
14
+ const parsed = JSON.parse(fs.readFileSync(SESSIONS_INDEX_FILE, 'utf-8'));
15
+ return parsed && typeof parsed === 'object' ? parsed : {};
16
+ }
17
+ catch {
18
+ return {};
19
+ }
20
+ }
21
+ function writeSessionIndex(index) {
22
+ fs.mkdirSync(path.dirname(SESSIONS_INDEX_FILE), { recursive: true });
23
+ fs.writeFileSync(SESSIONS_INDEX_FILE, JSON.stringify(index, null, 2));
24
+ }
25
+ function previewFromEnvelope(envelope) {
26
+ const text = envelope.payload.replace(/\s+/g, ' ').trim();
27
+ if (!text || text.startsWith('{"v":1,"type":"tool'))
28
+ return null;
29
+ return text.length > 120 ? `${text.slice(0, 120)}...` : text;
30
+ }
31
+ export function recordSession(input) {
32
+ try {
33
+ const index = readSessionIndex();
34
+ const existing = index[input.sessionId];
35
+ const now = new Date().toISOString();
36
+ index[input.sessionId] = {
37
+ sessionId: input.sessionId,
38
+ agentType: input.agentType,
39
+ workDir: input.workDir,
40
+ agentSessionId: input.agentSessionId ?? existing?.agentSessionId ?? null,
41
+ modelId: input.modelId ?? existing?.modelId ?? null,
42
+ status: input.status ?? existing?.status ?? 'active',
43
+ createdAt: existing?.createdAt ?? now,
44
+ updatedAt: now,
45
+ lastActivityAt: input.lastActivityAt ?? existing?.lastActivityAt ?? now,
46
+ lastMessagePreview: input.lastMessagePreview ?? existing?.lastMessagePreview ?? null,
47
+ };
48
+ writeSessionIndex(index);
49
+ }
50
+ catch {
51
+ // Local session indexing is best-effort; relay delivery remains authoritative.
52
+ }
53
+ }
54
+ export function listSessionRecords() {
55
+ return Object.values(readSessionIndex());
56
+ }
11
57
  export function appendMessage(sessionId, envelope) {
12
- ensureSessionsDir();
13
- const line = JSON.stringify(envelope) + '\n';
14
- fs.appendFileSync(sessionFile(sessionId), line, 'utf-8');
58
+ try {
59
+ ensureSessionsDir();
60
+ const line = JSON.stringify(envelope) + '\n';
61
+ fs.appendFileSync(sessionFile(sessionId), line, 'utf-8');
62
+ const index = readSessionIndex();
63
+ const existing = index[sessionId];
64
+ if (existing) {
65
+ const preview = previewFromEnvelope(envelope);
66
+ index[sessionId] = {
67
+ ...existing,
68
+ updatedAt: new Date().toISOString(),
69
+ lastActivityAt: new Date(envelope.ts).toISOString(),
70
+ lastMessagePreview: preview ?? existing.lastMessagePreview ?? null,
71
+ };
72
+ writeSessionIndex(index);
73
+ }
74
+ }
75
+ catch {
76
+ // Local transcript persistence is best-effort; relay delivery remains authoritative.
77
+ }
15
78
  }
16
79
  export function readMessages(sessionId, opts) {
17
80
  const file = sessionFile(sessionId);
@@ -2,6 +2,7 @@ import type { AgentType } from '@shennian/wire';
2
2
  import type { AgentAdapter } from '../agents/adapter.js';
3
3
  import type { CliRelayClient } from '../relay/client.js';
4
4
  import type { NativeSessionFusionService } from '../native-fusion/service.js';
5
+ import type { ManagerRuntimeService } from '../manager/runtime.js';
5
6
  export type ActiveSession = {
6
7
  adapter: AgentAdapter;
7
8
  workDir: string;
@@ -32,4 +33,5 @@ export type SessionManagerRuntime = {
32
33
  sessions: Map<string, ActiveSession>;
33
34
  evictIdleSessions: () => void;
34
35
  nativeFusion: NativeSessionFusionService | null;
36
+ managerRuntime: ManagerRuntimeService | null;
35
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shennian",
3
- "version": "0.2.43",
3
+ "version": "0.2.47",
4
4
  "description": "Shennian — AI Agent Control Plane CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,11 +36,11 @@
36
36
  "dependencies": {
37
37
  "@mariozechner/pi-agent-core": "^0.64.0",
38
38
  "@sinclair/typebox": "^0.34.49",
39
- "@shennian/wire": "^0.1.3",
40
39
  "chalk": "^5.4.1",
41
40
  "commander": "^13.1.0",
42
41
  "qrcode-terminal": "^0.12.0",
43
- "ws": "^8.18.1"
42
+ "ws": "^8.18.1",
43
+ "@shennian/wire": "0.1.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "@types/node": "^20",
@@ -50,8 +50,8 @@
50
50
  "typescript": "^5.9.3"
51
51
  },
52
52
  "scripts": {
53
- "build": "tsc",
54
- "build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json",
53
+ "build": "tsc && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
54
+ "build:publish": "node -e \"const fs=require('node:fs'); fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('.tsbuildinfo.publish', { force: true })\" && tsc -p tsconfig.publish.json && node -e \"require('node:fs').chmodSync('dist/bin/shennian.js', 0o755)\"",
55
55
  "dev": "tsc --watch"
56
56
  }
57
57
  }