@chrisromp/copilot-bridge 0.6.0-dev.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.
Files changed (89) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/bin/copilot-bridge.js +61 -0
  4. package/config.sample.json +100 -0
  5. package/dist/channels/mattermost/adapter.d.ts +55 -0
  6. package/dist/channels/mattermost/adapter.d.ts.map +1 -0
  7. package/dist/channels/mattermost/adapter.js +524 -0
  8. package/dist/channels/mattermost/adapter.js.map +1 -0
  9. package/dist/channels/mattermost/streaming.d.ts +29 -0
  10. package/dist/channels/mattermost/streaming.d.ts.map +1 -0
  11. package/dist/channels/mattermost/streaming.js +151 -0
  12. package/dist/channels/mattermost/streaming.js.map +1 -0
  13. package/dist/config.d.ts +107 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +817 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/core/bridge.d.ts +73 -0
  18. package/dist/core/bridge.d.ts.map +1 -0
  19. package/dist/core/bridge.js +166 -0
  20. package/dist/core/bridge.js.map +1 -0
  21. package/dist/core/channel-idle.d.ts +40 -0
  22. package/dist/core/channel-idle.d.ts.map +1 -0
  23. package/dist/core/channel-idle.js +120 -0
  24. package/dist/core/channel-idle.js.map +1 -0
  25. package/dist/core/command-handler.d.ts +51 -0
  26. package/dist/core/command-handler.d.ts.map +1 -0
  27. package/dist/core/command-handler.js +393 -0
  28. package/dist/core/command-handler.js.map +1 -0
  29. package/dist/core/inter-agent.d.ts +52 -0
  30. package/dist/core/inter-agent.d.ts.map +1 -0
  31. package/dist/core/inter-agent.js +179 -0
  32. package/dist/core/inter-agent.js.map +1 -0
  33. package/dist/core/onboarding.d.ts +44 -0
  34. package/dist/core/onboarding.d.ts.map +1 -0
  35. package/dist/core/onboarding.js +205 -0
  36. package/dist/core/onboarding.js.map +1 -0
  37. package/dist/core/scheduler.d.ts +38 -0
  38. package/dist/core/scheduler.d.ts.map +1 -0
  39. package/dist/core/scheduler.js +253 -0
  40. package/dist/core/scheduler.js.map +1 -0
  41. package/dist/core/session-manager.d.ts +166 -0
  42. package/dist/core/session-manager.d.ts.map +1 -0
  43. package/dist/core/session-manager.js +1732 -0
  44. package/dist/core/session-manager.js.map +1 -0
  45. package/dist/core/stream-formatter.d.ts +14 -0
  46. package/dist/core/stream-formatter.d.ts.map +1 -0
  47. package/dist/core/stream-formatter.js +198 -0
  48. package/dist/core/stream-formatter.js.map +1 -0
  49. package/dist/core/thread-utils.d.ts +22 -0
  50. package/dist/core/thread-utils.d.ts.map +1 -0
  51. package/dist/core/thread-utils.js +44 -0
  52. package/dist/core/thread-utils.js.map +1 -0
  53. package/dist/core/workspace-manager.d.ts +38 -0
  54. package/dist/core/workspace-manager.d.ts.map +1 -0
  55. package/dist/core/workspace-manager.js +230 -0
  56. package/dist/core/workspace-manager.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +1286 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/logger.d.ts +9 -0
  62. package/dist/logger.d.ts.map +1 -0
  63. package/dist/logger.js +34 -0
  64. package/dist/logger.js.map +1 -0
  65. package/dist/state/store.d.ts +124 -0
  66. package/dist/state/store.d.ts.map +1 -0
  67. package/dist/state/store.js +523 -0
  68. package/dist/state/store.js.map +1 -0
  69. package/dist/types.d.ts +185 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -0
  73. package/package.json +61 -0
  74. package/scripts/check.ts +267 -0
  75. package/scripts/com.copilot-bridge.plist +41 -0
  76. package/scripts/copilot-bridge.service +30 -0
  77. package/scripts/init.ts +250 -0
  78. package/scripts/install-service.ts +123 -0
  79. package/scripts/lib/config-gen.ts +129 -0
  80. package/scripts/lib/mattermost.ts +109 -0
  81. package/scripts/lib/output.ts +69 -0
  82. package/scripts/lib/prerequisites.ts +86 -0
  83. package/scripts/lib/prompts.ts +65 -0
  84. package/scripts/lib/service.ts +191 -0
  85. package/scripts/uninstall-service.ts +90 -0
  86. package/templates/admin/AGENTS.md +325 -0
  87. package/templates/admin/MEMORY.md +4 -0
  88. package/templates/agents/AGENTS.md +97 -0
  89. package/templates/agents/MEMORY.md +4 -0
@@ -0,0 +1,185 @@
1
+ export interface BotConfig {
2
+ token: string;
3
+ agent?: string | null;
4
+ admin?: boolean;
5
+ }
6
+ export interface PlatformConfig {
7
+ url: string;
8
+ botToken?: string;
9
+ bots?: Record<string, BotConfig>;
10
+ }
11
+ export interface ChannelConfig {
12
+ id: string;
13
+ platform: string;
14
+ name: string;
15
+ workingDirectory: string;
16
+ bot?: string;
17
+ agent?: string | null;
18
+ model?: string;
19
+ triggerMode: 'mention' | 'all';
20
+ threadedReplies: boolean;
21
+ verbose: boolean;
22
+ isDM?: boolean;
23
+ }
24
+ export interface PermissionsConfig {
25
+ allow?: string[];
26
+ deny?: string[];
27
+ allowPaths?: string[];
28
+ allowUrls?: string[];
29
+ }
30
+ export interface AppConfig {
31
+ platforms: Record<string, PlatformConfig>;
32
+ channels: ChannelConfig[];
33
+ defaults: {
34
+ model: string;
35
+ agent: string | null;
36
+ triggerMode: 'mention' | 'all';
37
+ threadedReplies: boolean;
38
+ verbose: boolean;
39
+ permissionMode: 'interactive' | 'autopilot';
40
+ };
41
+ permissions?: PermissionsConfig;
42
+ interAgent?: InterAgentConfig;
43
+ }
44
+ export interface InterAgentConfig {
45
+ enabled: boolean;
46
+ defaultTimeout?: number;
47
+ maxTimeout?: number;
48
+ maxDepth?: number;
49
+ allow?: Record<string, InterAgentPermission>;
50
+ }
51
+ export interface InterAgentPermission {
52
+ canCall?: string[];
53
+ canBeCalledBy?: string[];
54
+ }
55
+ export interface InboundMessage {
56
+ platform: string;
57
+ channelId: string;
58
+ userId: string;
59
+ username: string;
60
+ text: string;
61
+ postId: string;
62
+ threadRootId?: string;
63
+ mentionsBot: boolean;
64
+ isDM: boolean;
65
+ attachments?: MessageAttachment[];
66
+ }
67
+ export interface MessageAttachment {
68
+ type: 'image' | 'file' | 'video' | 'audio';
69
+ id: string;
70
+ url: string;
71
+ name: string;
72
+ mimeType?: string;
73
+ size?: number;
74
+ }
75
+ export interface InboundReaction {
76
+ platform: string;
77
+ channelId: string;
78
+ userId: string;
79
+ postId: string;
80
+ emoji: string;
81
+ action: 'added' | 'removed';
82
+ }
83
+ export interface SendOpts {
84
+ threadRootId?: string;
85
+ }
86
+ export interface CreateChannelOpts {
87
+ name: string;
88
+ displayName: string;
89
+ private: boolean;
90
+ teamId: string;
91
+ }
92
+ export interface TeamInfo {
93
+ id: string;
94
+ name: string;
95
+ displayName: string;
96
+ }
97
+ export interface ChannelInfo {
98
+ id: string;
99
+ name: string;
100
+ displayName: string;
101
+ type: string;
102
+ teamId: string;
103
+ }
104
+ export interface ChannelAdapter {
105
+ readonly platform: string;
106
+ connect(): Promise<void>;
107
+ disconnect(): Promise<void>;
108
+ onMessage(handler: (msg: InboundMessage) => void): void;
109
+ onReaction(handler: (reaction: InboundReaction) => void): void;
110
+ sendMessage(channelId: string, content: string, opts?: SendOpts): Promise<string>;
111
+ updateMessage(channelId: string, messageId: string, content: string): Promise<void>;
112
+ deleteMessage(channelId: string, messageId: string): Promise<void>;
113
+ setTyping(channelId: string): Promise<void>;
114
+ replyInThread(channelId: string, rootId: string, content: string): Promise<string>;
115
+ getBotUserId(): string;
116
+ /** Download a file attachment to a local path. Returns the written path. */
117
+ downloadFile(fileId: string, destPath: string): Promise<string>;
118
+ /** Upload a local file and send it as a message in a channel. Returns the post ID. */
119
+ sendFile(channelId: string, filePath: string, message?: string, opts?: SendOpts): Promise<string>;
120
+ /** Add an emoji reaction to a message. Best-effort — implementations should not throw. */
121
+ addReaction?(postId: string, emoji: string): Promise<void>;
122
+ createChannel?(opts: CreateChannelOpts): Promise<string>;
123
+ addUserToChannel?(channelId: string, userId: string): Promise<void>;
124
+ getTeams?(): Promise<TeamInfo[]>;
125
+ getChannelByName?(teamId: string, name: string): Promise<ChannelInfo | null>;
126
+ /** Discover DM channels for this bot (optional — platform-specific). */
127
+ discoverDMChannels?(): Promise<{
128
+ channelId: string;
129
+ otherUserId: string;
130
+ }[]>;
131
+ }
132
+ /** Factory function type for constructing a ChannelAdapter instance for a given platform. */
133
+ export type AdapterFactory = (platformName: string, url: string, token: string) => ChannelAdapter;
134
+ export interface ChannelSessionState {
135
+ channelId: string;
136
+ sessionId: string;
137
+ model: string;
138
+ agent: string | null;
139
+ verbose: boolean;
140
+ triggerMode: 'mention' | 'all';
141
+ threadedReplies: boolean;
142
+ permissionMode: 'interactive' | 'autopilot';
143
+ createdAt: string;
144
+ }
145
+ export interface PermissionRule {
146
+ id?: number;
147
+ scope: string;
148
+ tool: string;
149
+ commandPattern: string;
150
+ action: 'allow' | 'deny';
151
+ createdAt: string;
152
+ }
153
+ export interface PendingPermission {
154
+ sessionId: string;
155
+ channelId: string;
156
+ messageId?: string;
157
+ toolName: string;
158
+ serverName?: string;
159
+ toolInput: unknown;
160
+ commands: string[];
161
+ resolve: (result: {
162
+ kind: 'approved' | 'denied-by-rules' | 'denied-interactively-by-user' | 'denied-no-approval-rule-and-could-not-request-from-user';
163
+ }) => void;
164
+ createdAt: number;
165
+ }
166
+ export interface PendingUserInput {
167
+ sessionId: string;
168
+ channelId: string;
169
+ messageId?: string;
170
+ question: string;
171
+ choices?: string[];
172
+ allowFreeform?: boolean;
173
+ resolve: (answer: {
174
+ answer: string;
175
+ wasFreeform: boolean;
176
+ }) => void;
177
+ createdAt: number;
178
+ }
179
+ export type CopilotEventType = 'assistant.message' | 'assistant.message_delta' | 'assistant.turn_start' | 'assistant.turn_end' | 'assistant.reasoning' | 'assistant.reasoning_delta' | 'tool.execution_start' | 'tool.execution_complete' | 'session.idle' | 'session.error';
180
+ export interface FormattedEvent {
181
+ type: 'content' | 'tool_start' | 'tool_complete' | 'error' | 'status';
182
+ content: string;
183
+ verbose: boolean;
184
+ }
185
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@chrisromp/copilot-bridge",
3
+ "version": "0.6.0-dev.2",
4
+ "description": "Mattermost ↔ GitHub Copilot bridge",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/ChrisRomp/copilot-bridge.git"
10
+ },
11
+ "bin": {
12
+ "copilot-bridge": "bin/copilot-bridge.js"
13
+ },
14
+ "files": [
15
+ "bin/",
16
+ "dist/",
17
+ "scripts/*.ts",
18
+ "scripts/lib/*.ts",
19
+ "!scripts/**/*.test.ts",
20
+ "!scripts/test-admin-api.ts",
21
+ "scripts/com.copilot-bridge.plist",
22
+ "scripts/copilot-bridge.service",
23
+ "templates/",
24
+ "config.sample.json"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "engines": {
30
+ "node": ">=20"
31
+ },
32
+ "scripts": {
33
+ "dev": "tsx watch src/index.ts",
34
+ "build": "tsc",
35
+ "prepublishOnly": "npm run build",
36
+ "start": "tsx dist/index.js",
37
+ "init": "tsx scripts/init.ts",
38
+ "check": "tsx scripts/check.ts",
39
+ "install-service": "tsx scripts/install-service.ts",
40
+ "uninstall-service": "tsx scripts/uninstall-service.ts",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest"
43
+ },
44
+ "dependencies": {
45
+ "@github/copilot-sdk": "^0.1.32",
46
+ "@mattermost/client": "^10.3.0",
47
+ "@mattermost/types": "^10.3.0",
48
+ "better-sqlite3": "^12.6.2",
49
+ "cron": "^4.4.0",
50
+ "cronstrue": "^3.13.0",
51
+ "luxon": "^3.7.2",
52
+ "tsx": "^4.21.0",
53
+ "ws": "^8.18.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/better-sqlite3": "^7.6.12",
57
+ "@types/ws": "^8.5.14",
58
+ "typescript": "^5.7.3",
59
+ "vitest": "^4.0.18"
60
+ }
61
+ }
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env npx tsx
2
+ /**
3
+ * copilot-bridge check — Validate an existing installation.
4
+ *
5
+ * Usage: npm run check
6
+ * npx tsx scripts/check.ts
7
+ */
8
+
9
+ import * as fs from 'node:fs';
10
+ import * as path from 'node:path';
11
+ import * as os from 'node:os';
12
+ import { heading, printCheck, printSummary, info, dim, type CheckResult } from './lib/output.js';
13
+ import { runAllPrereqs } from './lib/prerequisites.js';
14
+ import { pingServer, validateBotToken, checkChannelAccess } from './lib/mattermost.js';
15
+ import { getConfigPath, getConfigDir } from './lib/config-gen.js';
16
+ import { detectPlatform, getServiceStatus } from './lib/service.js';
17
+
18
+ async function main() {
19
+ console.log();
20
+ heading('🔍 copilot-bridge check');
21
+ dim('Validating your installation...\n');
22
+
23
+ const results: CheckResult[] = [];
24
+
25
+ // --- Prerequisites ---
26
+ heading('Prerequisites');
27
+ const prereqs = runAllPrereqs();
28
+ for (const check of prereqs) {
29
+ printCheck(check);
30
+ results.push(check);
31
+ }
32
+
33
+ // --- Config file ---
34
+ heading('Configuration');
35
+ const configPath = getConfigPath();
36
+
37
+ if (!fs.existsSync(configPath)) {
38
+ const result: CheckResult = { status: 'fail', label: 'Config file', detail: `not found at ${configPath}` };
39
+ printCheck(result);
40
+ results.push(result);
41
+ info('Run "npm run init" to create a config file.');
42
+ printSummary(results);
43
+ process.exit(results.some(r => r.status === 'fail') ? 1 : 0);
44
+ }
45
+
46
+ // Parse config
47
+ let config: any;
48
+ try {
49
+ const raw = fs.readFileSync(configPath, 'utf-8');
50
+ config = JSON.parse(raw);
51
+ const result: CheckResult = { status: 'pass', label: `Config: ${configPath}` };
52
+ printCheck(result);
53
+ results.push(result);
54
+ } catch (err: any) {
55
+ const result: CheckResult = { status: 'fail', label: 'Config file', detail: `invalid JSON: ${err.message}` };
56
+ printCheck(result);
57
+ results.push(result);
58
+ printSummary(results);
59
+ process.exit(1);
60
+ }
61
+
62
+ // Validate required fields
63
+ if (!config.platforms?.mattermost) {
64
+ const result: CheckResult = { status: 'fail', label: 'Config structure', detail: 'missing platforms.mattermost' };
65
+ printCheck(result);
66
+ results.push(result);
67
+ } else {
68
+ const result: CheckResult = { status: 'pass', label: 'Config structure', detail: 'platforms.mattermost present' };
69
+ printCheck(result);
70
+ results.push(result);
71
+ }
72
+
73
+ if (!config.channels || !Array.isArray(config.channels) || config.channels.length === 0) {
74
+ const result: CheckResult = { status: 'warn', label: 'Channels', detail: 'none configured — DMs will still work' };
75
+ printCheck(result);
76
+ results.push(result);
77
+ }
78
+
79
+ // --- Mattermost connectivity ---
80
+ const mmConfig = config.platforms?.mattermost;
81
+ if (mmConfig?.url) {
82
+ heading('Mattermost');
83
+ const pingResult = await pingServer(mmConfig.url);
84
+ printCheck(pingResult);
85
+ results.push(pingResult);
86
+
87
+ // Validate bot tokens
88
+ const bots: Array<{ name: string; token: string }> = [];
89
+
90
+ if (mmConfig.botToken) {
91
+ bots.push({ name: 'default', token: mmConfig.botToken });
92
+ }
93
+ if (mmConfig.bots && typeof mmConfig.bots === 'object') {
94
+ for (const [name, botConfig] of Object.entries(mmConfig.bots)) {
95
+ if ((botConfig as any)?.token) {
96
+ bots.push({ name, token: (botConfig as any).token });
97
+ }
98
+ }
99
+ }
100
+
101
+ if (bots.length === 0) {
102
+ const result: CheckResult = { status: 'fail', label: 'Bot tokens', detail: 'no botToken or bots configured' };
103
+ printCheck(result);
104
+ results.push(result);
105
+ }
106
+
107
+ for (const bot of bots) {
108
+ const validation = await validateBotToken(mmConfig.url, bot.token);
109
+ printCheck(validation.result);
110
+ results.push(validation.result);
111
+ }
112
+
113
+ // Validate channel access
114
+ if (config.channels?.length > 0) {
115
+ heading('Channels (from config)');
116
+ const primaryToken = bots[0]?.token;
117
+ if (primaryToken) {
118
+ for (const ch of config.channels) {
119
+ const channelBot = bots.find(b => b.name === ch.bot) || bots[0];
120
+ const access = await checkChannelAccess(mmConfig.url, channelBot.token, ch.id, ch.name);
121
+ printCheck(access);
122
+ results.push(access);
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ // --- Working directories ---
129
+ if (config.channels?.length > 0) {
130
+ heading('Working Directories');
131
+ for (const ch of config.channels) {
132
+ if (!ch.workingDirectory) {
133
+ const result: CheckResult = { status: 'warn', label: `Channel ${ch.id}`, detail: 'no workingDirectory set' };
134
+ printCheck(result);
135
+ results.push(result);
136
+ continue;
137
+ }
138
+ if (fs.existsSync(ch.workingDirectory)) {
139
+ const result: CheckResult = { status: 'pass', label: ch.workingDirectory };
140
+ printCheck(result);
141
+ results.push(result);
142
+ } else {
143
+ const result: CheckResult = { status: 'fail', label: ch.workingDirectory, detail: 'directory does not exist' };
144
+ printCheck(result);
145
+ results.push(result);
146
+ }
147
+ }
148
+ }
149
+
150
+ // --- Database ---
151
+ heading('Database');
152
+ const dbPath = path.join(getConfigDir(), 'state.db');
153
+ let dbExists = false;
154
+ if (fs.existsSync(dbPath)) {
155
+ dbExists = true;
156
+ const stats = fs.statSync(dbPath);
157
+ const sizeKb = Math.round(stats.size / 1024);
158
+ const result: CheckResult = { status: 'pass', label: `Database: ${dbPath}`, detail: `${sizeKb} KB` };
159
+ printCheck(result);
160
+ results.push(result);
161
+ } else {
162
+ const result: CheckResult = { status: 'warn', label: 'Database', detail: `not yet created at ${dbPath} — will be created on first run` };
163
+ printCheck(result);
164
+ results.push(result);
165
+ }
166
+
167
+ // --- Dynamic channels (from database) ---
168
+ if (dbExists) {
169
+ try {
170
+ const Database = (await import('better-sqlite3')).default;
171
+ const db = new Database(dbPath, { readonly: true });
172
+ const dynamicChannels = db.prepare(
173
+ 'SELECT channel_id, platform, bot, is_dm FROM dynamic_channels'
174
+ ).all() as Array<{ channel_id: string; platform: string; bot: string; is_dm: number }>;
175
+ db.close();
176
+
177
+ if (dynamicChannels.length > 0) {
178
+ heading('Dynamic Channels (from database)');
179
+ dim(' Auto-discovered channels not in config.json (DMs, etc.)');
180
+ const dmCount = dynamicChannels.filter(c => c.is_dm).length;
181
+ const groupCount = dynamicChannels.length - dmCount;
182
+ const parts: string[] = [];
183
+ if (dmCount > 0) parts.push(`${dmCount} DM(s)`);
184
+ if (groupCount > 0) parts.push(`${groupCount} group channel(s)`);
185
+ const result: CheckResult = {
186
+ status: 'pass',
187
+ label: `${dynamicChannels.length} dynamic channel(s)`,
188
+ detail: parts.join(', '),
189
+ };
190
+ printCheck(result);
191
+ results.push(result);
192
+ }
193
+ } catch {
194
+ // DB might not have dynamic_channels table yet — not an error
195
+ }
196
+ }
197
+
198
+ // --- Workspaces ---
199
+ heading('Workspaces');
200
+ const workspacesDir = path.join(getConfigDir(), 'workspaces');
201
+ if (fs.existsSync(workspacesDir)) {
202
+ const botDirs = fs.readdirSync(workspacesDir).filter(f =>
203
+ fs.statSync(path.join(workspacesDir, f)).isDirectory()
204
+ );
205
+ if (botDirs.length > 0) {
206
+ const result: CheckResult = { status: 'pass', label: `Workspaces: ${botDirs.length} bot(s)`, detail: botDirs.join(', ') };
207
+ printCheck(result);
208
+ results.push(result);
209
+ } else {
210
+ const result: CheckResult = { status: 'warn', label: 'Workspaces', detail: 'directory exists but no bots initialized yet' };
211
+ printCheck(result);
212
+ results.push(result);
213
+ }
214
+ } else {
215
+ const result: CheckResult = { status: 'warn', label: 'Workspaces', detail: 'not yet created — will be initialized on first run' };
216
+ printCheck(result);
217
+ results.push(result);
218
+ }
219
+
220
+ // --- Service status ---
221
+ heading('Service');
222
+ const serviceStatus = getServiceStatus();
223
+ if (serviceStatus.running) {
224
+ const result: CheckResult = { status: 'pass', label: 'Service running', detail: serviceStatus.detail };
225
+ printCheck(result);
226
+ results.push(result);
227
+ } else {
228
+ const platform = detectPlatform();
229
+ const serviceHint = platform === 'macos'
230
+ ? 'install with: npm run install-service'
231
+ : platform === 'linux'
232
+ ? 'install with: npm run install-service (requires sudo)'
233
+ : 'start with: npm run dev (or npm start)';
234
+ const result: CheckResult = { status: 'warn', label: 'Service not running', detail: serviceHint };
235
+ printCheck(result);
236
+ results.push(result);
237
+ }
238
+
239
+ // --- MCP servers (optional) ---
240
+ const mcpConfigPath = path.join(os.homedir(), '.copilot', 'mcp-config.json');
241
+ if (fs.existsSync(mcpConfigPath)) {
242
+ heading('MCP Servers (user-level)');
243
+ dim(` ${mcpConfigPath}`);
244
+ try {
245
+ const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf-8'));
246
+ const servers = Object.keys(mcpConfig.mcpServers || {});
247
+ if (servers.length > 0) {
248
+ const result: CheckResult = { status: 'pass', label: `MCP: ${servers.length} server(s)`, detail: servers.join(', ') };
249
+ printCheck(result);
250
+ results.push(result);
251
+ }
252
+ } catch {
253
+ const result: CheckResult = { status: 'warn', label: 'MCP config', detail: 'exists but could not parse' };
254
+ printCheck(result);
255
+ results.push(result);
256
+ }
257
+ }
258
+
259
+ // --- Summary ---
260
+ printSummary(results);
261
+ process.exit(results.some(r => r.status === 'fail') ? 1 : 0);
262
+ }
263
+
264
+ main().catch((err) => {
265
+ console.error('\nCheck failed:', err.message || err);
266
+ process.exit(1);
267
+ });
@@ -0,0 +1,41 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.copilot-bridge</string>
7
+
8
+ <key>ProgramArguments</key>
9
+ <array>
10
+ <string>/opt/homebrew/bin/npx</string>
11
+ <string>tsx</string>
12
+ <string>src/index.ts</string>
13
+ </array>
14
+
15
+ <key>WorkingDirectory</key>
16
+ <string>/Users/USERNAME/path/to/copilot-bridge</string>
17
+
18
+ <key>EnvironmentVariables</key>
19
+ <dict>
20
+ <key>PATH</key>
21
+ <string>/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
22
+ <key>HOME</key>
23
+ <string>/Users/USERNAME</string>
24
+ </dict>
25
+
26
+ <key>RunAtLoad</key>
27
+ <true/>
28
+
29
+ <key>KeepAlive</key>
30
+ <true/>
31
+
32
+ <key>ThrottleInterval</key>
33
+ <integer>10</integer>
34
+
35
+ <key>StandardOutPath</key>
36
+ <string>/tmp/copilot-bridge.log</string>
37
+
38
+ <key>StandardErrorPath</key>
39
+ <string>/tmp/copilot-bridge.log</string>
40
+ </dict>
41
+ </plist>
@@ -0,0 +1,30 @@
1
+ # copilot-bridge systemd service
2
+ #
3
+ # Install:
4
+ # sudo cp scripts/copilot-bridge.service /etc/systemd/system/
5
+ # # Edit ExecStart, WorkingDirectory, User, and HOME below
6
+ # sudo systemctl daemon-reload
7
+ # sudo systemctl enable --now copilot-bridge
8
+ #
9
+ # Logs:
10
+ # sudo journalctl -u copilot-bridge -f
11
+ #
12
+ # Restart:
13
+ # sudo systemctl restart copilot-bridge
14
+
15
+ [Unit]
16
+ Description=Copilot Bridge
17
+ After=network.target
18
+
19
+ [Service]
20
+ Type=simple
21
+ User=username
22
+ ExecStart=/usr/local/bin/node /path/to/copilot-bridge/node_modules/.bin/tsx dist/index.js
23
+ WorkingDirectory=/path/to/copilot-bridge
24
+ Environment=HOME=/home/username
25
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
26
+ Restart=always
27
+ RestartSec=10
28
+
29
+ [Install]
30
+ WantedBy=multi-user.target