@astrosheep/square 0.3.4 → 0.3.6

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 (52) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +3 -2
  2. package/dist/activity-feed.js +26 -18
  3. package/dist/activity.js +23 -22
  4. package/dist/artifact.js +126 -202
  5. package/dist/claude-hook.js +45 -21
  6. package/dist/cli/context.js +143 -0
  7. package/dist/cli/harness-command.js +50 -0
  8. package/dist/cli/maintenance-commands.js +76 -0
  9. package/dist/cli/meta-commands.js +28 -0
  10. package/dist/cli/observation-commands.js +453 -0
  11. package/dist/cli/program.js +48 -0
  12. package/dist/cli/registry.js +40 -0
  13. package/dist/cli/square-commands.js +219 -0
  14. package/dist/cmd/notify-once.js +23 -21
  15. package/dist/compact.js +6 -19
  16. package/dist/decisions.js +53 -86
  17. package/dist/delivery-health.js +104 -210
  18. package/dist/delivery.js +68 -18
  19. package/dist/doctor.js +9 -8
  20. package/dist/harness-claude.js +68 -0
  21. package/dist/harness-codex.js +119 -0
  22. package/dist/harness-links.js +123 -0
  23. package/dist/harness-stage.js +36 -0
  24. package/dist/harness.js +94 -576
  25. package/dist/help.js +44 -35
  26. package/dist/inbox.js +12 -11
  27. package/dist/index.js +30 -129
  28. package/dist/list.js +1 -1
  29. package/dist/model.js +0 -6
  30. package/dist/notification-failures.js +54 -0
  31. package/dist/notifications.js +47 -62
  32. package/dist/paseo-timeline.js +58 -188
  33. package/dist/presentation.js +55 -63
  34. package/dist/presented.js +9 -8
  35. package/dist/registry.js +55 -45
  36. package/dist/runtime.js +26 -137
  37. package/dist/square-application.js +264 -0
  38. package/dist/square-core.js +3 -11
  39. package/dist/square.js +5 -1362
  40. package/dist/stream.js +27 -126
  41. package/dist/wake-sink.js +134 -188
  42. package/dist/watch.js +79 -138
  43. package/extensions/square-opencode.js +1 -1
  44. package/extensions/square-pi.js +8 -130
  45. package/guides/architect.md +3 -3
  46. package/guides/participant.md +25 -16
  47. package/package.json +2 -2
  48. package/skills/brainstorm/SKILL.md +25 -32
  49. package/skills/square/.claude-plugin/plugin.json +1 -1
  50. package/skills/square/SKILL.md +39 -107
  51. package/skills/square-feedback/SKILL.md +4 -4
  52. package/dist/terminal.js +0 -125
@@ -0,0 +1,453 @@
1
+ import { loadSquare } from '../artifact.js';
2
+ import { runClaudeHook, runCodexHook } from '../claude-hook.js';
3
+ import { coreActivities, coreParticipants, coreStatus } from '../decisions.js';
4
+ import { sessionInbox } from '../inbox.js';
5
+ import { cmdListSquares } from '../list.js';
6
+ import { sameName } from '../model.js';
7
+ import { commandPrefix, participantCommandPrefix, renderActivitiesView, renderGrepActivitiesView, renderVisibleEvent, withPathOutput, } from '../presentation.js';
8
+ import { recordLocalJoin } from '../registry.js';
9
+ import { actId, inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName, sayNumberFor, } from '../runtime.js';
10
+ import { cmdStream, cmdStreamNdjson } from '../stream.js';
11
+ import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
12
+ import { cmdWatch } from '../watch.js';
13
+ import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdinSync, requireParticipant, requireValue, usage, } from './context.js';
14
+ export const listCommand = {
15
+ parse: (argv) => argv,
16
+ execute(argv, context) {
17
+ cmdListSquares(argv, () => usage(context.command));
18
+ },
19
+ present: () => { },
20
+ };
21
+ export const streamCommand = {
22
+ parse(argv, context) {
23
+ let ndjson = false;
24
+ let forName;
25
+ for (let index = 0; index < argv.length; index++) {
26
+ if (argv[index] === '--ndjson')
27
+ ndjson = true;
28
+ else if (argv[index] === '--for') {
29
+ forName = requireValue(argv, index, argv[index]);
30
+ index += 1;
31
+ }
32
+ else
33
+ usage(context.command);
34
+ }
35
+ if (forName !== undefined && !ndjson)
36
+ usage(context.command);
37
+ return { ndjson, forName };
38
+ },
39
+ async execute(intent, context) {
40
+ if (intent.ndjson)
41
+ await cmdStreamNdjson(context.squarePath, intent.forName);
42
+ else
43
+ await cmdStream(context.squarePath);
44
+ },
45
+ present: () => { },
46
+ };
47
+ export const catchCommand = {
48
+ parse(argv, context) {
49
+ const name = requireParticipant(context.name);
50
+ let idleMs;
51
+ let mention;
52
+ let replace = false;
53
+ let now = false;
54
+ const participants = [];
55
+ for (let index = 0; index < argv.length; index++) {
56
+ if (argv[index] === '--from') {
57
+ participants.push(...parseNameList(requireValue(argv, index, argv[index]), argv[index]));
58
+ index += 1;
59
+ }
60
+ else if (argv[index] === '--idle') {
61
+ idleMs = parseDurationMs(requireValue(argv, index, argv[index]), argv[index]);
62
+ index += 1;
63
+ }
64
+ else if (argv[index] === '--mention') {
65
+ const value = argv[index + 1];
66
+ if (value !== undefined && !value.startsWith('--')) {
67
+ mention = value;
68
+ index += 1;
69
+ }
70
+ else {
71
+ mention = name;
72
+ }
73
+ }
74
+ else if (argv[index] === '--replace')
75
+ replace = true;
76
+ else if (argv[index] === '--now')
77
+ now = true;
78
+ else
79
+ fail(`✕ catch does not know ${argv[index]}\n» square catch --help`);
80
+ }
81
+ if (now === (idleMs !== undefined))
82
+ fail('catch requires exactly one mode: --now or --idle <duration>.');
83
+ if (replace && now)
84
+ fail('--replace can only be used with --idle.');
85
+ return {
86
+ ...(participants.length > 0 ? { participants } : {}),
87
+ ...(mention === undefined ? {} : { mention }),
88
+ ...(idleMs === undefined ? {} : { idleMs }),
89
+ ...(replace ? { replace } : {}),
90
+ ...(now ? { now } : {}),
91
+ };
92
+ },
93
+ async execute(intent, context) {
94
+ await cmdWatch(context.squarePath, requireParticipant(context.name), intent);
95
+ },
96
+ present: () => { },
97
+ };
98
+ function parseActRef(value, flag) {
99
+ const match = value.trim().match(/^(?:act_)?(\d+)$/i);
100
+ if (!match)
101
+ fail(`Invalid ${flag}: expected an activity id like act_12 or 12.`);
102
+ return Number(match[1]);
103
+ }
104
+ function parseTimestamp(value, flag) {
105
+ const timestamp = parseTimeOrRelative(value, nowMs());
106
+ if (!Number.isFinite(timestamp))
107
+ fail(`Invalid ${flag} timestamp: ${value}`);
108
+ return timestamp;
109
+ }
110
+ function parseHistory(argv, context) {
111
+ const viewer = context.name;
112
+ let lastN = 10;
113
+ let lastNExplicit = false;
114
+ let before;
115
+ let after;
116
+ let afterIndex;
117
+ let atIndex;
118
+ let beforeContext;
119
+ let afterContext;
120
+ let mention;
121
+ let pending = false;
122
+ let full = false;
123
+ let grep;
124
+ let fixed;
125
+ let ids;
126
+ let order;
127
+ let format;
128
+ let countOnly = false;
129
+ let json = false;
130
+ const participants = [];
131
+ for (let index = 0; index < argv.length; index++) {
132
+ const flag = argv[index];
133
+ if (flag === '--limit') {
134
+ const value = argv[index + 1];
135
+ const retry = `${commandPrefix(context.squarePath)} history --limit 30`;
136
+ if (value === undefined || value.startsWith('--'))
137
+ fail(`✕ --limit needs a positive number\n» ${retry}`);
138
+ if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) {
139
+ fail(`✕ --limit needs a positive number\n» ${retry}`);
140
+ }
141
+ lastN = Number(value);
142
+ lastNExplicit = true;
143
+ index += 1;
144
+ }
145
+ else if (flag === '--all') {
146
+ lastN = null;
147
+ lastNExplicit = true;
148
+ }
149
+ else if (flag === '--from') {
150
+ participants.push(...parseNameList(requireValue(argv, index, flag), flag));
151
+ index += 1;
152
+ }
153
+ else if (flag === '--until') {
154
+ before = parseTimestamp(requireValue(argv, index, flag), flag);
155
+ index += 1;
156
+ }
157
+ else if (flag === '--since') {
158
+ after = parseTimestamp(requireValue(argv, index, flag), flag);
159
+ index += 1;
160
+ }
161
+ else if (flag === '--after') {
162
+ afterIndex = parseActRef(requireValue(argv, index, flag), flag);
163
+ index += 1;
164
+ }
165
+ else if (flag === '--at') {
166
+ atIndex = parseActRef(requireValue(argv, index, flag), flag);
167
+ index += 1;
168
+ }
169
+ else if (flag === '-B') {
170
+ beforeContext = parseNonNegativeInteger(requireValue(argv, index, flag), flag);
171
+ index += 1;
172
+ }
173
+ else if (flag === '-A') {
174
+ afterContext = parseNonNegativeInteger(requireValue(argv, index, flag), flag);
175
+ index += 1;
176
+ }
177
+ else if (flag === '-C') {
178
+ const context = parseNonNegativeInteger(requireValue(argv, index, flag), flag);
179
+ beforeContext = context;
180
+ afterContext = context;
181
+ index += 1;
182
+ }
183
+ else if (flag === '--full')
184
+ full = true;
185
+ else if (flag === '--mention') {
186
+ mention = requireValue(argv, index, flag);
187
+ index += 1;
188
+ }
189
+ else if (flag === '--pending')
190
+ pending = true;
191
+ else if (flag === '--grep') {
192
+ grep = requireValue(argv, index, flag);
193
+ index += 1;
194
+ }
195
+ else if (flag === '--fixed') {
196
+ fixed = requireValue(argv, index, flag);
197
+ index += 1;
198
+ }
199
+ else if (flag === '--ids') {
200
+ ids = requireValue(argv, index, flag)
201
+ .split(',')
202
+ .map((item) => item.trim())
203
+ .filter(Boolean)
204
+ .map((item) => parseActRef(item, flag));
205
+ index += 1;
206
+ }
207
+ else if (flag === '--order') {
208
+ const value = requireValue(argv, index, flag);
209
+ if (value !== 'asc' && value !== 'desc')
210
+ fail('Invalid --order: expected asc or desc.');
211
+ order = value;
212
+ index += 1;
213
+ }
214
+ else if (flag === '--format') {
215
+ format = requireValue(argv, index, flag).split(',').map((item) => item.trim()).filter(Boolean);
216
+ index += 1;
217
+ }
218
+ else if (flag === '--count')
219
+ countOnly = true;
220
+ else if (flag === '--json')
221
+ json = true;
222
+ else
223
+ fail(`✕ history does not know ${flag}\n» square history --help`);
224
+ }
225
+ if (pending && !viewer)
226
+ fail('--pending requires --as <name>.');
227
+ if (grep !== undefined && fixed !== undefined)
228
+ fail('--grep and --fixed cannot be combined.');
229
+ if (grep === '' || fixed === '')
230
+ fail('--grep and --fixed require non-empty text.');
231
+ if (!lastNExplicit && (atIndex !== undefined || ids !== undefined || pending))
232
+ lastN = null;
233
+ return {
234
+ lastN,
235
+ participants,
236
+ before,
237
+ after,
238
+ afterIndex,
239
+ atIndex,
240
+ beforeContext,
241
+ afterContext,
242
+ mention,
243
+ pending,
244
+ viewer,
245
+ full,
246
+ grep,
247
+ fixed,
248
+ ids,
249
+ order,
250
+ format,
251
+ countOnly,
252
+ json,
253
+ };
254
+ }
255
+ function renderFields(doc, item, fields) {
256
+ return fields.map((field) => {
257
+ switch (field) {
258
+ case 'id': return actId(item.index);
259
+ case 'author':
260
+ case 'actor': return item.actor ?? '';
261
+ case 'ts':
262
+ case 'at': return formatTimestamp(item.at);
263
+ case 'kind': return item.kind;
264
+ case 'body': return 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
265
+ case 'number': return item.kind === 'say' ? String(sayNumberFor(doc.acts, item)) : '';
266
+ default: return '';
267
+ }
268
+ }).join('\t');
269
+ }
270
+ function jsonLine(doc, item) {
271
+ const act = item;
272
+ return JSON.stringify({
273
+ id: actId(item.index),
274
+ index: item.index,
275
+ kind: act.kind,
276
+ author: act.actor ?? null,
277
+ at: act.at,
278
+ ts: formatTimestamp(act.at),
279
+ body: 'body' in act && typeof act.body === 'string' ? act.body : '',
280
+ number: act.kind === 'say' ? sayNumberFor(doc.acts, act) : null,
281
+ reach: act.kind === 'say' ? act.reach ?? null : null,
282
+ });
283
+ }
284
+ export const historyCommand = {
285
+ parse(argv, context) { return parseHistory(argv, context); },
286
+ execute(options, context) {
287
+ const doc = loadSquare(context.squarePath);
288
+ let events = coreActivities(doc, options);
289
+ const searching = options.grep !== undefined || options.fixed !== undefined;
290
+ const totalMatches = searching ? events.length : 0;
291
+ if (options.lastN != null) {
292
+ events = options.order === 'desc'
293
+ ? events.slice(0, options.lastN)
294
+ : events.slice(-options.lastN);
295
+ }
296
+ if (options.countOnly)
297
+ return `${searching ? totalMatches : events.length}\n`;
298
+ if (options.json)
299
+ return events.map((item) => jsonLine(doc, item)).join('\n') + (events.length > 0 ? '\n' : '');
300
+ if (options.format !== undefined && options.format.length > 0) {
301
+ return events.map((item) => renderFields(doc, item, options.format)).join('\n') + (events.length > 0 ? '\n' : '');
302
+ }
303
+ const pattern = options.grep ?? options.fixed;
304
+ const output = pattern === undefined || pattern === ''
305
+ ? renderActivitiesView(doc, events, null, options.full, context.squarePath, options.viewer ?? '')
306
+ : renderGrepActivitiesView(events, totalMatches, options.full, context.squarePath, pattern, options.fixed !== undefined);
307
+ return withPathOutput(context.squarePath, output, { participantCount: inSquareCount(doc) });
308
+ },
309
+ present: (result) => process.stdout.write(result),
310
+ };
311
+ export const warmupCommand = {
312
+ parse(argv, context) { if (argv.length > 0)
313
+ usage(context.command); return undefined; },
314
+ execute(_intent, context) {
315
+ const doc = loadSquare(context.squarePath);
316
+ return withPathOutput(context.squarePath, doc.warmup.join('\n'), {
317
+ participantCount: inSquareCount(doc),
318
+ });
319
+ },
320
+ present: (result) => process.stdout.write(result),
321
+ };
322
+ export const participantsCommand = {
323
+ parse(argv, context) { if (argv.length > 0)
324
+ usage(context.command); return undefined; },
325
+ execute(_intent, context) {
326
+ const doc = loadSquare(context.squarePath);
327
+ const now = nowMs();
328
+ const participants = coreParticipants(doc, now);
329
+ const lines = participants.map((participant) => {
330
+ const glyph = participant.state === 'done' ? '×' : participant.presence === 'watching' ? '◎' : participant.activityCount > 0 ? '●' : '○';
331
+ const state = participant.state === 'done' ? 'done' : participant.presence === 'watching' ? 'catching' : participant.state;
332
+ const last = participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, now);
333
+ return ` ${glyph} ${participant.name} · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`;
334
+ });
335
+ const participantCount = participants.filter((participant) => participant.state === 'active').length;
336
+ return withPathOutput(context.squarePath, ['participants', ...lines].join('\n'), {
337
+ participantCount,
338
+ });
339
+ },
340
+ present: (result) => process.stdout.write(result),
341
+ };
342
+ export const statusCommand = {
343
+ parse(argv, context) { if (argv.length > 0)
344
+ usage(context.command); return undefined; },
345
+ execute(_intent, context) {
346
+ const doc = loadSquare(context.squarePath);
347
+ const result = coreStatus(doc, nowMs());
348
+ const active = result.participants.filter((participant) => participant.state === 'active').sort((a, b) => {
349
+ const aViewer = context.name !== undefined && sameName(a.name, context.name);
350
+ const bViewer = context.name !== undefined && sameName(b.name, context.name);
351
+ if (aViewer !== bViewer)
352
+ return aViewer ? -1 : 1;
353
+ return (b.lastActiveAt ?? -Infinity) - (a.lastActiveAt ?? -Infinity) || a.name.localeCompare(b.name);
354
+ });
355
+ const people = active.length === 0 ? [' ○ nobody in the square'] : active.map((participant) => {
356
+ const glyph = participant.presence === 'watching'
357
+ ? '◎'
358
+ : participant.activityCount > 0 ? '●' : '○';
359
+ const summary = participant.activityCount > 0
360
+ ? `${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${participant.lastActiveAt === undefined
361
+ ? 'just now'
362
+ : formatRelativeTime(participant.lastActiveAt, result.now)}`
363
+ : `quiet · ${participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, result.now)}`;
364
+ const showAttention = context.name === undefined || sameName(participant.name, context.name);
365
+ const attention = !showAttention
366
+ ? ''
367
+ : participant.pendingMentionCount > 0
368
+ ? `${participant.pendingMentionCount} mention${participant.pendingMentionCount === 1 ? '' : 's'} waiting`
369
+ : participant.unreadActivityCount > 0
370
+ ? `${participant.unreadActivityCount} change${participant.unreadActivityCount === 1 ? '' : 's'} waiting`
371
+ : 'caught up';
372
+ return ` ${glyph} ${participant.name} · ${summary}${attention === '' ? '' : ` · ${attention}`}`;
373
+ });
374
+ const cap = result.hardCap === null ? 'unlimited' : String(result.hardCap);
375
+ const hold = result.holdActive
376
+ ? `· ${result.holdActor ?? 'someone'} raised a hand${result.holdReason ? ` — ${result.holdReason}` : ''} · ${result.holdAt === undefined
377
+ ? 'just now'
378
+ : formatRelativeTime(result.holdAt, result.now)}`
379
+ : undefined;
380
+ const visible = result.latestAct === undefined
381
+ ? ''
382
+ : renderVisibleEvent(doc.acts, result.latestAct, context.name ?? '', {
383
+ now: result.now,
384
+ preview: 200,
385
+ actNumber: result.latestAct.kind === 'say'
386
+ ? sayNumberFor(doc.acts, result.latestAct)
387
+ : undefined,
388
+ });
389
+ const latest = visible === ''
390
+ ? [result.latestAct === undefined
391
+ ? ' ○ no public activity yet'
392
+ : ' · latest activity is private to another participant']
393
+ : [` ${visible.replace(/\n/g, '\n ')}`];
394
+ if (visible.includes('more chars') && result.latestAct !== undefined) {
395
+ const prefix = context.name === undefined ? commandPrefix(context.squarePath) : participantCommandPrefix(context.squarePath, context.name);
396
+ latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --full`);
397
+ }
398
+ const output = [
399
+ `${result.activeCount} active · ${result.doneCount} done · cap ${cap} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`,
400
+ ...(hold === undefined ? [] : ['', hold]), '', 'around the square', ...people, '', 'latest', ...latest,
401
+ ].join('\n');
402
+ return withPathOutput(context.squarePath, output, { participantCount: result.activeCount, held: result.holdActive });
403
+ },
404
+ present: (result) => process.stdout.write(result),
405
+ };
406
+ export const inboxCommand = {
407
+ parse(argv, context) {
408
+ let sessionId;
409
+ let json = false;
410
+ for (let index = 0; index < argv.length; index++) {
411
+ if (argv[index] === '--for-session') {
412
+ sessionId = requireValue(argv, index, argv[index]);
413
+ index += 1;
414
+ }
415
+ else if (argv[index] === '--json')
416
+ json = true;
417
+ else
418
+ usage(context.command);
419
+ }
420
+ if (!sessionId)
421
+ fail('inbox requires --for-session <session-id>.');
422
+ return { sessionId, json };
423
+ },
424
+ execute(intent) {
425
+ const inbox = sessionInbox(intent.sessionId);
426
+ return intent.json ? `${JSON.stringify(inbox)}\n` : inbox.map((membership) => `${membership.name}\t${membership.squarePath}\t${membership.notifications.length}\n`).join('');
427
+ },
428
+ present: (result) => process.stdout.write(result),
429
+ };
430
+ function hookCommand(runHook) {
431
+ return {
432
+ parse(argv, context) { if (argv.length > 0)
433
+ usage(context.command); return undefined; },
434
+ execute: () => runHook(readStdinSync()),
435
+ present: (result) => process.stdout.write(result),
436
+ };
437
+ }
438
+ export const claudeHookCommand = hookCommand(runClaudeHook);
439
+ export const codexHookCommand = hookCommand(runCodexHook);
440
+ /** Maintain the local discovery cache before participant-facing adapters run. */
441
+ export function refreshLocalRegistration(squarePath, name) {
442
+ if (name === undefined)
443
+ return;
444
+ try {
445
+ const doc = loadSquare(squarePath);
446
+ const known = resolveRosterName(doc, name);
447
+ if (known !== undefined && isCurrentlyJoined(doc.acts, known))
448
+ recordLocalJoin(known, squarePath);
449
+ }
450
+ catch {
451
+ // The machine-local discovery cache never makes a Square command fail.
452
+ }
453
+ }
@@ -0,0 +1,48 @@
1
+ import { helpRequest } from '../help.js';
2
+ import { SquareError } from '../model.js';
3
+ import { defaultContext, parseGlobalArgs } from './context.js';
4
+ import { refreshLocalRegistration } from './observation-commands.js';
5
+ import { executeRegisteredCommand, findCommand } from './registry.js';
6
+ function isMutatingCommand(command, argv) {
7
+ if (['build', 'join', 'catch', 'express', 'done', 'hold', 'resume', 'compact'].includes(command))
8
+ return true;
9
+ return command === 'doctor' && argv.includes('--fix');
10
+ }
11
+ function handleSquareError(error) {
12
+ if (error instanceof SquareError) {
13
+ process.stderr.write(`${error.message}\n`);
14
+ process.exit(error.code === 'not_found' ? 1 : 2);
15
+ }
16
+ throw error;
17
+ }
18
+ /** Parse global flags, select an executable adapter, and leave all command work to the registry. */
19
+ export async function runCli(rawArgs = process.argv.slice(2)) {
20
+ try {
21
+ const requestedHelp = helpRequest(rawArgs);
22
+ if (requestedHelp !== undefined) {
23
+ await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help', '.square/SQUARE.md'));
24
+ return;
25
+ }
26
+ const parsed = parseGlobalArgs(rawArgs);
27
+ if (parsed.args.length === 0 || parsed.args[0] === '--help' || parsed.args[0] === '-h') {
28
+ await executeRegisteredCommand('help', [], defaultContext('help', parsed.squarePath, parsed.name));
29
+ return;
30
+ }
31
+ const command = parsed.args[0];
32
+ if (findCommand(command) === undefined) {
33
+ process.stderr.write(`unknown command: ${command}\nrun 'square' for usage\n`);
34
+ process.exit(2);
35
+ }
36
+ if (!parsed.explicitSquarePath && parsed.multipleSquares && isMutatingCommand(command, parsed.args.slice(1))) {
37
+ process.stderr.write('✕ more than one square is active here; choose the path before changing or consuming activity.\n» square list\n');
38
+ process.exit(2);
39
+ }
40
+ if (['express', 'catch', 'done', 'hold', 'resume'].includes(command)) {
41
+ refreshLocalRegistration(parsed.squarePath, parsed.name);
42
+ }
43
+ await executeRegisteredCommand(command, parsed.args.slice(1), defaultContext(command, parsed.squarePath, parsed.name));
44
+ }
45
+ catch (error) {
46
+ handleSquareError(error);
47
+ }
48
+ }
@@ -0,0 +1,40 @@
1
+ import { buildCommand, compactCommand, doneCommand, expressCommand, holdCommand, joinCommand, resumeCommand } from './square-commands.js';
2
+ import { doctorCommand } from './maintenance-commands.js';
3
+ import { harnessCommand } from './harness-command.js';
4
+ import { helpCommand, versionCommand } from './meta-commands.js';
5
+ import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, warmupCommand, } from './observation-commands.js';
6
+ /** Every public command is an executable adapter, including aliases and utility commands. */
7
+ export const commandRegistry = [
8
+ { names: ['build'], spec: buildCommand },
9
+ { names: ['list', 'ls'], spec: listCommand },
10
+ { names: ['join'], spec: joinCommand },
11
+ { names: ['stream'], spec: streamCommand },
12
+ { names: ['inbox'], spec: inboxCommand },
13
+ { names: ['claude-hook'], spec: claudeHookCommand },
14
+ { names: ['codex-hook'], spec: codexHookCommand },
15
+ { names: ['catch'], spec: catchCommand },
16
+ { names: ['express'], spec: expressCommand },
17
+ { names: ['done'], spec: doneCommand },
18
+ { names: ['hold'], spec: holdCommand },
19
+ { names: ['resume'], spec: resumeCommand },
20
+ { names: ['harness'], spec: harnessCommand },
21
+ { names: ['compact'], spec: compactCommand },
22
+ { names: ['doctor'], spec: doctorCommand },
23
+ { names: ['history'], spec: historyCommand },
24
+ { names: ['warmup'], spec: warmupCommand },
25
+ { names: ['status'], spec: statusCommand },
26
+ { names: ['participants'], spec: participantsCommand },
27
+ { names: ['help'], spec: helpCommand },
28
+ { names: ['version', '--version', '-v'], spec: versionCommand },
29
+ ];
30
+ export function findCommand(name) {
31
+ return commandRegistry.find((command) => command.names.includes(name));
32
+ }
33
+ export async function executeRegisteredCommand(name, argv, context) {
34
+ const command = findCommand(name);
35
+ if (command === undefined)
36
+ throw new Error(`No registered command named ${name}`);
37
+ const intent = command.spec.parse(argv, context);
38
+ const result = await command.spec.execute(intent, context);
39
+ command.spec.present(result, context);
40
+ }