@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
package/dist/square.js
CHANGED
|
@@ -1,1366 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import fs from 'node:fs';
|
|
5
|
-
import os from 'node:os';
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
-
import { SquareError, formatHardCap, parseParticipantList, sameName, validateParticipantName, } from './model.js';
|
|
9
|
-
import { commandPrefix, participantCommandPrefix, quoteShell, renderActivitiesView, renderGrepActivitiesView, renderDoctorClean, renderDoctorProblems, renderDoctorRepaired, renderDoctorUnfixable, renderEventCli, renderVisibleEvent, withJoinNextOutput, renderPublicTail, withPathOutput, } from './presentation.js';
|
|
10
|
-
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from './time.js';
|
|
11
|
-
import { cmdListSquares } from './list.js';
|
|
12
|
-
import { cmdActivity } from './activity.js';
|
|
13
|
-
import { cmdCompact } from './compact.js';
|
|
14
|
-
import { cmdWatch } from './watch.js';
|
|
15
|
-
import { cmdStream, cmdStreamNdjson } from './stream.js';
|
|
16
|
-
import { diagnoseSquare, loadSquare, renderSquare, saveRuntimeSidecar, emptyRuntimeState } from './artifact.js';
|
|
17
|
-
import { doctorDeliveryHealth, findStalePendingMentions, reconcileDeliveryBacklog, } from './delivery-health.js';
|
|
18
|
-
import { dispatchActNotifications } from './notifications.js';
|
|
19
|
-
import { hasAutomaticDeliveryIdentity, recordLocalJoin, recordLocalDone } from './registry.js';
|
|
20
|
-
import { sessionInbox } from './inbox.js';
|
|
21
|
-
import { runClaudeHook, runCodexHook } from './claude-hook.js';
|
|
22
|
-
import { doctorCodexPlugin, installClaudePlugin, installCodexPlugin, uninstallCodexPlugin, } from './harness.js';
|
|
23
|
-
import { actId, appendAct, inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName, sayNumberFor, withSquareLock, writeSquareDoc } from './runtime.js';
|
|
24
|
-
import { planRepair } from './doctor.js';
|
|
25
|
-
import { commandUsageHint, helpRequest as parseHelpRequest, renderGlobalHelp, renderSubcommandHelp as renderScopedHelp, } from './help.js';
|
|
26
|
-
import { decideJoin, coreDone, coreHold, coreResume, coreStatus, coreParticipants, coreActivities, } from './decisions.js';
|
|
27
|
-
const DEFAULT_SQUARE_PATH = '.square/SQUARE.md';
|
|
28
|
-
function readStdinSync() {
|
|
29
|
-
try {
|
|
30
|
-
return fs.readFileSync(0, 'utf8');
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return '';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function resolveBody(arg) {
|
|
37
|
-
return arg === '-' ? readStdinSync() : arg;
|
|
38
|
-
}
|
|
39
|
-
// When no body argument is given and stdin is piped (not a TTY), read it to EOF
|
|
40
|
-
// and use it as the body if non-empty. Returns undefined if stdin is a TTY or
|
|
41
|
-
// the piped content is blank, so callers fall back to their "missing body" path.
|
|
42
|
-
function readPipedBodyFallback() {
|
|
43
|
-
if (process.stdin.isTTY)
|
|
44
|
-
return undefined;
|
|
45
|
-
const content = readStdinSync();
|
|
46
|
-
return content.trim() === '' ? undefined : content;
|
|
47
|
-
}
|
|
48
|
-
function requireValue(args, index, flag) {
|
|
49
|
-
const value = args[index + 1];
|
|
50
|
-
if (value === undefined || value.startsWith('--')) {
|
|
51
|
-
process.stderr.write(`Missing value for ${flag}.\n`);
|
|
52
|
-
process.exit(2);
|
|
53
|
-
}
|
|
54
|
-
return value;
|
|
55
|
-
}
|
|
56
|
-
function parseDurationMs(value, flag) {
|
|
57
|
-
const match = value.match(/^([1-9]\d*)(ms|s|m|h)$/);
|
|
58
|
-
if (!match) {
|
|
59
|
-
process.stderr.write(`Invalid ${flag}: expected a positive duration with unit ms, s, m, or h (for example 500ms, 30s, 3m, 1h).\n`);
|
|
60
|
-
process.exit(2);
|
|
61
|
-
}
|
|
62
|
-
const amount = parseInt(match[1], 10);
|
|
63
|
-
const unit = match[2];
|
|
64
|
-
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000 };
|
|
65
|
-
const ms = amount * multipliers[unit];
|
|
66
|
-
if (!Number.isSafeInteger(ms)) {
|
|
67
|
-
process.stderr.write(`Invalid ${flag}: duration is too large.\n`);
|
|
68
|
-
process.exit(2);
|
|
69
|
-
}
|
|
70
|
-
return ms;
|
|
71
|
-
}
|
|
72
|
-
function parsePositiveIntegerOption(value, flag) {
|
|
73
|
-
if (!/^[1-9]\d*$/.test(value)) {
|
|
74
|
-
process.stderr.write(`Invalid ${flag}: expected a positive integer.\n`);
|
|
75
|
-
process.exit(2);
|
|
76
|
-
}
|
|
77
|
-
const parsed = Number(value);
|
|
78
|
-
if (!Number.isSafeInteger(parsed)) {
|
|
79
|
-
process.stderr.write(`Invalid ${flag}: value is too large.\n`);
|
|
80
|
-
process.exit(2);
|
|
81
|
-
}
|
|
82
|
-
return parsed;
|
|
83
|
-
}
|
|
84
|
-
function parseNonNegativeIntegerOption(value, flag) {
|
|
85
|
-
if (!/^\d+$/.test(value)) {
|
|
86
|
-
process.stderr.write(`Invalid ${flag}: expected a non-negative integer.\n`);
|
|
87
|
-
process.exit(2);
|
|
88
|
-
}
|
|
89
|
-
const parsed = Number(value);
|
|
90
|
-
if (!Number.isSafeInteger(parsed)) {
|
|
91
|
-
process.stderr.write(`Invalid ${flag}: value is too large.\n`);
|
|
92
|
-
process.exit(2);
|
|
93
|
-
}
|
|
94
|
-
return parsed;
|
|
95
|
-
}
|
|
96
|
-
function parseHardCap(value) {
|
|
97
|
-
if (value === '-1')
|
|
98
|
-
return null;
|
|
99
|
-
const hardCap = parseInt(value, 10);
|
|
100
|
-
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(hardCap)) {
|
|
101
|
-
process.stderr.write('Invalid build option: --cap must be a positive integer or -1.\n');
|
|
102
|
-
process.exit(2);
|
|
103
|
-
}
|
|
104
|
-
return hardCap;
|
|
105
|
-
}
|
|
106
|
-
function parseNameListOption(value, flag) {
|
|
107
|
-
const names = parseParticipantList(value);
|
|
108
|
-
if (names.length === 0) {
|
|
109
|
-
process.stderr.write(`Invalid ${flag}: expected at least one participant name.\n`);
|
|
110
|
-
process.exit(2);
|
|
111
|
-
}
|
|
112
|
-
for (const name of names)
|
|
113
|
-
validateParticipantName(name);
|
|
114
|
-
return names;
|
|
115
|
-
}
|
|
116
|
-
function requireNameFlag(name) {
|
|
117
|
-
if (!name) {
|
|
118
|
-
process.stderr.write('Missing required option: --as <name>.\n');
|
|
119
|
-
process.exit(2);
|
|
120
|
-
}
|
|
121
|
-
validateParticipantName(name);
|
|
122
|
-
return name;
|
|
123
|
-
}
|
|
124
|
-
function resolveDefaultSquarePath() {
|
|
125
|
-
const dir = path.join(process.cwd(), '.square');
|
|
126
|
-
let entries;
|
|
127
|
-
try {
|
|
128
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
129
|
-
}
|
|
130
|
-
catch {
|
|
131
|
-
return { path: DEFAULT_SQUARE_PATH, multiple: false };
|
|
132
|
-
}
|
|
133
|
-
const candidates = [];
|
|
134
|
-
for (const entry of entries) {
|
|
135
|
-
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
136
|
-
continue;
|
|
137
|
-
const fullPath = path.join(dir, entry.name);
|
|
138
|
-
let at;
|
|
139
|
-
try {
|
|
140
|
-
const doc = loadSquare(fullPath);
|
|
141
|
-
const latestAct = doc.acts.at(-1);
|
|
142
|
-
at = latestAct?.at ?? fs.statSync(fullPath).mtimeMs;
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
candidates.push({ relPath: path.relative(process.cwd(), fullPath), at });
|
|
148
|
-
}
|
|
149
|
-
candidates.sort((a, b) => b.at - a.at);
|
|
150
|
-
return { path: candidates[0]?.relPath ?? DEFAULT_SQUARE_PATH, multiple: candidates.length > 1 };
|
|
151
|
-
}
|
|
152
|
-
function parseGlobalArgs(rawArgs) {
|
|
153
|
-
const args = [...rawArgs];
|
|
154
|
-
let squarePath;
|
|
155
|
-
let name;
|
|
156
|
-
for (let i = 0; i < args.length; i++) {
|
|
157
|
-
if (args[i] === '--square-path') {
|
|
158
|
-
squarePath = requireValue(args, i, args[i]);
|
|
159
|
-
args.splice(i, 2);
|
|
160
|
-
i -= 1;
|
|
161
|
-
}
|
|
162
|
-
else if (args[i] === '--as') {
|
|
163
|
-
name = requireValue(args, i, args[i]);
|
|
164
|
-
args.splice(i, 2);
|
|
165
|
-
i -= 1;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (name !== undefined)
|
|
169
|
-
validateParticipantName(name);
|
|
170
|
-
const explicitSquarePath = squarePath !== undefined;
|
|
171
|
-
const command = args[0];
|
|
172
|
-
const needsDefaultSquare = !explicitSquarePath && command !== 'ls' && command !== 'list' && command !== 'version';
|
|
173
|
-
const resolved = needsDefaultSquare
|
|
174
|
-
? resolveDefaultSquarePath()
|
|
175
|
-
: { path: squarePath ?? DEFAULT_SQUARE_PATH, multiple: false };
|
|
176
|
-
return {
|
|
177
|
-
squarePath: resolved.path,
|
|
178
|
-
explicitSquarePath,
|
|
179
|
-
multipleSquares: resolved.multiple,
|
|
180
|
-
name,
|
|
181
|
-
args,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
function parseBuildOptions(args) {
|
|
185
|
-
const opts = {
|
|
186
|
-
force: false,
|
|
187
|
-
};
|
|
188
|
-
for (let i = 0; i < args.length; i++) {
|
|
189
|
-
const flag = args[i];
|
|
190
|
-
switch (flag) {
|
|
191
|
-
case '--cap':
|
|
192
|
-
opts.hardCap = parseHardCap(requireValue(args, i, flag));
|
|
193
|
-
i++;
|
|
194
|
-
break;
|
|
195
|
-
case '--template':
|
|
196
|
-
opts.template = requireValue(args, i, flag);
|
|
197
|
-
i++;
|
|
198
|
-
break;
|
|
199
|
-
case '--throttle':
|
|
200
|
-
case '--throttle-per-minute':
|
|
201
|
-
opts.throttlePerMinute = parsePositiveIntegerOption(requireValue(args, i, flag), flag);
|
|
202
|
-
i++;
|
|
203
|
-
break;
|
|
204
|
-
case '--force':
|
|
205
|
-
case '-f':
|
|
206
|
-
opts.force = true;
|
|
207
|
-
break;
|
|
208
|
-
default:
|
|
209
|
-
process.stderr.write(`Unknown build option: ${flag}\n`);
|
|
210
|
-
process.exit(2);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (opts.template && !/^[a-zA-Z0-9-]+$/.test(opts.template)) {
|
|
214
|
-
process.stderr.write('Invalid template name: only letters, digits, and hyphens allowed.\n');
|
|
215
|
-
process.exit(2);
|
|
216
|
-
}
|
|
217
|
-
if (opts.hardCap === undefined) {
|
|
218
|
-
process.stderr.write('Missing required build option: --cap must be a positive integer or -1.\n');
|
|
219
|
-
process.exit(2);
|
|
220
|
-
}
|
|
221
|
-
if (opts.throttlePerMinute !== undefined &&
|
|
222
|
-
(!Number.isInteger(opts.throttlePerMinute) || opts.throttlePerMinute <= 0)) {
|
|
223
|
-
process.stderr.write('Invalid build option: --throttle must be a positive integer.\n');
|
|
224
|
-
process.exit(2);
|
|
225
|
-
}
|
|
226
|
-
return opts;
|
|
227
|
-
}
|
|
228
|
-
async function cmdBuild(squarePath, args) {
|
|
229
|
-
const opts = parseBuildOptions(args);
|
|
230
|
-
const snippet = readStdinSync();
|
|
231
|
-
if (snippet.trim() === '') {
|
|
232
|
-
process.stderr.write('Missing Markdown body snippet on stdin.\n');
|
|
233
|
-
process.exit(2);
|
|
234
|
-
}
|
|
235
|
-
try {
|
|
236
|
-
fs.mkdirSync(path.dirname(squarePath), { recursive: true });
|
|
237
|
-
await withSquareLock(squarePath, () => {
|
|
238
|
-
if (fs.existsSync(squarePath) && !opts.force) {
|
|
239
|
-
throw new SquareError('conflict', `Refusing to overwrite existing square: ${squarePath}\nPass -f to overwrite.`);
|
|
240
|
-
}
|
|
241
|
-
fs.writeFileSync(squarePath, renderSquare(opts, snippet));
|
|
242
|
-
saveRuntimeSidecar(squarePath, emptyRuntimeState(0));
|
|
243
|
-
});
|
|
244
|
-
const capText = formatHardCap(opts.hardCap) === '-1' ? 'unlimited' : formatHardCap(opts.hardCap);
|
|
245
|
-
const throttleText = opts.throttlePerMinute !== undefined ? `throttle ${opts.throttlePerMinute}/min` : '';
|
|
246
|
-
const summary = ['✓ built', ` · cap ${capText}`, ...(throttleText ? [` · ${throttleText.trim()}`] : []), ' · participants (none seeded — first join adds names)'].join('\n');
|
|
247
|
-
process.stdout.write(withPathOutput(squarePath, summary, { participantCount: 0 }));
|
|
248
|
-
}
|
|
249
|
-
catch (err) {
|
|
250
|
-
if (err instanceof SquareError) {
|
|
251
|
-
process.stderr.write(err.message + '\n');
|
|
252
|
-
process.exit(2);
|
|
253
|
-
}
|
|
254
|
-
throw err;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
function parseJoinOptions(args) {
|
|
258
|
-
let lastN = 10;
|
|
259
|
-
for (let i = 0; i < args.length; i++) {
|
|
260
|
-
if (args[i] === '--last') {
|
|
261
|
-
lastN = parsePositiveIntegerOption(requireValue(args, i, args[i]), args[i]);
|
|
262
|
-
i++;
|
|
263
|
-
}
|
|
264
|
-
else if (args[i] === '--all') {
|
|
265
|
-
lastN = null;
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
usage();
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
return { lastN };
|
|
272
|
-
}
|
|
273
|
-
async function cmdJoin(squarePath, name, opts = { lastN: 10 }) {
|
|
274
|
-
validateParticipantName(name);
|
|
275
|
-
try {
|
|
276
|
-
let joinedName;
|
|
277
|
-
let context;
|
|
278
|
-
let allActs;
|
|
279
|
-
let isRejoin;
|
|
280
|
-
let sent;
|
|
281
|
-
await withSquareLock(squarePath, () => {
|
|
282
|
-
const doc = loadSquare(squarePath);
|
|
283
|
-
const decision = decideJoin(doc, name, nowMs());
|
|
284
|
-
joinedName = decision.joinedName;
|
|
285
|
-
isRejoin = doc.acts.some((e) => e.kind === 'join' && e.actor === decision.joinedName);
|
|
286
|
-
const appended = appendAct(squarePath, doc, decision.joinAct);
|
|
287
|
-
sent = { act: appended, index: appended.index };
|
|
288
|
-
context = doc.preamble.at(-1) === '---' ? doc.preamble.slice(0, -1) : doc.preamble;
|
|
289
|
-
allActs = doc.acts;
|
|
290
|
-
});
|
|
291
|
-
if (sent)
|
|
292
|
-
await dispatchActNotifications(squarePath, sent);
|
|
293
|
-
recordLocalJoin(joinedName, squarePath);
|
|
294
|
-
const now = nowMs();
|
|
295
|
-
const activities = renderPublicTail(allActs, opts.lastN, now, joinedName);
|
|
296
|
-
const contextText = context.join('\n').trim();
|
|
297
|
-
const fallbackCatch = hasAutomaticDeliveryIdentity()
|
|
298
|
-
? []
|
|
299
|
-
: [
|
|
300
|
-
'',
|
|
301
|
-
`» ${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m`,
|
|
302
|
-
' no session delivery detected — keep this catch open for new activity',
|
|
303
|
-
];
|
|
304
|
-
const out = [
|
|
305
|
-
`● ${joinedName} stepped into the square`,
|
|
306
|
-
...(isRejoin || contextText === '' ? [] : ['', 'context', contextText]),
|
|
307
|
-
...(activities === '' ? [] : ['', 'recent activity', activities]),
|
|
308
|
-
...(isRejoin ? [] : ['', `» ${participantCommandPrefix(squarePath, joinedName)} warmup`]),
|
|
309
|
-
...fallbackCatch,
|
|
310
|
-
].join('\n');
|
|
311
|
-
process.stdout.write(withJoinNextOutput(squarePath, out, { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
312
|
-
}
|
|
313
|
-
catch (err) {
|
|
314
|
-
if (err instanceof SquareError && err.code === 'conflict') {
|
|
315
|
-
const doc = loadSquare(squarePath);
|
|
316
|
-
const joinedName = resolveRosterName(doc, name);
|
|
317
|
-
if (joinedName === undefined || !isCurrentlyJoined(doc.acts, joinedName))
|
|
318
|
-
throw err;
|
|
319
|
-
recordLocalJoin(joinedName, squarePath);
|
|
320
|
-
const fallbackCatch = hasAutomaticDeliveryIdentity()
|
|
321
|
-
? ''
|
|
322
|
-
: `\n» ${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m\n no session delivery detected — keep this catch open for new activity`;
|
|
323
|
-
process.stdout.write(withJoinNextOutput(squarePath, `● ${joinedName} is already in the square${fallbackCatch}`, {
|
|
324
|
-
participantCount: inSquareCount(doc),
|
|
325
|
-
}));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
handleSquareError(err);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
function parseActOptions(args) {
|
|
332
|
-
let force = false;
|
|
333
|
-
let noWait = false;
|
|
334
|
-
let beside;
|
|
335
|
-
let bell = false;
|
|
336
|
-
const activityArgs = [];
|
|
337
|
-
for (let i = 0; i < args.length; i++) {
|
|
338
|
-
const arg = args[i];
|
|
339
|
-
if (arg === '-f' || arg === '--force') {
|
|
340
|
-
force = true;
|
|
341
|
-
}
|
|
342
|
-
else if (arg === '--no-wait') {
|
|
343
|
-
noWait = true;
|
|
344
|
-
}
|
|
345
|
-
else if (arg === '--beside') {
|
|
346
|
-
beside = requireValue(args, i, arg);
|
|
347
|
-
i++;
|
|
348
|
-
}
|
|
349
|
-
else if (arg === '--bell') {
|
|
350
|
-
bell = true;
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
activityArgs.push(arg);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
if (bell && beside !== undefined) {
|
|
357
|
-
process.stderr.write('Invalid act options: --beside and --bell are mutually exclusive.\n');
|
|
358
|
-
process.exit(2);
|
|
359
|
-
}
|
|
360
|
-
const reach = bell ? 'bell' : beside !== undefined ? { beside } : undefined;
|
|
361
|
-
if (activityArgs.length !== 1) {
|
|
362
|
-
if (activityArgs.length === 0) {
|
|
363
|
-
const piped = readPipedBodyFallback();
|
|
364
|
-
if (piped !== undefined) {
|
|
365
|
-
return { activity: piped, force, noWait, reach };
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
process.stderr.write("act requires a body argument (a quoted string or '-' with piped stdin)\n");
|
|
369
|
-
process.exit(2);
|
|
370
|
-
}
|
|
371
|
-
return { activity: activityArgs[0], force, noWait, reach };
|
|
372
|
-
}
|
|
373
|
-
function forceActCommand(squarePath, name, reach) {
|
|
374
|
-
const reachArg = reach === 'bell' ? ' --bell' : reach !== undefined ? ` --beside ${quoteShell(reach.beside)}` : '';
|
|
375
|
-
return `${participantCommandPrefix(squarePath, name)} act --force${reachArg} -`;
|
|
376
|
-
}
|
|
377
|
-
function parseDoneOptions(args) {
|
|
378
|
-
if (args.length > 1)
|
|
379
|
-
usage();
|
|
380
|
-
if (args.length === 1)
|
|
381
|
-
return args[0];
|
|
382
|
-
return readPipedBodyFallback();
|
|
383
|
-
}
|
|
384
|
-
async function cmdDone(squarePath, name, final) {
|
|
385
|
-
const body = String(resolveBody(final ?? '') ?? '').replace(/\r\n/g, '\n').trim();
|
|
386
|
-
try {
|
|
387
|
-
let resolvedName;
|
|
388
|
-
let sent;
|
|
389
|
-
await withSquareLock(squarePath, () => {
|
|
390
|
-
const doc = loadSquare(squarePath);
|
|
391
|
-
const act = coreDone(doc, name, body, nowMs());
|
|
392
|
-
resolvedName = act.actor;
|
|
393
|
-
const appended = appendAct(squarePath, doc, act);
|
|
394
|
-
sent = { act: appended, index: appended.index };
|
|
395
|
-
});
|
|
396
|
-
if (sent)
|
|
397
|
-
await dispatchActNotifications(squarePath, sent);
|
|
398
|
-
recordLocalDone(resolvedName, squarePath);
|
|
399
|
-
const doc = loadSquare(squarePath);
|
|
400
|
-
process.stdout.write(withPathOutput(squarePath, `× ${resolvedName} steps out of the square — done · just now`, { participantCount: inSquareCount(doc) }));
|
|
401
|
-
}
|
|
402
|
-
catch (err) {
|
|
403
|
-
handleSquareError(err);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
async function cmdHold(squarePath, actor, reason) {
|
|
407
|
-
const body = String(resolveBody(reason ?? '') ?? '').replace(/\r\n/g, '\n').trim();
|
|
408
|
-
try {
|
|
409
|
-
let confirmation;
|
|
410
|
-
let sent;
|
|
411
|
-
await withSquareLock(squarePath, () => {
|
|
412
|
-
const doc = loadSquare(squarePath);
|
|
413
|
-
const appended = appendAct(squarePath, doc, coreHold(doc, actor, body, nowMs()));
|
|
414
|
-
sent = { act: appended, index: appended.index };
|
|
415
|
-
confirmation = renderEventCli(appended);
|
|
416
|
-
});
|
|
417
|
-
if (sent)
|
|
418
|
-
await dispatchActNotifications(squarePath, sent);
|
|
419
|
-
const doc = loadSquare(squarePath);
|
|
420
|
-
process.stdout.write(withPathOutput(squarePath, confirmation, { participantCount: inSquareCount(doc), held: true }));
|
|
421
|
-
}
|
|
422
|
-
catch (err) {
|
|
423
|
-
handleSquareError(err);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
async function cmdResume(squarePath, actor) {
|
|
427
|
-
try {
|
|
428
|
-
let confirmation;
|
|
429
|
-
let sent;
|
|
430
|
-
await withSquareLock(squarePath, () => {
|
|
431
|
-
const doc = loadSquare(squarePath);
|
|
432
|
-
const appended = appendAct(squarePath, doc, coreResume(doc, actor, nowMs()));
|
|
433
|
-
sent = { act: appended, index: appended.index };
|
|
434
|
-
confirmation = renderEventCli(appended);
|
|
435
|
-
});
|
|
436
|
-
if (sent)
|
|
437
|
-
await dispatchActNotifications(squarePath, sent);
|
|
438
|
-
const doc = loadSquare(squarePath);
|
|
439
|
-
process.stdout.write(withPathOutput(squarePath, confirmation, { participantCount: inSquareCount(doc) }));
|
|
440
|
-
}
|
|
441
|
-
catch (err) {
|
|
442
|
-
handleSquareError(err);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function parseHarnessInstallOptions(args) {
|
|
446
|
-
let force = false;
|
|
447
|
-
for (let i = 0; i < args.length; i++) {
|
|
448
|
-
if (args[i] === '-f' || args[i] === '--force') {
|
|
449
|
-
force = true;
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
usage();
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return { force };
|
|
456
|
-
}
|
|
457
|
-
function lstatMaybe(target) {
|
|
458
|
-
try {
|
|
459
|
-
return fs.lstatSync(target);
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
if (error.code === 'ENOENT')
|
|
463
|
-
return null;
|
|
464
|
-
throw error;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const HARNESS_SKILL_NAMES = ['square', 'brainstorm'];
|
|
468
|
-
function skillLinksFor(root, homeDir = os.homedir()) {
|
|
469
|
-
return HARNESS_SKILL_NAMES.map((name) => ({
|
|
470
|
-
source: fileURLToPath(new URL(`../skills/${name}/`, import.meta.url)),
|
|
471
|
-
target: path.join(homeDir, root, 'skills', name),
|
|
472
|
-
kind: 'skill',
|
|
473
|
-
}));
|
|
474
|
-
}
|
|
475
|
-
function skillLinks(homeDir = os.homedir()) {
|
|
476
|
-
return [...skillLinksFor('.claude', homeDir), ...skillLinksFor('.agents', homeDir)];
|
|
477
|
-
}
|
|
478
|
-
function piExtensionLink(homeDir = os.homedir()) {
|
|
479
|
-
return {
|
|
480
|
-
source: fileURLToPath(new URL('../extensions/square-pi.js', import.meta.url)),
|
|
481
|
-
target: path.join(homeDir, '.pi', 'agent', 'extensions', 'square.js'),
|
|
482
|
-
kind: 'extension',
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
function opencodeExtensionLink(homeDir = os.homedir()) {
|
|
486
|
-
const configHome = process.env['XDG_CONFIG_HOME']?.trim() || path.join(homeDir, '.config');
|
|
487
|
-
return {
|
|
488
|
-
source: fileURLToPath(new URL('../extensions/square-opencode.js', import.meta.url)),
|
|
489
|
-
target: path.join(configHome, 'opencode', 'plugins', 'square.js'),
|
|
490
|
-
kind: 'extension',
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
function doctorOpenCode(homeDir = os.homedir()) {
|
|
494
|
-
const extension = opencodeExtensionLink(homeDir);
|
|
495
|
-
let linked = false;
|
|
496
|
-
try {
|
|
497
|
-
linked = fs.realpathSync(extension.target) === fs.realpathSync(extension.source);
|
|
498
|
-
}
|
|
499
|
-
catch { }
|
|
500
|
-
const skill = path.join(homeDir, '.agents', 'skills', 'square', 'SKILL.md');
|
|
501
|
-
const lines = [
|
|
502
|
-
linked ? `✓ OpenCode Square plugin ${extension.target}` : `✕ OpenCode Square plugin missing ${extension.target}`,
|
|
503
|
-
fs.existsSync(skill) ? `✓ OpenCode Square skill ${skill}` : `✕ OpenCode Square skill missing ${skill}`,
|
|
504
|
-
];
|
|
505
|
-
if (!linked)
|
|
506
|
-
return lines;
|
|
507
|
-
const runtime = spawnSync(process.env['SQUARE_OPENCODE_BIN'] || 'opencode', ['debug', 'config'], {
|
|
508
|
-
encoding: 'utf8',
|
|
509
|
-
env: { ...process.env, HOME: homeDir },
|
|
510
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
511
|
-
timeout: 30_000,
|
|
512
|
-
});
|
|
513
|
-
try {
|
|
514
|
-
const config = JSON.parse(runtime.stdout || '');
|
|
515
|
-
const expected = pathToFileURL(extension.target).href;
|
|
516
|
-
lines.push(runtime.status === 0 && config.plugin?.includes(expected)
|
|
517
|
-
? '✓ OpenCode loaded the Square plugin'
|
|
518
|
-
: '✕ OpenCode did not load the Square plugin');
|
|
519
|
-
}
|
|
520
|
-
catch {
|
|
521
|
-
lines.push(`✕ OpenCode plugin runtime unavailable${runtime.stderr?.trim() ? ` (${runtime.stderr.trim()})` : ''}`);
|
|
522
|
-
}
|
|
523
|
-
return lines;
|
|
524
|
-
}
|
|
525
|
-
function installLinks(links, force) {
|
|
526
|
-
const existing = links.filter(({ target }) => lstatMaybe(target) !== null);
|
|
527
|
-
if (existing.length > 0 && !force) {
|
|
528
|
-
for (const { target, kind } of existing) {
|
|
529
|
-
process.stderr.write(`Refusing to overwrite existing ${kind} link: ${target}\n`);
|
|
530
|
-
}
|
|
531
|
-
process.stderr.write('Pass -f to replace it.\n');
|
|
532
|
-
process.exit(1);
|
|
533
|
-
}
|
|
534
|
-
for (const { source, target } of links) {
|
|
535
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
536
|
-
if (lstatMaybe(target) !== null)
|
|
537
|
-
fs.rmSync(target, { recursive: true, force: true });
|
|
538
|
-
const symlinkType = os.platform() === 'win32' && fs.statSync(source).isDirectory() ? 'junction' : 'file';
|
|
539
|
-
fs.symlinkSync(source, target, symlinkType);
|
|
540
|
-
}
|
|
541
|
-
return links.map(({ target }) => target);
|
|
542
|
-
}
|
|
543
|
-
async function cmdHarness(args, squarePath) {
|
|
544
|
-
const action = args[0];
|
|
545
|
-
if (action !== 'install' && action !== 'uninstall' && action !== 'doctor')
|
|
546
|
-
usage();
|
|
547
|
-
const rest = args.slice(1);
|
|
548
|
-
const target = rest.find((arg) => !arg.startsWith('-'));
|
|
549
|
-
const force = rest.includes('-f') || rest.includes('--force');
|
|
550
|
-
if (action === 'doctor') {
|
|
551
|
-
if (target && target !== 'codex' && target !== 'opencode' && target !== 'delivery')
|
|
552
|
-
usage();
|
|
553
|
-
const lines = [];
|
|
554
|
-
if (!target || target === 'codex')
|
|
555
|
-
lines.push(...(await doctorCodexPlugin(os.homedir())));
|
|
556
|
-
if (!target || target === 'opencode')
|
|
557
|
-
lines.push(...doctorOpenCode(os.homedir()));
|
|
558
|
-
if (!target || target === 'delivery') {
|
|
559
|
-
if (squarePath) {
|
|
560
|
-
try {
|
|
561
|
-
lines.push(...doctorDeliveryHealth(squarePath));
|
|
562
|
-
}
|
|
563
|
-
catch {
|
|
564
|
-
lines.push(`○ delivery health skipped (unreadable square at ${squarePath})`);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
lines.push('○ delivery health skipped (no square path)');
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
process.stdout.write(lines.join('\n') + '\n');
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
if (!target) {
|
|
575
|
-
process.stderr.write('harness install/uninstall requires an explicit target: skills | claude | codex | opencode | pi\n');
|
|
576
|
-
process.exit(2);
|
|
577
|
-
}
|
|
578
|
-
if (action === 'uninstall') {
|
|
579
|
-
if (target !== 'codex') {
|
|
580
|
-
process.stderr.write(`harness uninstall currently supports only codex (got ${target})\n`);
|
|
581
|
-
process.exit(2);
|
|
582
|
-
}
|
|
583
|
-
const result = uninstallCodexPlugin(os.homedir());
|
|
584
|
-
for (const note of result.notes)
|
|
585
|
-
process.stderr.write(`${note}\n`);
|
|
586
|
-
process.stdout.write(result.paths.join('\n') + '\n');
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
if (target === 'skills') {
|
|
590
|
-
process.stdout.write(installLinks(skillLinks(), force).join('\n') + '\n');
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
if (target === 'claude') {
|
|
594
|
-
const claude = installClaudePlugin(os.homedir());
|
|
595
|
-
process.stdout.write(`${claude.marketplaceRoot}\n${claude.pluginRoot}\n`);
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
if (target === 'pi') {
|
|
599
|
-
process.stdout.write(installLinks([piExtensionLink()], force).join('\n') + '\n');
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
if (target === 'opencode') {
|
|
603
|
-
process.stdout.write(installLinks([opencodeExtensionLink(), ...skillLinksFor('.agents')], force).join('\n') + '\n');
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
if (target === 'codex') {
|
|
607
|
-
const codex = await installCodexPlugin(os.homedir());
|
|
608
|
-
for (const note of codex.notes)
|
|
609
|
-
process.stderr.write(`${note}\n`);
|
|
610
|
-
process.stdout.write([codex.configPath, codex.marketplaceRoot, codex.pluginRoot, codex.installedPath]
|
|
611
|
-
.filter((item) => item !== undefined)
|
|
612
|
-
.join('\n') + '\n');
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
usage();
|
|
616
|
-
}
|
|
617
|
-
function handleSquareError(err) {
|
|
618
|
-
if (err instanceof SquareError) {
|
|
619
|
-
process.stderr.write(err.message + '\n');
|
|
620
|
-
process.exit(err.code === 'not_found' ? 1 : 2);
|
|
621
|
-
}
|
|
622
|
-
throw err;
|
|
623
|
-
}
|
|
624
|
-
function cmdWarmup(squarePath) {
|
|
625
|
-
try {
|
|
626
|
-
const doc = loadSquare(squarePath);
|
|
627
|
-
process.stdout.write(withPathOutput(squarePath, doc.warmup.join('\n'), { participantCount: inSquareCount(doc) }));
|
|
628
|
-
}
|
|
629
|
-
catch (err) {
|
|
630
|
-
handleSquareError(err);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
function cmdStatus(squarePath, viewer) {
|
|
634
|
-
try {
|
|
635
|
-
const doc = loadSquare(squarePath);
|
|
636
|
-
const result = coreStatus(doc, nowMs());
|
|
637
|
-
const { now } = result;
|
|
638
|
-
const activeParticipants = result.participants
|
|
639
|
-
.filter((participant) => participant.state === 'active')
|
|
640
|
-
.sort((a, b) => {
|
|
641
|
-
if (viewer !== undefined) {
|
|
642
|
-
const aIsViewer = sameName(a.name, viewer);
|
|
643
|
-
const bIsViewer = sameName(b.name, viewer);
|
|
644
|
-
if (aIsViewer !== bIsViewer)
|
|
645
|
-
return aIsViewer ? -1 : 1;
|
|
646
|
-
}
|
|
647
|
-
return (b.lastActiveAt ?? -Infinity) - (a.lastActiveAt ?? -Infinity) || a.name.localeCompare(b.name);
|
|
648
|
-
});
|
|
649
|
-
const participantLines = activeParticipants.length === 0
|
|
650
|
-
? [' ○ nobody in the square']
|
|
651
|
-
: activeParticipants.map((p) => {
|
|
652
|
-
const glyph = p.presence === 'watching' ? '◎' : p.activityCount > 0 ? '●' : '○';
|
|
653
|
-
const summary = p.activityCount > 0
|
|
654
|
-
? `${p.activityCount} act${p.activityCount === 1 ? '' : 's'} · ${p.lastActiveAt === undefined ? 'just now' : formatRelativeTime(p.lastActiveAt, now)}`
|
|
655
|
-
: `quiet · ${p.lastActiveAt === undefined ? '—' : formatRelativeTime(p.lastActiveAt, now)}`;
|
|
656
|
-
const isViewer = viewer !== undefined && sameName(p.name, viewer);
|
|
657
|
-
const showAttention = viewer === undefined || isViewer;
|
|
658
|
-
const attention = !showAttention
|
|
659
|
-
? ''
|
|
660
|
-
: p.pendingMentionCount > 0
|
|
661
|
-
? `${p.pendingMentionCount} mention${p.pendingMentionCount === 1 ? '' : 's'} waiting`
|
|
662
|
-
: p.unreadActivityCount > 0
|
|
663
|
-
? `${p.unreadActivityCount} change${p.unreadActivityCount === 1 ? '' : 's'} waiting`
|
|
664
|
-
: 'caught up';
|
|
665
|
-
return ` ${glyph} ${p.name} · ${summary}${attention === '' ? '' : ` · ${attention}`}`;
|
|
666
|
-
});
|
|
667
|
-
const capText = formatHardCap(result.hardCap) === '-1' ? 'unlimited' : formatHardCap(result.hardCap);
|
|
668
|
-
const holdText = result.holdActive
|
|
669
|
-
? `· ${result.holdActor ?? 'someone'} raised a hand${result.holdReason ? ` — ${result.holdReason}` : ''} · ${result.holdAt === undefined ? 'just now' : formatRelativeTime(result.holdAt, now)}`
|
|
670
|
-
: undefined;
|
|
671
|
-
const counts = `${result.activeCount} active · ${result.doneCount} done · cap ${capText} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`;
|
|
672
|
-
const latestRendered = result.latestAct === undefined
|
|
673
|
-
? ''
|
|
674
|
-
: renderVisibleEvent(doc.acts, result.latestAct, viewer ?? '', {
|
|
675
|
-
now,
|
|
676
|
-
preview: 200,
|
|
677
|
-
actNumber: result.latestAct.kind === 'say' ? sayNumberFor(doc.acts, result.latestAct) : undefined,
|
|
678
|
-
});
|
|
679
|
-
const latestLines = latestRendered === ''
|
|
680
|
-
? [result.latestAct === undefined ? ' ○ no public activity yet' : ' · latest activity is private to another participant']
|
|
681
|
-
: [` ${latestRendered.replace(/\n/g, '\n ')}`];
|
|
682
|
-
if (latestRendered.includes('more chars') && result.latestAct !== undefined) {
|
|
683
|
-
const readPrefix = viewer === undefined ? commandPrefix(squarePath) : participantCommandPrefix(squarePath, viewer);
|
|
684
|
-
latestLines.push(`» ${readPrefix} echo --at ${actId(result.latestAct)} -C 2 --full`);
|
|
685
|
-
}
|
|
686
|
-
const out = [
|
|
687
|
-
counts,
|
|
688
|
-
...(holdText ? ['', holdText] : []),
|
|
689
|
-
'',
|
|
690
|
-
'around the square',
|
|
691
|
-
...participantLines,
|
|
692
|
-
'',
|
|
693
|
-
'latest',
|
|
694
|
-
...latestLines,
|
|
695
|
-
].join('\n');
|
|
696
|
-
process.stdout.write(withPathOutput(squarePath, out, { participantCount: result.activeCount, held: result.holdActive }));
|
|
697
|
-
}
|
|
698
|
-
catch (err) {
|
|
699
|
-
handleSquareError(err);
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
function cmdParticipants(squarePath) {
|
|
703
|
-
try {
|
|
704
|
-
const doc = loadSquare(squarePath);
|
|
705
|
-
const result = coreParticipants(doc, nowMs());
|
|
706
|
-
const { now } = result;
|
|
707
|
-
const lines = result.participants.map((p) => {
|
|
708
|
-
const glyph = p.state === 'done' ? '×' : p.presence === 'watching' ? '◎' : p.activityCount > 0 ? '●' : '○';
|
|
709
|
-
const state = p.state === 'done' ? 'done' : p.presence === 'watching' ? 'catching' : p.state;
|
|
710
|
-
const last = p.lastActiveAt === undefined ? '—' : formatRelativeTime(p.lastActiveAt, now);
|
|
711
|
-
return ` ${glyph} ${p.name} · ${state} · ${p.activityCount} act${p.activityCount === 1 ? '' : 's'} · ${last}`;
|
|
712
|
-
});
|
|
713
|
-
const out = ['participants', ...lines].join('\n');
|
|
714
|
-
process.stdout.write(withPathOutput(squarePath, out, { participantCount: result.participants.filter((p) => p.state === 'active').length }));
|
|
715
|
-
}
|
|
716
|
-
catch (err) {
|
|
717
|
-
handleSquareError(err);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
function parseActRef(value, flag) {
|
|
721
|
-
const match = value.trim().match(/^(?:act_)?(\d+)$/i);
|
|
722
|
-
if (!match) {
|
|
723
|
-
process.stderr.write(`Invalid ${flag}: expected act id like act_12 or 12.\n`);
|
|
724
|
-
process.exit(2);
|
|
725
|
-
}
|
|
726
|
-
return Number(match[1]);
|
|
727
|
-
}
|
|
728
|
-
function parseActRefList(value, flag) {
|
|
729
|
-
return value
|
|
730
|
-
.split(',')
|
|
731
|
-
.map((part) => part.trim())
|
|
732
|
-
.filter(Boolean)
|
|
733
|
-
.map((part) => parseActRef(part, flag));
|
|
734
|
-
}
|
|
735
|
-
function formatActivityFields(doc, item, fields) {
|
|
736
|
-
const values = fields.map((field) => {
|
|
737
|
-
switch (field) {
|
|
738
|
-
case 'id':
|
|
739
|
-
return actId(item.index);
|
|
740
|
-
case 'author':
|
|
741
|
-
case 'actor':
|
|
742
|
-
return item.act.actor ?? '';
|
|
743
|
-
case 'ts':
|
|
744
|
-
case 'at':
|
|
745
|
-
return formatTimestamp(item.act.at);
|
|
746
|
-
case 'kind':
|
|
747
|
-
return item.act.kind;
|
|
748
|
-
case 'body':
|
|
749
|
-
return 'body' in item.act && typeof item.act.body === 'string'
|
|
750
|
-
? item.act.body.replace(/\s+/g, ' ').trim()
|
|
751
|
-
: '';
|
|
752
|
-
case 'number':
|
|
753
|
-
return item.act.kind === 'say' ? String(sayNumberFor(doc.acts, item.act)) : '';
|
|
754
|
-
default:
|
|
755
|
-
return '';
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
return values.join('\t');
|
|
759
|
-
}
|
|
760
|
-
function activityJsonLine(doc, item) {
|
|
761
|
-
const act = item.act;
|
|
762
|
-
return JSON.stringify({
|
|
763
|
-
id: actId(item.index),
|
|
764
|
-
index: item.index,
|
|
765
|
-
kind: act.kind,
|
|
766
|
-
author: act.actor ?? null,
|
|
767
|
-
at: act.at,
|
|
768
|
-
ts: formatTimestamp(act.at),
|
|
769
|
-
body: 'body' in act && typeof act.body === 'string' ? act.body : '',
|
|
770
|
-
number: act.kind === 'say' ? sayNumberFor(doc.acts, act) : null,
|
|
771
|
-
reach: act.kind === 'say' ? act.reach ?? null : null,
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
function cmdActivities(squarePath, opts = {}) {
|
|
775
|
-
try {
|
|
776
|
-
const doc = loadSquare(squarePath);
|
|
777
|
-
let events = coreActivities(doc, opts);
|
|
778
|
-
const searching = opts.grep !== undefined || opts.fixed !== undefined;
|
|
779
|
-
const totalMatches = searching ? events.length : 0;
|
|
780
|
-
if (opts.lastN != null) {
|
|
781
|
-
events = opts.order === 'desc' ? events.slice(0, opts.lastN) : events.slice(-opts.lastN);
|
|
782
|
-
}
|
|
783
|
-
if (opts.countOnly) {
|
|
784
|
-
process.stdout.write(`${searching ? totalMatches : events.length}\n`);
|
|
785
|
-
return;
|
|
786
|
-
}
|
|
787
|
-
if (opts.json) {
|
|
788
|
-
process.stdout.write(events.map((item) => activityJsonLine(doc, item)).join('\n') + (events.length > 0 ? '\n' : ''));
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
if (opts.format !== undefined && opts.format.length > 0) {
|
|
792
|
-
process.stdout.write(events.map((item) => formatActivityFields(doc, item, opts.format)).join('\n') + (events.length > 0 ? '\n' : ''));
|
|
793
|
-
return;
|
|
794
|
-
}
|
|
795
|
-
const pattern = opts.grep ?? opts.fixed;
|
|
796
|
-
const out = pattern !== undefined && pattern !== ''
|
|
797
|
-
? renderGrepActivitiesView(events, totalMatches, opts.full, squarePath, pattern, opts.fixed !== undefined)
|
|
798
|
-
: renderActivitiesView(doc, events, null, opts.full, squarePath, opts.viewer ?? '');
|
|
799
|
-
if (out === '') {
|
|
800
|
-
process.stdout.write(withPathOutput(squarePath, '', { participantCount: inSquareCount(doc) }));
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
process.stdout.write(withPathOutput(squarePath, out, { participantCount: inSquareCount(doc) }));
|
|
804
|
-
}
|
|
805
|
-
catch (err) {
|
|
806
|
-
handleSquareError(err);
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
function parseTimestampOption(value, flag) {
|
|
810
|
-
const ms = parseTimeOrRelative(value, nowMs());
|
|
811
|
-
if (!Number.isFinite(ms)) {
|
|
812
|
-
process.stderr.write(`Invalid ${flag} timestamp: ${value}\n`);
|
|
813
|
-
process.exit(2);
|
|
814
|
-
}
|
|
815
|
-
return ms;
|
|
816
|
-
}
|
|
817
|
-
function parseActivitiesOptions(args, viewer) {
|
|
818
|
-
let lastN = 10;
|
|
819
|
-
let lastNExplicit = false;
|
|
820
|
-
let before;
|
|
821
|
-
let after;
|
|
822
|
-
let afterIndex;
|
|
823
|
-
let atIndex;
|
|
824
|
-
let beforeContext;
|
|
825
|
-
let afterContext;
|
|
826
|
-
let mention;
|
|
827
|
-
let mentionsViewer = false;
|
|
828
|
-
let pending = false;
|
|
829
|
-
let full = false;
|
|
830
|
-
let grep;
|
|
831
|
-
let fixed;
|
|
832
|
-
let ids;
|
|
833
|
-
let order;
|
|
834
|
-
let format;
|
|
835
|
-
let countOnly = false;
|
|
836
|
-
let json = false;
|
|
837
|
-
const participants = [];
|
|
838
|
-
for (let i = 0; i < args.length; i++) {
|
|
839
|
-
const arg = args[i];
|
|
840
|
-
if (arg === '--last' || arg === '--limit') {
|
|
841
|
-
lastN = parsePositiveIntegerOption(requireValue(args, i, arg), arg);
|
|
842
|
-
lastNExplicit = true;
|
|
843
|
-
i++;
|
|
844
|
-
}
|
|
845
|
-
else if (arg === '--all') {
|
|
846
|
-
lastN = null;
|
|
847
|
-
lastNExplicit = true;
|
|
848
|
-
}
|
|
849
|
-
else if (arg === '--by' || arg === '--from') {
|
|
850
|
-
participants.push(...parseNameListOption(requireValue(args, i, arg), arg));
|
|
851
|
-
i++;
|
|
852
|
-
}
|
|
853
|
-
else if (arg === '--before' || arg === '--until') {
|
|
854
|
-
before = parseTimestampOption(requireValue(args, i, arg), arg);
|
|
855
|
-
i++;
|
|
856
|
-
}
|
|
857
|
-
else if (arg === '--since') {
|
|
858
|
-
after = parseTimestampOption(requireValue(args, i, arg), arg);
|
|
859
|
-
i++;
|
|
860
|
-
}
|
|
861
|
-
else if (arg === '--after') {
|
|
862
|
-
afterIndex = parseActRef(requireValue(args, i, arg), arg);
|
|
863
|
-
i++;
|
|
864
|
-
}
|
|
865
|
-
else if (arg === '--at') {
|
|
866
|
-
atIndex = parseActRef(requireValue(args, i, arg), arg);
|
|
867
|
-
i++;
|
|
868
|
-
}
|
|
869
|
-
else if (arg === '-B') {
|
|
870
|
-
beforeContext = parseNonNegativeIntegerOption(requireValue(args, i, arg), arg);
|
|
871
|
-
i++;
|
|
872
|
-
}
|
|
873
|
-
else if (arg === '-A') {
|
|
874
|
-
afterContext = parseNonNegativeIntegerOption(requireValue(args, i, arg), arg);
|
|
875
|
-
i++;
|
|
876
|
-
}
|
|
877
|
-
else if (arg === '-C') {
|
|
878
|
-
const n = parseNonNegativeIntegerOption(requireValue(args, i, arg), arg);
|
|
879
|
-
beforeContext = n;
|
|
880
|
-
afterContext = n;
|
|
881
|
-
i++;
|
|
882
|
-
}
|
|
883
|
-
else if (arg === '--full') {
|
|
884
|
-
full = true;
|
|
885
|
-
}
|
|
886
|
-
else if (arg === '--mention') {
|
|
887
|
-
mention = requireValue(args, i, arg);
|
|
888
|
-
i++;
|
|
889
|
-
}
|
|
890
|
-
else if (arg === '--mentions') {
|
|
891
|
-
const value = requireValue(args, i, arg);
|
|
892
|
-
if (value !== 'me') {
|
|
893
|
-
process.stderr.write(`Invalid --mentions: only 'me' is supported (got ${value}).\n`);
|
|
894
|
-
process.exit(2);
|
|
895
|
-
}
|
|
896
|
-
mentionsViewer = true;
|
|
897
|
-
i++;
|
|
898
|
-
}
|
|
899
|
-
else if (arg === '--pending') {
|
|
900
|
-
pending = true;
|
|
901
|
-
}
|
|
902
|
-
else if (arg === '--grep') {
|
|
903
|
-
grep = requireValue(args, i, arg);
|
|
904
|
-
i++;
|
|
905
|
-
}
|
|
906
|
-
else if (arg === '--fixed') {
|
|
907
|
-
fixed = requireValue(args, i, arg);
|
|
908
|
-
i++;
|
|
909
|
-
}
|
|
910
|
-
else if (arg === '--ids') {
|
|
911
|
-
ids = parseActRefList(requireValue(args, i, arg), arg);
|
|
912
|
-
i++;
|
|
913
|
-
}
|
|
914
|
-
else if (arg === '--order') {
|
|
915
|
-
const value = requireValue(args, i, arg);
|
|
916
|
-
if (value !== 'asc' && value !== 'desc') {
|
|
917
|
-
process.stderr.write(`Invalid --order: expected asc or desc.\n`);
|
|
918
|
-
process.exit(2);
|
|
919
|
-
}
|
|
920
|
-
order = value;
|
|
921
|
-
i++;
|
|
922
|
-
}
|
|
923
|
-
else if (arg === '--format') {
|
|
924
|
-
format = requireValue(args, i, arg)
|
|
925
|
-
.split(',')
|
|
926
|
-
.map((part) => part.trim())
|
|
927
|
-
.filter(Boolean);
|
|
928
|
-
i++;
|
|
929
|
-
}
|
|
930
|
-
else if (arg === '--count') {
|
|
931
|
-
countOnly = true;
|
|
932
|
-
}
|
|
933
|
-
else if (arg === '--json') {
|
|
934
|
-
json = true;
|
|
935
|
-
}
|
|
936
|
-
else {
|
|
937
|
-
usage();
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
if ((mentionsViewer || pending) && (viewer === undefined || viewer === '')) {
|
|
941
|
-
process.stderr.write(`--mentions me / --pending require --as <name>.\n`);
|
|
942
|
-
process.exit(2);
|
|
943
|
-
}
|
|
944
|
-
if (grep !== undefined && fixed !== undefined) {
|
|
945
|
-
process.stderr.write('--grep and --fixed cannot be combined.\n');
|
|
946
|
-
process.exit(2);
|
|
947
|
-
}
|
|
948
|
-
if (grep === '' || fixed === '') {
|
|
949
|
-
process.stderr.write('--grep and --fixed require non-empty text.\n');
|
|
950
|
-
process.exit(2);
|
|
951
|
-
}
|
|
952
|
-
// Context windows and exact id lists should not be silently clipped by the default --last 10.
|
|
953
|
-
if (!lastNExplicit && (atIndex !== undefined || ids !== undefined || pending)) {
|
|
954
|
-
lastN = null;
|
|
955
|
-
}
|
|
956
|
-
return {
|
|
957
|
-
lastN,
|
|
958
|
-
participants,
|
|
959
|
-
before,
|
|
960
|
-
after,
|
|
961
|
-
afterIndex,
|
|
962
|
-
atIndex,
|
|
963
|
-
beforeContext,
|
|
964
|
-
afterContext,
|
|
965
|
-
mention,
|
|
966
|
-
mentionsViewer,
|
|
967
|
-
pending,
|
|
968
|
-
viewer,
|
|
969
|
-
full,
|
|
970
|
-
grep,
|
|
971
|
-
fixed,
|
|
972
|
-
ids,
|
|
973
|
-
order,
|
|
974
|
-
format,
|
|
975
|
-
countOnly,
|
|
976
|
-
json,
|
|
977
|
-
};
|
|
978
|
-
}
|
|
979
|
-
function parseWatchOptions(args, name) {
|
|
980
|
-
let activityCount = 1;
|
|
981
|
-
let idleMs;
|
|
982
|
-
let mention;
|
|
983
|
-
let force = false;
|
|
984
|
-
let now = false;
|
|
985
|
-
let follow = false;
|
|
986
|
-
const participants = [];
|
|
987
|
-
for (let i = 0; i < args.length; i++) {
|
|
988
|
-
if (args[i] === '--count') {
|
|
989
|
-
activityCount = parsePositiveIntegerOption(requireValue(args, i, args[i]), args[i]);
|
|
990
|
-
i++;
|
|
991
|
-
}
|
|
992
|
-
else if (args[i] === '--by') {
|
|
993
|
-
participants.push(...parseNameListOption(requireValue(args, i, args[i]), args[i]));
|
|
994
|
-
i++;
|
|
995
|
-
}
|
|
996
|
-
else if (args[i] === '--idle') {
|
|
997
|
-
idleMs = parseDurationMs(requireValue(args, i, args[i]), args[i]);
|
|
998
|
-
i++;
|
|
999
|
-
}
|
|
1000
|
-
else if (args[i] === '--mention') {
|
|
1001
|
-
const value = args[i + 1];
|
|
1002
|
-
if (value !== undefined && !value.startsWith('--')) {
|
|
1003
|
-
mention = value;
|
|
1004
|
-
i++;
|
|
1005
|
-
}
|
|
1006
|
-
else {
|
|
1007
|
-
mention = name;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
else if (args[i] === '-f' || args[i] === '--force') {
|
|
1011
|
-
force = true;
|
|
1012
|
-
}
|
|
1013
|
-
else if (args[i] === '--now') {
|
|
1014
|
-
now = true;
|
|
1015
|
-
}
|
|
1016
|
-
else if (args[i] === '--follow') {
|
|
1017
|
-
follow = true;
|
|
1018
|
-
}
|
|
1019
|
-
else {
|
|
1020
|
-
usage();
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
if (now && follow) {
|
|
1024
|
-
process.stderr.write('--follow cannot be combined with --now\n');
|
|
1025
|
-
process.exit(2);
|
|
1026
|
-
}
|
|
1027
|
-
return {
|
|
1028
|
-
activityCount,
|
|
1029
|
-
...(participants.length > 0 ? { participants } : {}),
|
|
1030
|
-
...(mention !== undefined ? { mention } : {}),
|
|
1031
|
-
...(idleMs !== undefined ? { idleMs } : {}),
|
|
1032
|
-
...(force ? { force } : {}),
|
|
1033
|
-
...(now ? { now } : {}),
|
|
1034
|
-
...(follow ? { follow } : {}),
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
function parseCompactOptions(args) {
|
|
1038
|
-
let keep = 50;
|
|
1039
|
-
for (let i = 0; i < args.length; i++) {
|
|
1040
|
-
if (args[i] === '--keep') {
|
|
1041
|
-
keep = parsePositiveIntegerOption(requireValue(args, i, args[i]), args[i]);
|
|
1042
|
-
i++;
|
|
1043
|
-
}
|
|
1044
|
-
else {
|
|
1045
|
-
usage();
|
|
1046
|
-
}
|
|
1047
|
-
}
|
|
1048
|
-
return { keep };
|
|
1049
|
-
}
|
|
1050
|
-
function parseDoctorOptions(args) {
|
|
1051
|
-
let fix = false;
|
|
1052
|
-
let reconcileBacklog = false;
|
|
1053
|
-
for (let i = 0; i < args.length; i++) {
|
|
1054
|
-
const arg = args[i];
|
|
1055
|
-
if (arg === '--fix') {
|
|
1056
|
-
fix = true;
|
|
1057
|
-
}
|
|
1058
|
-
else if (arg === 'reconcile-backlog') {
|
|
1059
|
-
reconcileBacklog = true;
|
|
1060
|
-
}
|
|
1061
|
-
else if (arg === '--before') {
|
|
1062
|
-
// Optional timestamp accepted for operator clarity; reconcile always uses lookback partition.
|
|
1063
|
-
i++;
|
|
1064
|
-
if (args[i] === undefined)
|
|
1065
|
-
usage();
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
usage();
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
if (reconcileBacklog && !fix) {
|
|
1072
|
-
process.stderr.write('doctor reconcile-backlog requires --fix.\n');
|
|
1073
|
-
process.exit(2);
|
|
1074
|
-
}
|
|
1075
|
-
return { fix, reconcileBacklog };
|
|
1076
|
-
}
|
|
1077
|
-
function quarantinePath(squarePath) {
|
|
1078
|
-
return squarePath.replace(/\.md$/, '') + '.quarantine.md';
|
|
1079
|
-
}
|
|
1080
|
-
function readSquareText(squarePath) {
|
|
1081
|
-
try {
|
|
1082
|
-
return fs.readFileSync(squarePath, 'utf8');
|
|
1083
|
-
}
|
|
1084
|
-
catch (err) {
|
|
1085
|
-
if (err.code === 'ENOENT') {
|
|
1086
|
-
throw new SquareError('not_found', `square file not found: ${squarePath}`);
|
|
1087
|
-
}
|
|
1088
|
-
throw err;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
async function cmdDoctor(squarePath, opts) {
|
|
1092
|
-
try {
|
|
1093
|
-
if (!opts.fix) {
|
|
1094
|
-
const diagnosis = diagnoseSquare(readSquareText(squarePath));
|
|
1095
|
-
if (diagnosis.unfixable) {
|
|
1096
|
-
process.stdout.write(withPathOutput(squarePath, renderDoctorUnfixable(diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
1097
|
-
process.exit(2);
|
|
1098
|
-
}
|
|
1099
|
-
const out = diagnosis.problems.length === 0 ? renderDoctorClean() : renderDoctorProblems(diagnosis.problems);
|
|
1100
|
-
const delivery = doctorDeliveryHealth(squarePath).join('\n');
|
|
1101
|
-
process.stdout.write(withPathOutput(squarePath, `${out}\n\n${delivery}`, { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
1102
|
-
const recentStale = findStalePendingMentions(squarePath);
|
|
1103
|
-
process.exit(diagnosis.problems.length === 0 && recentStale.length === 0 ? 0 : 1);
|
|
1104
|
-
}
|
|
1105
|
-
if (opts.reconcileBacklog) {
|
|
1106
|
-
const result = await withSquareLock(squarePath, () => reconcileDeliveryBacklog(squarePath));
|
|
1107
|
-
const delivery = doctorDeliveryHealth(squarePath).join('\n');
|
|
1108
|
-
process.stdout.write(withPathOutput(squarePath, [
|
|
1109
|
-
`✓ reconciled ${result.reconciled} backlog receipt(s) as delivered(reason=reconciled)`,
|
|
1110
|
-
result.skippedRecent > 0
|
|
1111
|
-
? `· left ${result.skippedRecent} recent liveness failure(s) untouched`
|
|
1112
|
-
: '· no recent liveness failures present',
|
|
1113
|
-
'',
|
|
1114
|
-
delivery,
|
|
1115
|
-
].join('\n'), { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
1116
|
-
process.exit(result.skippedRecent > 0 ? 1 : 0);
|
|
1117
|
-
}
|
|
1118
|
-
const sidecar = quarantinePath(squarePath);
|
|
1119
|
-
let plan;
|
|
1120
|
-
await withSquareLock(squarePath, () => {
|
|
1121
|
-
plan = planRepair(readSquareText(squarePath));
|
|
1122
|
-
if (plan.diagnosis.unfixable || plan.repaired === undefined)
|
|
1123
|
-
return;
|
|
1124
|
-
if (plan.repaired.quarantinedBlocks.length > 0) {
|
|
1125
|
-
const sidecarExists = fs.existsSync(sidecar);
|
|
1126
|
-
const block = plan.repaired.quarantinedBlocks.join('\n\n');
|
|
1127
|
-
fs.appendFileSync(sidecar, (sidecarExists ? '\n' : '') + block + '\n');
|
|
1128
|
-
}
|
|
1129
|
-
writeSquareDoc(squarePath, plan.repaired.doc);
|
|
1130
|
-
});
|
|
1131
|
-
if (plan.diagnosis.unfixable) {
|
|
1132
|
-
process.stdout.write(withPathOutput(squarePath, renderDoctorUnfixable(plan.diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
1133
|
-
process.exit(2);
|
|
1134
|
-
}
|
|
1135
|
-
const repaired = plan.repaired;
|
|
1136
|
-
process.stdout.write(withPathOutput(squarePath, renderDoctorRepaired(repaired.actions, repaired.quarantinedBlocks.length, repaired.quarantinedBlocks.length > 0 ? sidecar : undefined), { participantCount: inSquareCount(loadSquare(squarePath)) }));
|
|
1137
|
-
}
|
|
1138
|
-
catch (err) {
|
|
1139
|
-
handleSquareError(err);
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
let usageCommand;
|
|
1143
|
-
function usage() {
|
|
1144
|
-
process.stderr.write(`✕ invalid arguments${usageCommand === undefined ? '' : ` for ${usageCommand}`}\n`);
|
|
1145
|
-
process.stderr.write(commandUsageHint(usageCommand));
|
|
1146
|
-
process.exit(2);
|
|
1147
|
-
}
|
|
1148
|
-
function cmdHelp() {
|
|
1149
|
-
process.stdout.write(renderGlobalHelp());
|
|
1150
|
-
}
|
|
1151
|
-
function cmdSubcommandHelp(command) {
|
|
1152
|
-
const rendered = renderScopedHelp(command);
|
|
1153
|
-
if (rendered === undefined) {
|
|
1154
|
-
process.stderr.write(`unknown command: ${command}\nrun 'square help' to list every command\n`);
|
|
1155
|
-
process.exit(2);
|
|
1156
|
-
}
|
|
1157
|
-
process.stdout.write(rendered);
|
|
1158
|
-
}
|
|
1159
|
-
function cmdVersion() {
|
|
1160
|
-
const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
1161
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
1162
|
-
process.stdout.write(`${packageJson.version ?? 'unknown'}\n`);
|
|
1163
|
-
}
|
|
1164
|
-
function cmdInbox(args) {
|
|
1165
|
-
let sessionId;
|
|
1166
|
-
let json = false;
|
|
1167
|
-
for (let index = 0; index < args.length; index++) {
|
|
1168
|
-
if (args[index] === '--for-session') {
|
|
1169
|
-
sessionId = requireValue(args, index, args[index]);
|
|
1170
|
-
index++;
|
|
1171
|
-
}
|
|
1172
|
-
else if (args[index] === '--json') {
|
|
1173
|
-
json = true;
|
|
1174
|
-
}
|
|
1175
|
-
else {
|
|
1176
|
-
usage();
|
|
1177
|
-
}
|
|
1178
|
-
}
|
|
1179
|
-
if (!sessionId) {
|
|
1180
|
-
process.stderr.write('inbox requires --for-session <session-id>.\n');
|
|
1181
|
-
process.exit(2);
|
|
1182
|
-
}
|
|
1183
|
-
const inbox = sessionInbox(sessionId);
|
|
1184
|
-
if (json) {
|
|
1185
|
-
process.stdout.write(`${JSON.stringify(inbox)}\n`);
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
for (const membership of inbox) {
|
|
1189
|
-
process.stdout.write(`${membership.name}\t${membership.squarePath}\t${membership.notifications.length}\n`);
|
|
1190
|
-
}
|
|
1191
|
-
}
|
|
1192
|
-
function cmdClaudeHook(args) {
|
|
1193
|
-
if (args.length > 0)
|
|
1194
|
-
usage();
|
|
1195
|
-
process.stdout.write(runClaudeHook(readStdinSync()));
|
|
1196
|
-
}
|
|
1197
|
-
function cmdCodexHook(args) {
|
|
1198
|
-
if (args.length > 0)
|
|
1199
|
-
usage();
|
|
1200
|
-
process.stdout.write(runCodexHook(readStdinSync()));
|
|
1201
|
-
}
|
|
1202
|
-
function refreshLocalRegistration(squarePath, name) {
|
|
1203
|
-
if (!name)
|
|
1204
|
-
return;
|
|
1205
|
-
try {
|
|
1206
|
-
const doc = loadSquare(squarePath);
|
|
1207
|
-
const known = resolveRosterName(doc, name);
|
|
1208
|
-
if (known !== undefined && isCurrentlyJoined(doc.acts, known)) {
|
|
1209
|
-
recordLocalJoin(known, squarePath);
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
catch {
|
|
1213
|
-
// Registration is a best-effort machine-local discovery cache.
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
2
|
+
import { runCli } from './cli/program.js';
|
|
1216
3
|
try {
|
|
1217
|
-
|
|
1218
|
-
const requestedHelp = parseHelpRequest(rawArgs);
|
|
1219
|
-
if (requestedHelp !== undefined) {
|
|
1220
|
-
if (requestedHelp.command === undefined)
|
|
1221
|
-
cmdHelp();
|
|
1222
|
-
else
|
|
1223
|
-
cmdSubcommandHelp(requestedHelp.command);
|
|
1224
|
-
process.exit(0);
|
|
1225
|
-
}
|
|
1226
|
-
const { squarePath: defaultSquarePath, explicitSquarePath, multipleSquares, name: defaultName, args } = parseGlobalArgs(rawArgs);
|
|
1227
|
-
if (args.length === 0) {
|
|
1228
|
-
cmdHelp();
|
|
1229
|
-
process.exit(0);
|
|
1230
|
-
}
|
|
1231
|
-
if (args[0] === 'help') {
|
|
1232
|
-
process.stderr.write('Usage: square help [command]\n');
|
|
1233
|
-
process.exit(2);
|
|
1234
|
-
}
|
|
1235
|
-
if (args[0] === '--help' || args[0] === '-h') {
|
|
1236
|
-
cmdHelp();
|
|
1237
|
-
process.exit(0);
|
|
1238
|
-
}
|
|
1239
|
-
if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
|
|
1240
|
-
cmdVersion();
|
|
1241
|
-
process.exit(0);
|
|
1242
|
-
}
|
|
1243
|
-
const cmd = args[0];
|
|
1244
|
-
usageCommand = cmd;
|
|
1245
|
-
const mutatingDoctor = cmd === 'doctor' && args.slice(1).some((arg) => arg === '--fix' || arg === 'reconcile-backlog');
|
|
1246
|
-
if (!explicitSquarePath && multipleSquares && (['build', 'join', 'catch', 'act', 'done', 'hold', 'resume', 'compact'].includes(cmd) || mutatingDoctor)) {
|
|
1247
|
-
process.stderr.write('✕ more than one square is active here; choose the path before changing or consuming activity.\n» square ls\n');
|
|
1248
|
-
process.exit(2);
|
|
1249
|
-
}
|
|
1250
|
-
const participantActionCommands = new Set(['act', 'catch', 'done', 'hold', 'resume']);
|
|
1251
|
-
if (participantActionCommands.has(cmd))
|
|
1252
|
-
refreshLocalRegistration(defaultSquarePath, defaultName);
|
|
1253
|
-
switch (cmd) {
|
|
1254
|
-
case 'build':
|
|
1255
|
-
await cmdBuild(defaultSquarePath, args.slice(1));
|
|
1256
|
-
break;
|
|
1257
|
-
case 'ls':
|
|
1258
|
-
case 'list':
|
|
1259
|
-
cmdListSquares(args.slice(1), usage);
|
|
1260
|
-
break;
|
|
1261
|
-
case 'join': {
|
|
1262
|
-
const name = requireNameFlag(defaultName);
|
|
1263
|
-
await cmdJoin(defaultSquarePath, name, parseJoinOptions(args.slice(1)));
|
|
1264
|
-
break;
|
|
1265
|
-
}
|
|
1266
|
-
case 'stream': {
|
|
1267
|
-
let ndjson = false;
|
|
1268
|
-
let forName;
|
|
1269
|
-
for (let i = 1; i < args.length; i++) {
|
|
1270
|
-
if (args[i] === '--ndjson')
|
|
1271
|
-
ndjson = true;
|
|
1272
|
-
else if (args[i] === '--for' && i + 1 < args.length)
|
|
1273
|
-
forName = args[++i];
|
|
1274
|
-
else
|
|
1275
|
-
usage();
|
|
1276
|
-
}
|
|
1277
|
-
if (ndjson) {
|
|
1278
|
-
await cmdStreamNdjson(defaultSquarePath, forName);
|
|
1279
|
-
}
|
|
1280
|
-
else {
|
|
1281
|
-
if (forName)
|
|
1282
|
-
usage();
|
|
1283
|
-
await cmdStream(defaultSquarePath);
|
|
1284
|
-
}
|
|
1285
|
-
break;
|
|
1286
|
-
}
|
|
1287
|
-
case 'inbox':
|
|
1288
|
-
cmdInbox(args.slice(1));
|
|
1289
|
-
break;
|
|
1290
|
-
case 'claude-hook':
|
|
1291
|
-
cmdClaudeHook(args.slice(1));
|
|
1292
|
-
break;
|
|
1293
|
-
case 'codex-hook':
|
|
1294
|
-
cmdCodexHook(args.slice(1));
|
|
1295
|
-
break;
|
|
1296
|
-
case 'catch': {
|
|
1297
|
-
const name = requireNameFlag(defaultName);
|
|
1298
|
-
await cmdWatch(defaultSquarePath, name, parseWatchOptions(args.slice(1), name));
|
|
1299
|
-
break;
|
|
1300
|
-
}
|
|
1301
|
-
case 'act': {
|
|
1302
|
-
const name = requireNameFlag(defaultName);
|
|
1303
|
-
const opts = parseActOptions(args.slice(1));
|
|
1304
|
-
await cmdActivity(defaultSquarePath, name, opts.activity, resolveBody, {
|
|
1305
|
-
force: opts.force,
|
|
1306
|
-
noWait: opts.noWait,
|
|
1307
|
-
forceCommand: forceActCommand(defaultSquarePath, name, opts.reach),
|
|
1308
|
-
reach: opts.reach,
|
|
1309
|
-
});
|
|
1310
|
-
break;
|
|
1311
|
-
}
|
|
1312
|
-
case 'done': {
|
|
1313
|
-
const name = requireNameFlag(defaultName);
|
|
1314
|
-
await cmdDone(defaultSquarePath, name, parseDoneOptions(args.slice(1)));
|
|
1315
|
-
break;
|
|
1316
|
-
}
|
|
1317
|
-
case 'hold':
|
|
1318
|
-
if (args.length > 2)
|
|
1319
|
-
usage();
|
|
1320
|
-
await cmdHold(defaultSquarePath, requireNameFlag(defaultName), args[1]);
|
|
1321
|
-
break;
|
|
1322
|
-
case 'resume':
|
|
1323
|
-
if (args.length > 1)
|
|
1324
|
-
usage();
|
|
1325
|
-
await cmdResume(defaultSquarePath, requireNameFlag(defaultName));
|
|
1326
|
-
break;
|
|
1327
|
-
case 'harness':
|
|
1328
|
-
await cmdHarness(args.slice(1), defaultSquarePath);
|
|
1329
|
-
break;
|
|
1330
|
-
case 'compact': {
|
|
1331
|
-
const opts = parseCompactOptions(args.slice(1));
|
|
1332
|
-
await cmdCompact(defaultSquarePath, { keep: opts.keep });
|
|
1333
|
-
break;
|
|
1334
|
-
}
|
|
1335
|
-
case 'doctor': {
|
|
1336
|
-
const opts = parseDoctorOptions(args.slice(1));
|
|
1337
|
-
await cmdDoctor(defaultSquarePath, { fix: opts.fix, reconcileBacklog: opts.reconcileBacklog });
|
|
1338
|
-
break;
|
|
1339
|
-
}
|
|
1340
|
-
case 'echo': {
|
|
1341
|
-
cmdActivities(defaultSquarePath, parseActivitiesOptions(args.slice(1), defaultName));
|
|
1342
|
-
break;
|
|
1343
|
-
}
|
|
1344
|
-
case 'warmup':
|
|
1345
|
-
if (args.length > 1)
|
|
1346
|
-
usage();
|
|
1347
|
-
cmdWarmup(defaultSquarePath);
|
|
1348
|
-
break;
|
|
1349
|
-
case 'status':
|
|
1350
|
-
if (args.length > 1)
|
|
1351
|
-
usage();
|
|
1352
|
-
cmdStatus(defaultSquarePath, defaultName);
|
|
1353
|
-
break;
|
|
1354
|
-
case 'participants':
|
|
1355
|
-
if (args.length > 1)
|
|
1356
|
-
usage();
|
|
1357
|
-
cmdParticipants(defaultSquarePath);
|
|
1358
|
-
break;
|
|
1359
|
-
default:
|
|
1360
|
-
process.stderr.write(`unknown command: ${cmd}\nrun 'square' for usage\n`);
|
|
1361
|
-
process.exit(2);
|
|
1362
|
-
}
|
|
4
|
+
await runCli();
|
|
1363
5
|
}
|
|
1364
|
-
catch (
|
|
1365
|
-
|
|
6
|
+
catch (error) {
|
|
7
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
8
|
+
process.exitCode = 1;
|
|
1366
9
|
}
|