gitswarm 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -0
- package/bin/gitswarm.js +840 -0
- package/package.json +64 -0
- package/src/config-reader.js +149 -0
- package/src/core/activity.js +37 -0
- package/src/core/council.js +425 -0
- package/src/core/permissions.js +17 -0
- package/src/core/stages.js +49 -0
- package/src/core/tasks.js +217 -0
- package/src/federation.js +1602 -0
- package/src/index.js +21 -0
- package/src/merge-lock.js +89 -0
- package/src/plugins/builtins.js +46 -0
- package/src/plugins/safe-outputs.js +187 -0
- package/src/shared/field-normalize.js +44 -0
- package/src/shared/ids.js +48 -0
- package/src/shared/permissions.js +345 -0
- package/src/shared/query-adapter.js +90 -0
- package/src/shared/stages.js +243 -0
- package/src/store/schema.js +569 -0
- package/src/store/sqlite.js +142 -0
- package/src/sync-client.js +476 -0
package/bin/gitswarm.js
ADDED
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* gitswarm — standalone CLI for local multi-agent federation coordination.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* gitswarm init [--name <name>] [--model solo|guild|open] [--mode swarm|review|gated]
|
|
8
|
+
* gitswarm agent register <name> [--desc <description>]
|
|
9
|
+
* gitswarm agent list
|
|
10
|
+
* gitswarm agent info <name|id>
|
|
11
|
+
* gitswarm workspace create --as <agent> [--name <n>] [--task <id>] [--fork <stream>]
|
|
12
|
+
* gitswarm workspace list
|
|
13
|
+
* gitswarm workspace destroy <agent> [--abandon]
|
|
14
|
+
* gitswarm commit --as <agent> -m <message>
|
|
15
|
+
* gitswarm stream list [--status <s>]
|
|
16
|
+
* gitswarm stream info <stream-id>
|
|
17
|
+
* gitswarm stream diff <stream-id>
|
|
18
|
+
* gitswarm review submit <stream-id> approve|request_changes --as <agent> [--feedback <f>]
|
|
19
|
+
* gitswarm review list <stream-id>
|
|
20
|
+
* gitswarm review check <stream-id>
|
|
21
|
+
* gitswarm merge <stream-id> --as <agent>
|
|
22
|
+
* gitswarm stabilize
|
|
23
|
+
* gitswarm promote [--tag <t>]
|
|
24
|
+
* gitswarm task create <title> [--desc <d>] [--priority <p>] [--as <agent>]
|
|
25
|
+
* gitswarm task list [--status <s>]
|
|
26
|
+
* gitswarm task claim <id> --as <agent>
|
|
27
|
+
* gitswarm task submit <claim-id> --as <agent> [--notes <n>]
|
|
28
|
+
* gitswarm task review <claim-id> approve|reject --as <agent> [--notes <n>]
|
|
29
|
+
* gitswarm council create [--min-karma <n>] [--quorum <n>]
|
|
30
|
+
* gitswarm council propose <type> <title> --as <agent>
|
|
31
|
+
* gitswarm council vote <proposal-id> for|against --as <agent>
|
|
32
|
+
* gitswarm council status
|
|
33
|
+
* gitswarm status
|
|
34
|
+
* gitswarm log [--limit <n>]
|
|
35
|
+
* gitswarm config [key] [value]
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { resolve } from 'path';
|
|
39
|
+
import { Federation } from '../src/federation.js';
|
|
40
|
+
|
|
41
|
+
// ── Argument parsing ───────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
function parseArgs(argv) {
|
|
44
|
+
const args = argv.slice(2);
|
|
45
|
+
const positional = [];
|
|
46
|
+
const flags = {};
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < args.length; i++) {
|
|
49
|
+
if (args[i] === '-m' && i + 1 < args.length) {
|
|
50
|
+
// Special handling for -m <message>
|
|
51
|
+
flags.m = args[++i];
|
|
52
|
+
} else if (args[i].startsWith('--')) {
|
|
53
|
+
const key = args[i].slice(2);
|
|
54
|
+
if (i + 1 >= args.length || args[i + 1].startsWith('--')) {
|
|
55
|
+
flags[key] = true;
|
|
56
|
+
} else {
|
|
57
|
+
flags[key] = args[++i];
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
positional.push(args[i]);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { positional, flags };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Formatting helpers ─────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
function table(rows, columns) {
|
|
70
|
+
if (rows.length === 0) { console.log(' (none)'); return; }
|
|
71
|
+
const widths = columns.map(c =>
|
|
72
|
+
Math.max(c.label.length, ...rows.map(r => String(c.get(r) ?? '').length))
|
|
73
|
+
);
|
|
74
|
+
const header = columns.map((c, i) => c.label.padEnd(widths[i])).join(' ');
|
|
75
|
+
console.log(` ${header}`);
|
|
76
|
+
console.log(` ${widths.map(w => '─'.repeat(w)).join('──')}`);
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
const line = columns.map((c, i) => String(c.get(row) ?? '').padEnd(widths[i])).join(' ');
|
|
79
|
+
console.log(` ${line}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function short(id) {
|
|
84
|
+
return id ? id.slice(0, 8) : '—';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function timeAgo(ts) {
|
|
88
|
+
if (!ts) return '—';
|
|
89
|
+
const ms = typeof ts === 'number' ? Date.now() - ts : Date.now() - new Date(ts).getTime();
|
|
90
|
+
const mins = Math.floor(ms / 60000);
|
|
91
|
+
if (mins < 1) return 'just now';
|
|
92
|
+
if (mins < 60) return `${mins}m ago`;
|
|
93
|
+
const hours = Math.floor(mins / 60);
|
|
94
|
+
if (hours < 24) return `${hours}h ago`;
|
|
95
|
+
return `${Math.floor(hours / 24)}d ago`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Commands ───────────────────────────────────────────────
|
|
99
|
+
|
|
100
|
+
const commands = {};
|
|
101
|
+
|
|
102
|
+
// --- init ---
|
|
103
|
+
commands.init = (_fed, { flags }) => {
|
|
104
|
+
const cwd = resolve(process.cwd());
|
|
105
|
+
try {
|
|
106
|
+
const { config } = Federation.init(cwd, {
|
|
107
|
+
name: flags.name,
|
|
108
|
+
merge_mode: flags.mode,
|
|
109
|
+
ownership_model: flags.model,
|
|
110
|
+
agent_access: flags.access,
|
|
111
|
+
consensus_threshold: flags.threshold ? parseFloat(flags.threshold) : undefined,
|
|
112
|
+
min_reviews: flags['min-reviews'] ? parseInt(flags['min-reviews']) : undefined,
|
|
113
|
+
buffer_branch: flags['buffer-branch'],
|
|
114
|
+
promote_target: flags['promote-target'],
|
|
115
|
+
stabilize_command: flags['stabilize-command'],
|
|
116
|
+
});
|
|
117
|
+
console.log(`Initialised gitswarm federation: ${config.name}`);
|
|
118
|
+
console.log(` mode: ${config.merge_mode}`);
|
|
119
|
+
console.log(` model: ${config.ownership_model}`);
|
|
120
|
+
console.log(` access: ${config.agent_access}`);
|
|
121
|
+
console.log(` consensus: ${config.consensus_threshold}`);
|
|
122
|
+
console.log(` store: .gitswarm/federation.db`);
|
|
123
|
+
console.log('\nNext steps:');
|
|
124
|
+
console.log(' gitswarm agent register <name> Register an agent');
|
|
125
|
+
console.log(' gitswarm workspace create --as <a> Create an isolated workspace');
|
|
126
|
+
console.log(' gitswarm task create <title> Create a task');
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error(`Error: ${e.message}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// --- agent ---
|
|
134
|
+
commands.agent = async (fed, { positional, flags }) => {
|
|
135
|
+
const sub = positional[0];
|
|
136
|
+
|
|
137
|
+
if (sub === 'register') {
|
|
138
|
+
const name = positional[1] || flags.name;
|
|
139
|
+
if (!name) { console.error('Usage: gitswarm agent register <name>'); process.exit(1); }
|
|
140
|
+
const { agent, api_key } = await fed.registerAgent(name, flags.desc || '');
|
|
141
|
+
console.log(`Agent registered: ${agent.name}`);
|
|
142
|
+
console.log(` id: ${agent.id}`);
|
|
143
|
+
console.log(` api_key: ${api_key} (save this — shown only once)`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (sub === 'list') {
|
|
148
|
+
const agents = await fed.listAgents();
|
|
149
|
+
table(agents, [
|
|
150
|
+
{ label: 'ID', get: r => short(r.id) },
|
|
151
|
+
{ label: 'NAME', get: r => r.name },
|
|
152
|
+
{ label: 'KARMA', get: r => r.karma },
|
|
153
|
+
{ label: 'STATUS', get: r => r.status },
|
|
154
|
+
{ label: 'CREATED', get: r => r.created_at?.slice(0, 10) },
|
|
155
|
+
]);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (sub === 'info') {
|
|
160
|
+
const ref = positional[1];
|
|
161
|
+
if (!ref) { console.error('Usage: gitswarm agent info <name|id>'); process.exit(1); }
|
|
162
|
+
const agent = await fed.getAgent(ref);
|
|
163
|
+
if (!agent) { console.error(`Agent not found: ${ref}`); process.exit(1); }
|
|
164
|
+
console.log(`Agent: ${agent.name}`);
|
|
165
|
+
console.log(` id: ${agent.id}`);
|
|
166
|
+
console.log(` karma: ${agent.karma}`);
|
|
167
|
+
console.log(` status: ${agent.status}`);
|
|
168
|
+
console.log(` description: ${agent.description || '—'}`);
|
|
169
|
+
console.log(` created: ${agent.created_at}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.error('Usage: gitswarm agent <register|list|info>');
|
|
174
|
+
process.exit(1);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// --- workspace ---
|
|
178
|
+
commands.workspace = async (fed, { positional, flags }) => {
|
|
179
|
+
const sub = positional[0];
|
|
180
|
+
|
|
181
|
+
if (sub === 'create') {
|
|
182
|
+
if (!flags.as) {
|
|
183
|
+
console.error('Usage: gitswarm workspace create --as <agent> [--name <n>] [--task <id>] [--fork <stream>]');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
187
|
+
const taskId = flags.task ? await resolveId(fed, 'tasks', flags.task) : undefined;
|
|
188
|
+
const ws = await fed.createWorkspace({
|
|
189
|
+
agentId: agent.id,
|
|
190
|
+
name: flags.name,
|
|
191
|
+
taskId,
|
|
192
|
+
dependsOn: flags.fork,
|
|
193
|
+
});
|
|
194
|
+
console.log(`Workspace created for ${agent.name}`);
|
|
195
|
+
console.log(` stream: ${ws.streamId}`);
|
|
196
|
+
console.log(` path: ${ws.path}`);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (sub === 'list') {
|
|
201
|
+
const workspaces = await fed.listWorkspaces();
|
|
202
|
+
table(workspaces, [
|
|
203
|
+
{ label: 'AGENT', get: r => r.agentName },
|
|
204
|
+
{ label: 'STREAM', get: r => short(r.streamId) },
|
|
205
|
+
{ label: 'NAME', get: r => r.streamName || '—' },
|
|
206
|
+
{ label: 'STATUS', get: r => r.streamStatus || '—' },
|
|
207
|
+
{ label: 'ACTIVE', get: r => timeAgo(r.lastActive) },
|
|
208
|
+
{ label: 'PATH', get: r => r.path },
|
|
209
|
+
]);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (sub === 'destroy') {
|
|
214
|
+
const agentRef = positional[1];
|
|
215
|
+
if (!agentRef) { console.error('Usage: gitswarm workspace destroy <agent> [--abandon]'); process.exit(1); }
|
|
216
|
+
const agent = await fed.resolveAgent(agentRef);
|
|
217
|
+
await fed.destroyWorkspace(agent.id, { abandonStream: !!flags.abandon });
|
|
218
|
+
console.log(`Workspace destroyed for ${agent.name}`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.error('Usage: gitswarm workspace <create|list|destroy>');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
// --- commit ---
|
|
227
|
+
commands.commit = async (fed, { flags }) => {
|
|
228
|
+
if (!flags.as || !flags.m) {
|
|
229
|
+
console.error('Usage: gitswarm commit --as <agent> -m <message>');
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
233
|
+
const result = await fed.commit({
|
|
234
|
+
agentId: agent.id,
|
|
235
|
+
message: flags.m,
|
|
236
|
+
streamId: flags.stream,
|
|
237
|
+
});
|
|
238
|
+
console.log(`Committed: ${result.commit?.slice(0, 8)}`);
|
|
239
|
+
console.log(` Change-Id: ${result.changeId}`);
|
|
240
|
+
if (result.merged) console.log(` Auto-merged to buffer (swarm mode)`);
|
|
241
|
+
if (result.conflicts) console.log(` Merge conflicts: ${result.conflicts.length} files`);
|
|
242
|
+
if (result.mergeError) console.log(` Merge error: ${result.mergeError}`);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
// --- stream ---
|
|
246
|
+
commands.stream = async (fed, { positional, flags }) => {
|
|
247
|
+
const sub = positional[0];
|
|
248
|
+
|
|
249
|
+
if (sub === 'list') {
|
|
250
|
+
const streams = fed._ensureTracker().listStreams({
|
|
251
|
+
status: flags.status || undefined,
|
|
252
|
+
});
|
|
253
|
+
table(streams, [
|
|
254
|
+
{ label: 'ID', get: r => short(r.id) },
|
|
255
|
+
{ label: 'NAME', get: r => r.name },
|
|
256
|
+
{ label: 'AGENT', get: r => short(r.agentId) },
|
|
257
|
+
{ label: 'STATUS', get: r => r.status },
|
|
258
|
+
{ label: 'PARENT', get: r => r.parentStream ? short(r.parentStream) : '—' },
|
|
259
|
+
{ label: 'UPDATED', get: r => timeAgo(r.updatedAt) },
|
|
260
|
+
]);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (sub === 'info') {
|
|
265
|
+
const streamId = positional[1];
|
|
266
|
+
if (!streamId) { console.error('Usage: gitswarm stream info <stream-id>'); process.exit(1); }
|
|
267
|
+
const info = fed.getStreamInfo(streamId);
|
|
268
|
+
if (!info) { console.error(`Stream not found: ${streamId}`); process.exit(1); }
|
|
269
|
+
const { stream, changes, operations, dependencies, children } = info;
|
|
270
|
+
console.log(`Stream: ${stream.name}`);
|
|
271
|
+
console.log(` id: ${stream.id}`);
|
|
272
|
+
console.log(` agent: ${stream.agentId}`);
|
|
273
|
+
console.log(` status: ${stream.status}`);
|
|
274
|
+
console.log(` base: ${stream.baseCommit?.slice(0, 8) || '—'}`);
|
|
275
|
+
console.log(` parent: ${stream.parentStream || '—'}`);
|
|
276
|
+
console.log(` changes: ${changes.length}`);
|
|
277
|
+
console.log(` operations: ${operations.length}`);
|
|
278
|
+
console.log(` deps: ${dependencies.length}`);
|
|
279
|
+
console.log(` children: ${children.length}`);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (sub === 'diff') {
|
|
284
|
+
const streamId = positional[1];
|
|
285
|
+
if (!streamId) { console.error('Usage: gitswarm stream diff <stream-id>'); process.exit(1); }
|
|
286
|
+
const diff = flags.full ? fed.getStreamDiffFull(streamId) : fed.getStreamDiff(streamId);
|
|
287
|
+
console.log(diff || '(no changes)');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
console.error('Usage: gitswarm stream <list|info|diff>');
|
|
292
|
+
process.exit(1);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
// --- review (stream-based) ---
|
|
296
|
+
commands.review = async (fed, { positional, flags }) => {
|
|
297
|
+
const sub = positional[0];
|
|
298
|
+
|
|
299
|
+
if (sub === 'submit') {
|
|
300
|
+
const streamId = positional[1];
|
|
301
|
+
const verdict = positional[2];
|
|
302
|
+
if (!streamId || !verdict || !flags.as) {
|
|
303
|
+
console.error('Usage: gitswarm review submit <stream-id> approve|request_changes --as <agent> [--feedback <f>]');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
307
|
+
await fed.submitReview(streamId, agent.id, verdict, flags.feedback || '', {
|
|
308
|
+
isHuman: !!flags.human,
|
|
309
|
+
});
|
|
310
|
+
console.log(`Review submitted: ${verdict}`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (sub === 'list') {
|
|
315
|
+
const streamId = positional[1];
|
|
316
|
+
if (!streamId) { console.error('Usage: gitswarm review list <stream-id>'); process.exit(1); }
|
|
317
|
+
const reviews = await fed.getReviews(streamId);
|
|
318
|
+
table(reviews, [
|
|
319
|
+
{ label: 'REVIEWER', get: r => r.reviewer_name || short(r.reviewer_id) },
|
|
320
|
+
{ label: 'VERDICT', get: r => r.verdict },
|
|
321
|
+
{ label: 'HUMAN', get: r => r.is_human ? 'yes' : 'no' },
|
|
322
|
+
{ label: 'FEEDBACK', get: r => (r.feedback || '').slice(0, 50) },
|
|
323
|
+
{ label: 'DATE', get: r => r.reviewed_at?.slice(0, 10) },
|
|
324
|
+
]);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (sub === 'check') {
|
|
329
|
+
const streamId = positional[1];
|
|
330
|
+
if (!streamId) { console.error('Usage: gitswarm review check <stream-id>'); process.exit(1); }
|
|
331
|
+
const result = await fed.checkConsensus(streamId);
|
|
332
|
+
console.log(`Consensus: ${result.reached ? 'REACHED' : 'NOT REACHED'}`);
|
|
333
|
+
console.log(` reason: ${result.reason}`);
|
|
334
|
+
if (result.ratio !== undefined) console.log(` ratio: ${result.ratio} (threshold: ${result.threshold})`);
|
|
335
|
+
if (result.approvals !== undefined) console.log(` approvals: ${result.approvals} rejections: ${result.rejections}`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.error('Usage: gitswarm review <submit|list|check>');
|
|
340
|
+
process.exit(1);
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
// --- merge ---
|
|
344
|
+
commands.merge = async (fed, { positional, flags }) => {
|
|
345
|
+
const streamId = positional[0];
|
|
346
|
+
if (!streamId || !flags.as) {
|
|
347
|
+
console.error('Usage: gitswarm merge <stream-id> --as <agent>');
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
351
|
+
const result = await fed.mergeToBuffer(streamId, agent.id);
|
|
352
|
+
console.log(`Merge queued: ${short(result.entryId)}`);
|
|
353
|
+
if (result.queueResult) {
|
|
354
|
+
console.log(` processed: ${result.queueResult.processed || 0}`);
|
|
355
|
+
console.log(` merged: ${result.queueResult.merged || 0}`);
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// --- stabilize ---
|
|
360
|
+
commands.stabilize = async (fed) => {
|
|
361
|
+
const result = await fed.stabilize();
|
|
362
|
+
if (result.success) {
|
|
363
|
+
console.log(`Stabilization: GREEN`);
|
|
364
|
+
console.log(` tag: ${result.tag}`);
|
|
365
|
+
if (result.promoted) console.log(` promoted to main`);
|
|
366
|
+
} else {
|
|
367
|
+
console.log(`Stabilization: RED`);
|
|
368
|
+
if (result.output) console.log(` output: ${result.output.slice(0, 200)}`);
|
|
369
|
+
if (result.reverted) console.log(` reverted stream: ${result.reverted.streamId}`);
|
|
370
|
+
if (result.revertError) console.log(` revert error: ${result.revertError}`);
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// --- promote ---
|
|
375
|
+
commands.promote = async (fed, { flags }) => {
|
|
376
|
+
const result = await fed.promote({ tag: flags.tag });
|
|
377
|
+
console.log(`Promoted: ${result.from} → ${result.to}`);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
// --- task ---
|
|
381
|
+
commands.task = async (fed, { positional, flags }) => {
|
|
382
|
+
const repo = await fed.repo();
|
|
383
|
+
if (!repo) { console.error('No repo found. Run gitswarm init first.'); process.exit(1); }
|
|
384
|
+
const sub = positional[0];
|
|
385
|
+
|
|
386
|
+
if (sub === 'create') {
|
|
387
|
+
const title = positional.slice(1).join(' ') || flags.title;
|
|
388
|
+
if (!title) { console.error('Usage: gitswarm task create <title> [--as <agent>]'); process.exit(1); }
|
|
389
|
+
const agent = flags.as ? await fed.resolveAgent(flags.as) : null;
|
|
390
|
+
const task = await fed.tasks.create(repo.id, {
|
|
391
|
+
title,
|
|
392
|
+
description: flags.desc || '',
|
|
393
|
+
priority: flags.priority || 'medium',
|
|
394
|
+
amount: flags.amount ? parseInt(flags.amount) : 0,
|
|
395
|
+
difficulty: flags.difficulty,
|
|
396
|
+
}, agent?.id);
|
|
397
|
+
console.log(`Task created: ${short(task.id)} ${task.title}`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (sub === 'list') {
|
|
402
|
+
const tasks = await fed.tasks.list(repo.id, { status: flags.status });
|
|
403
|
+
table(tasks, [
|
|
404
|
+
{ label: 'ID', get: r => short(r.id) },
|
|
405
|
+
{ label: 'STATUS', get: r => r.status },
|
|
406
|
+
{ label: 'PRIORITY', get: r => r.priority },
|
|
407
|
+
{ label: 'TITLE', get: r => r.title },
|
|
408
|
+
{ label: 'CLAIMS', get: r => r.active_claims || 0 },
|
|
409
|
+
{ label: 'CREATOR', get: r => r.creator_name || '—' },
|
|
410
|
+
]);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (sub === 'claim') {
|
|
415
|
+
const taskId = positional[1];
|
|
416
|
+
if (!taskId || !flags.as) { console.error('Usage: gitswarm task claim <id> --as <agent>'); process.exit(1); }
|
|
417
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
418
|
+
const fullId = await resolveId(fed, 'tasks', taskId);
|
|
419
|
+
const claim = await fed.tasks.claim(fullId, agent.id, flags.stream);
|
|
420
|
+
console.log(`Task claimed: ${short(claim.id)}`);
|
|
421
|
+
if (claim.stream_id) console.log(` stream: ${claim.stream_id}`);
|
|
422
|
+
|
|
423
|
+
// Mode B: sync task claim to server
|
|
424
|
+
if (fed.sync) {
|
|
425
|
+
try {
|
|
426
|
+
await fed.sync.claimTask(repo.id, fullId, { streamId: flags.stream });
|
|
427
|
+
} catch {
|
|
428
|
+
fed.sync._queueEvent({ type: 'task_claim', data: {
|
|
429
|
+
repoId: repo.id, taskId: fullId, streamId: flags.stream,
|
|
430
|
+
}});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (sub === 'submit') {
|
|
437
|
+
const claimId = positional[1];
|
|
438
|
+
if (!claimId || !flags.as) { console.error('Usage: gitswarm task submit <claim-id> --as <agent>'); process.exit(1); }
|
|
439
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
440
|
+
const fullId = await resolveId(fed, 'task_claims', claimId);
|
|
441
|
+
const result = await fed.tasks.submit(fullId, agent.id, {
|
|
442
|
+
stream_id: flags.stream,
|
|
443
|
+
notes: flags.notes || '',
|
|
444
|
+
});
|
|
445
|
+
console.log(`Submission recorded: ${short(result.id)}`);
|
|
446
|
+
|
|
447
|
+
// Mode B: sync task submission to server
|
|
448
|
+
if (fed.sync) {
|
|
449
|
+
try {
|
|
450
|
+
await fed.sync.syncTaskSubmission(repo.id, result.task_id, fullId, {
|
|
451
|
+
streamId: result.stream_id,
|
|
452
|
+
notes: flags.notes || '',
|
|
453
|
+
});
|
|
454
|
+
} catch {
|
|
455
|
+
fed.sync._queueEvent({ type: 'task_submission', data: {
|
|
456
|
+
repoId: repo.id, taskId: result.task_id, claimId: fullId,
|
|
457
|
+
streamId: result.stream_id, notes: flags.notes || '',
|
|
458
|
+
}});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (sub === 'review') {
|
|
465
|
+
const claimId = positional[1];
|
|
466
|
+
const decision = positional[2];
|
|
467
|
+
if (!claimId || !decision || !flags.as) {
|
|
468
|
+
console.error('Usage: gitswarm task review <claim-id> approve|reject --as <agent>');
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
472
|
+
const fullId = await resolveId(fed, 'task_claims', claimId);
|
|
473
|
+
const result = await fed.tasks.review(fullId, agent.id, decision, flags.notes);
|
|
474
|
+
console.log(`Review: ${result.action}`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.error('Usage: gitswarm task <create|list|claim|submit|review>');
|
|
479
|
+
process.exit(1);
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
// --- council ---
|
|
483
|
+
commands.council = async (fed, { positional, flags }) => {
|
|
484
|
+
const repo = await fed.repo();
|
|
485
|
+
if (!repo) { console.error('No repo found.'); process.exit(1); }
|
|
486
|
+
const sub = positional[0];
|
|
487
|
+
|
|
488
|
+
if (sub === 'create') {
|
|
489
|
+
const council = await fed.council.create(repo.id, {
|
|
490
|
+
min_karma: flags['min-karma'] ? parseInt(flags['min-karma']) : undefined,
|
|
491
|
+
min_contributions: flags['min-contribs'] ? parseInt(flags['min-contribs']) : undefined,
|
|
492
|
+
min_members: flags['min-members'] ? parseInt(flags['min-members']) : undefined,
|
|
493
|
+
max_members: flags['max-members'] ? parseInt(flags['max-members']) : undefined,
|
|
494
|
+
standard_quorum: flags.quorum ? parseInt(flags.quorum) : undefined,
|
|
495
|
+
critical_quorum: flags['critical-quorum'] ? parseInt(flags['critical-quorum']) : undefined,
|
|
496
|
+
});
|
|
497
|
+
console.log(`Council created: ${short(council.id)} status: ${council.status}`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (sub === 'status') {
|
|
502
|
+
const council = await fed.council.getCouncil(repo.id);
|
|
503
|
+
if (!council) { console.log('No council for this repo.'); return; }
|
|
504
|
+
const members = await fed.council.getMembers(council.id);
|
|
505
|
+
const proposals = await fed.council.listProposals(council.id, 'open');
|
|
506
|
+
console.log(`Council: ${council.status}`);
|
|
507
|
+
console.log(` members: ${members.length}/${council.max_members} (min: ${council.min_members})`);
|
|
508
|
+
console.log(` quorum: ${council.standard_quorum} (critical: ${council.critical_quorum})`);
|
|
509
|
+
console.log(` open proposals: ${proposals.length}`);
|
|
510
|
+
if (members.length > 0) {
|
|
511
|
+
console.log('\nMembers:');
|
|
512
|
+
table(members, [
|
|
513
|
+
{ label: 'NAME', get: r => r.agent_name },
|
|
514
|
+
{ label: 'ROLE', get: r => r.role },
|
|
515
|
+
{ label: 'KARMA', get: r => r.karma },
|
|
516
|
+
{ label: 'VOTES', get: r => r.votes_cast },
|
|
517
|
+
]);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (sub === 'add-member') {
|
|
523
|
+
const agentRef = positional[1];
|
|
524
|
+
if (!agentRef) { console.error('Usage: gitswarm council add-member <agent>'); process.exit(1); }
|
|
525
|
+
const council = await fed.council.getCouncil(repo.id);
|
|
526
|
+
if (!council) { console.error('No council. Run gitswarm council create first.'); process.exit(1); }
|
|
527
|
+
const agent = await fed.resolveAgent(agentRef);
|
|
528
|
+
const member = await fed.council.addMember(council.id, agent.id, flags.role || 'member');
|
|
529
|
+
console.log(`Added ${agent.name} to council as ${member.role}`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (sub === 'propose') {
|
|
534
|
+
const type = positional[1];
|
|
535
|
+
const title = positional.slice(2).join(' ') || flags.title;
|
|
536
|
+
if (!type || !title || !flags.as) {
|
|
537
|
+
console.error('Usage: gitswarm council propose <type> <title> --as <agent> [--target <agent>]');
|
|
538
|
+
process.exit(1);
|
|
539
|
+
}
|
|
540
|
+
const council = await fed.council.getCouncil(repo.id);
|
|
541
|
+
if (!council) { console.error('No council.'); process.exit(1); }
|
|
542
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
543
|
+
|
|
544
|
+
const action_data = {};
|
|
545
|
+
if (flags.target) {
|
|
546
|
+
const target = await fed.resolveAgent(flags.target);
|
|
547
|
+
action_data.agent_id = target.id;
|
|
548
|
+
}
|
|
549
|
+
if (flags.role) action_data.role = flags.role;
|
|
550
|
+
if (flags['access-level']) action_data.access_level = flags['access-level'];
|
|
551
|
+
if (flags.stream) action_data.stream_id = flags.stream;
|
|
552
|
+
if (flags.priority) action_data.priority = parseInt(flags.priority);
|
|
553
|
+
if (flags.tag) action_data.tag = flags.tag;
|
|
554
|
+
|
|
555
|
+
const proposal = await fed.council.createProposal(council.id, agent.id, {
|
|
556
|
+
title,
|
|
557
|
+
description: flags.desc || '',
|
|
558
|
+
proposal_type: type,
|
|
559
|
+
action_data,
|
|
560
|
+
});
|
|
561
|
+
console.log(`Proposal created: ${short(proposal.id)} "${proposal.title}"`);
|
|
562
|
+
console.log(` type: ${proposal.proposal_type} quorum: ${proposal.quorum_required}`);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (sub === 'vote') {
|
|
567
|
+
const proposalId = positional[1];
|
|
568
|
+
const vote = positional[2];
|
|
569
|
+
if (!proposalId || !vote || !flags.as) {
|
|
570
|
+
console.error('Usage: gitswarm council vote <proposal-id> for|against|abstain --as <agent>');
|
|
571
|
+
process.exit(1);
|
|
572
|
+
}
|
|
573
|
+
const agent = await fed.resolveAgent(flags.as);
|
|
574
|
+
const fullId = await resolveId(fed, 'council_proposals', proposalId);
|
|
575
|
+
await fed.council.vote(fullId, agent.id, vote, flags.comment);
|
|
576
|
+
console.log(`Vote '${vote}' recorded.`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
if (sub === 'proposals') {
|
|
581
|
+
const council = await fed.council.getCouncil(repo.id);
|
|
582
|
+
if (!council) { console.log('No council.'); return; }
|
|
583
|
+
const proposals = await fed.council.listProposals(council.id, flags.status);
|
|
584
|
+
table(proposals, [
|
|
585
|
+
{ label: 'ID', get: r => short(r.id) },
|
|
586
|
+
{ label: 'STATUS', get: r => r.status },
|
|
587
|
+
{ label: 'TYPE', get: r => r.proposal_type },
|
|
588
|
+
{ label: 'TITLE', get: r => r.title },
|
|
589
|
+
{ label: 'VOTES', get: r => `+${r.votes_for} -${r.votes_against}` },
|
|
590
|
+
{ label: 'QUORUM', get: r => r.quorum_required },
|
|
591
|
+
{ label: 'BY', get: r => r.proposer_name || '—' },
|
|
592
|
+
]);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
console.error('Usage: gitswarm council <create|status|add-member|propose|vote|proposals>');
|
|
597
|
+
process.exit(1);
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// --- status ---
|
|
601
|
+
commands.status = async (fed) => {
|
|
602
|
+
const repo = await fed.repo();
|
|
603
|
+
const config = fed.config();
|
|
604
|
+
const agents = await fed.listAgents();
|
|
605
|
+
|
|
606
|
+
console.log(`Federation: ${config.name || '(unnamed)'}`);
|
|
607
|
+
console.log(` path: ${fed.repoPath}`);
|
|
608
|
+
console.log(` mode: ${repo?.merge_mode || config.merge_mode || 'review'}`);
|
|
609
|
+
console.log(` model: ${repo?.ownership_model || config.ownership_model}`);
|
|
610
|
+
console.log(` access: ${repo?.agent_access || config.agent_access}`);
|
|
611
|
+
console.log(` stage: ${repo?.stage || 'seed'}`);
|
|
612
|
+
console.log(` agents: ${agents.length}`);
|
|
613
|
+
|
|
614
|
+
if (repo) {
|
|
615
|
+
console.log(` buffer: ${repo.buffer_branch || 'buffer'} → ${repo.promote_target || 'main'}`);
|
|
616
|
+
|
|
617
|
+
const { metrics } = await fed.stages.getMetrics(repo.id);
|
|
618
|
+
console.log(` contributors: ${metrics.contributor_count}`);
|
|
619
|
+
console.log(` streams: ${metrics.patch_count}`);
|
|
620
|
+
console.log(` maintainers: ${metrics.maintainer_count}`);
|
|
621
|
+
console.log(` council: ${metrics.has_council ? 'yes' : 'no'}`);
|
|
622
|
+
|
|
623
|
+
const elig = await fed.stages.checkEligibility(repo.id);
|
|
624
|
+
if (elig.next_stage) {
|
|
625
|
+
console.log(`\n Next stage: ${elig.next_stage} (${elig.eligible ? 'ELIGIBLE' : 'not yet'})`);
|
|
626
|
+
if (elig.unmet && elig.unmet.length > 0) {
|
|
627
|
+
for (const u of elig.unmet) {
|
|
628
|
+
console.log(` - ${u.requirement}: ${u.current}/${u.required}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Streams summary
|
|
635
|
+
try {
|
|
636
|
+
const activeStreams = fed.listActiveStreams();
|
|
637
|
+
const workspaces = await fed.listWorkspaces();
|
|
638
|
+
console.log(`\nStreams: ${activeStreams.length} active`);
|
|
639
|
+
console.log(`Workspaces: ${workspaces.length} active`);
|
|
640
|
+
|
|
641
|
+
const queue = fed.tracker.getMergeQueue({ status: 'pending' });
|
|
642
|
+
if (queue.length > 0) {
|
|
643
|
+
console.log(`Merge queue: ${queue.length} pending`);
|
|
644
|
+
}
|
|
645
|
+
} catch {
|
|
646
|
+
// Tracker may not be available
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Check for Tier 2/3 plugin compatibility
|
|
650
|
+
try {
|
|
651
|
+
const pluginWarnings = fed.checkPluginCompatibility();
|
|
652
|
+
if (pluginWarnings.length > 0) {
|
|
653
|
+
console.log('\nWarnings:');
|
|
654
|
+
for (const w of pluginWarnings) {
|
|
655
|
+
console.log(` ! ${w}`);
|
|
656
|
+
}
|
|
657
|
+
console.log(' Connect to a server (Mode B) to enable these plugins.');
|
|
658
|
+
}
|
|
659
|
+
} catch {
|
|
660
|
+
// Non-fatal
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// --- log ---
|
|
665
|
+
commands.log = async (fed, { flags }) => {
|
|
666
|
+
const events = await fed.activity.recent({
|
|
667
|
+
limit: flags.limit ? parseInt(flags.limit) : 20,
|
|
668
|
+
});
|
|
669
|
+
if (events.length === 0) { console.log('No activity yet.'); return; }
|
|
670
|
+
for (const e of events) {
|
|
671
|
+
const ts = e.created_at?.slice(0, 19) || '';
|
|
672
|
+
const agent = e.agent_name || short(e.agent_id);
|
|
673
|
+
console.log(` ${ts} ${agent} ${e.event_type} ${e.target_type || ''}:${short(e.target_id)}`);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// --- config ---
|
|
678
|
+
commands.config = async (fed, { positional, flags }) => {
|
|
679
|
+
// Pull config from server
|
|
680
|
+
if (flags.pull) {
|
|
681
|
+
const result = await fed.pullConfig();
|
|
682
|
+
if (!result) {
|
|
683
|
+
console.error('Not connected to server. Use gitswarm config --pull after connecting (Mode B).');
|
|
684
|
+
process.exit(1);
|
|
685
|
+
}
|
|
686
|
+
if (result.updated.length === 0) {
|
|
687
|
+
console.log('Config is up to date with server.');
|
|
688
|
+
} else {
|
|
689
|
+
console.log(`Updated ${result.updated.length} fields from server:`);
|
|
690
|
+
for (const field of result.updated) {
|
|
691
|
+
console.log(` ${field} = ${result.config[field]}`);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const config = fed.config();
|
|
698
|
+
if (positional.length === 0) {
|
|
699
|
+
console.log(JSON.stringify(config, null, 2));
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
if (positional.length === 1) {
|
|
703
|
+
console.log(config[positional[0]] ?? '(not set)');
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
const { writeFileSync: write } = await import('fs');
|
|
707
|
+
const { join: joinPath } = await import('path');
|
|
708
|
+
config[positional[0]] = positional[1];
|
|
709
|
+
write(joinPath(fed.swarmDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
710
|
+
console.log(`Set ${positional[0]} = ${positional[1]}`);
|
|
711
|
+
};
|
|
712
|
+
|
|
713
|
+
// --- sync ---
|
|
714
|
+
commands.sync = async (fed) => {
|
|
715
|
+
if (!fed.sync) {
|
|
716
|
+
console.error('Not connected to server. Connect with Mode B first.');
|
|
717
|
+
process.exit(1);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Push: flush queued events to server
|
|
721
|
+
console.log('Pushing local events to server...');
|
|
722
|
+
try {
|
|
723
|
+
await fed.sync.flushQueue();
|
|
724
|
+
console.log(' Queue flushed.');
|
|
725
|
+
} catch (err) {
|
|
726
|
+
console.error(` Push failed: ${err.message}`);
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// Pull: poll for server updates
|
|
730
|
+
console.log('Pulling updates from server...');
|
|
731
|
+
const updates = await fed.pollUpdates();
|
|
732
|
+
if (!updates) {
|
|
733
|
+
console.error(' Failed to poll updates.');
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const counts = [];
|
|
738
|
+
if (updates.tasks?.length > 0) counts.push(`${updates.tasks.length} new tasks`);
|
|
739
|
+
if (updates.access_changes?.length > 0) counts.push(`${updates.access_changes.length} access changes`);
|
|
740
|
+
if (updates.proposals?.length > 0) counts.push(`${updates.proposals.length} proposals`);
|
|
741
|
+
if (updates.reviews?.length > 0) counts.push(`${updates.reviews.length} reviews`);
|
|
742
|
+
if (updates.merges?.length > 0) counts.push(`${updates.merges.length} merges`);
|
|
743
|
+
if (updates.config_changes?.length > 0) counts.push(`${updates.config_changes.length} config changes`);
|
|
744
|
+
|
|
745
|
+
if (counts.length === 0) {
|
|
746
|
+
console.log(' Up to date.');
|
|
747
|
+
} else {
|
|
748
|
+
console.log(` Received: ${counts.join(', ')}`);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
// ── ID resolution helper ───────────────────────────────────
|
|
753
|
+
|
|
754
|
+
async function resolveId(fed, tableName, prefix) {
|
|
755
|
+
if (prefix.length >= 32) return prefix;
|
|
756
|
+
const r = await fed.store.query(
|
|
757
|
+
`SELECT id FROM ${tableName} WHERE id LIKE ?`, [`${prefix}%`]
|
|
758
|
+
);
|
|
759
|
+
if (r.rows.length === 0) throw new Error(`No match for ID prefix: ${prefix}`);
|
|
760
|
+
if (r.rows.length > 1) throw new Error(`Ambiguous ID prefix: ${prefix} (${r.rows.length} matches)`);
|
|
761
|
+
return r.rows[0].id;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// ── Main ───────────────────────────────────────────────────
|
|
765
|
+
|
|
766
|
+
async function main() {
|
|
767
|
+
const { positional, flags } = parseArgs(process.argv);
|
|
768
|
+
|
|
769
|
+
if (positional.length === 0 || flags.help) {
|
|
770
|
+
console.log(`gitswarm — local multi-agent federation coordinator
|
|
771
|
+
|
|
772
|
+
Usage:
|
|
773
|
+
gitswarm <command> [subcommand] [options]
|
|
774
|
+
|
|
775
|
+
Commands:
|
|
776
|
+
init Initialise a federation in the current repo
|
|
777
|
+
agent Manage agents (register, list, info)
|
|
778
|
+
workspace Manage agent workspaces (create, list, destroy)
|
|
779
|
+
commit Commit from an agent's workspace
|
|
780
|
+
stream Inspect streams (list, info, diff)
|
|
781
|
+
review Stream reviews (submit, list, check consensus)
|
|
782
|
+
merge Merge a stream to buffer
|
|
783
|
+
stabilize Run tests and tag green/revert red
|
|
784
|
+
promote Promote buffer to main
|
|
785
|
+
task Task distribution (create, list, claim, submit, review)
|
|
786
|
+
council Governance (create, status, propose, vote, add-member)
|
|
787
|
+
status Show federation status
|
|
788
|
+
log View activity log
|
|
789
|
+
config View/set federation config
|
|
790
|
+
|
|
791
|
+
Options:
|
|
792
|
+
--as <agent> Act as a specific agent (name or ID)
|
|
793
|
+
--help Show this help
|
|
794
|
+
|
|
795
|
+
Examples:
|
|
796
|
+
gitswarm init --name my-project --mode review --model guild
|
|
797
|
+
gitswarm agent register architect --desc "System architect agent"
|
|
798
|
+
gitswarm workspace create --as coder --name "feature/auth"
|
|
799
|
+
gitswarm commit --as coder -m "Add auth module"
|
|
800
|
+
gitswarm review submit abc123 approve --as reviewer --feedback "LGTM"
|
|
801
|
+
gitswarm merge abc123 --as reviewer
|
|
802
|
+
gitswarm stabilize
|
|
803
|
+
gitswarm promote
|
|
804
|
+
gitswarm status`);
|
|
805
|
+
process.exit(0);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const cmd = positional[0];
|
|
809
|
+
const rest = { positional: positional.slice(1), flags };
|
|
810
|
+
|
|
811
|
+
// `init` doesn't require an existing federation
|
|
812
|
+
if (cmd === 'init') {
|
|
813
|
+
commands.init(null, rest);
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// All other commands need an open federation
|
|
818
|
+
let fed;
|
|
819
|
+
try {
|
|
820
|
+
fed = Federation.open();
|
|
821
|
+
} catch (e) {
|
|
822
|
+
console.error(e.message);
|
|
823
|
+
process.exit(1);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
try {
|
|
827
|
+
if (!commands[cmd]) {
|
|
828
|
+
console.error(`Unknown command: ${cmd}. Run 'gitswarm --help' for usage.`);
|
|
829
|
+
process.exit(1);
|
|
830
|
+
}
|
|
831
|
+
await commands[cmd](fed, rest);
|
|
832
|
+
} catch (e) {
|
|
833
|
+
console.error(`Error: ${e.message}`);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
} finally {
|
|
836
|
+
fed.close();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
main();
|