@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.
- package/codex-plugin/.codex-plugin/plugin.json +3 -2
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +23 -22
- package/dist/artifact.js +126 -202
- package/dist/claude-hook.js +45 -21
- package/dist/cli/context.js +143 -0
- package/dist/cli/harness-command.js +50 -0
- package/dist/cli/maintenance-commands.js +76 -0
- package/dist/cli/meta-commands.js +28 -0
- package/dist/cli/observation-commands.js +453 -0
- package/dist/cli/program.js +48 -0
- package/dist/cli/registry.js +40 -0
- package/dist/cli/square-commands.js +219 -0
- package/dist/cmd/notify-once.js +23 -21
- package/dist/compact.js +6 -19
- package/dist/decisions.js +53 -86
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +68 -0
- package/dist/harness-codex.js +119 -0
- package/dist/harness-links.js +123 -0
- package/dist/harness-stage.js +36 -0
- package/dist/harness.js +94 -576
- package/dist/help.js +44 -35
- package/dist/inbox.js +12 -11
- package/dist/index.js +30 -129
- package/dist/list.js +1 -1
- package/dist/model.js +0 -6
- package/dist/notification-failures.js +54 -0
- package/dist/notifications.js +47 -62
- package/dist/paseo-timeline.js +58 -188
- package/dist/presentation.js +55 -63
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +26 -137
- package/dist/square-application.js +264 -0
- package/dist/square-core.js +3 -11
- package/dist/square.js +5 -1362
- package/dist/stream.js +27 -126
- package/dist/wake-sink.js +134 -188
- package/dist/watch.js +79 -138
- package/extensions/square-opencode.js +1 -1
- package/extensions/square-pi.js +8 -130
- package/guides/architect.md +3 -3
- package/guides/participant.md +25 -16
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +25 -32
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +39 -107
- package/skills/square-feedback/SKILL.md +4 -4
- 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
|
+
}
|