@astrosheep/square 0.3.11 → 0.3.13

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 (127) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/codex-plugin/hooks/hooks.json +9 -0
  3. package/dist/activity-feed.d.ts +14 -0
  4. package/dist/activity-feed.js +5 -5
  5. package/dist/activity.d.ts +9 -0
  6. package/dist/activity.js +112 -96
  7. package/dist/artifact.d.ts +20 -0
  8. package/dist/artifact.js +317 -648
  9. package/dist/automatic-session.d.ts +5 -0
  10. package/dist/automatic-session.js +86 -0
  11. package/dist/boundary-presentation.d.ts +5 -0
  12. package/dist/boundary-presentation.js +6 -5
  13. package/dist/claude-hook.d.ts +9 -0
  14. package/dist/claude-hook.js +35 -3
  15. package/dist/cli/context.d.ts +35 -0
  16. package/dist/cli/context.js +26 -31
  17. package/dist/cli/harness-command.d.ts +28 -0
  18. package/dist/cli/harness-command.js +30 -15
  19. package/dist/cli/maintenance-commands.d.ts +7 -0
  20. package/dist/cli/maintenance-commands.js +12 -69
  21. package/dist/cli/meta-commands.d.ts +5 -0
  22. package/dist/cli/observation-commands.d.ts +20 -0
  23. package/dist/cli/observation-commands.js +159 -139
  24. package/dist/cli/program.d.ts +2 -0
  25. package/dist/cli/program.js +3 -12
  26. package/dist/cli/registry.d.ts +9 -0
  27. package/dist/cli/registry.js +2 -4
  28. package/dist/cli/square-commands.d.ts +34 -0
  29. package/dist/cli/square-commands.js +90 -84
  30. package/dist/cmd/notify-once.d.ts +2 -0
  31. package/dist/codex-hook.d.ts +10 -0
  32. package/dist/codex-hook.js +35 -3
  33. package/dist/decisions.d.ts +92 -0
  34. package/dist/decisions.js +93 -81
  35. package/dist/delivery-health.d.ts +21 -0
  36. package/dist/delivery-health.js +41 -34
  37. package/dist/delivery.d.ts +81 -0
  38. package/dist/delivery.js +71 -69
  39. package/dist/file-lock.d.ts +6 -0
  40. package/dist/harness-claude.d.ts +19 -0
  41. package/dist/harness-claude.js +8 -3
  42. package/dist/harness-codex.d.ts +29 -0
  43. package/dist/harness-codex.js +2 -2
  44. package/dist/harness-links.d.ts +17 -0
  45. package/dist/harness-links.js +2 -2
  46. package/dist/harness-pi.d.ts +11 -0
  47. package/dist/harness-pi.js +2 -2
  48. package/dist/harness-stage.d.ts +6 -0
  49. package/dist/harness.d.ts +20 -0
  50. package/dist/harness.js +5 -3
  51. package/dist/help.d.ts +6 -0
  52. package/dist/help.js +12 -13
  53. package/dist/identity.d.ts +13 -0
  54. package/dist/inbox.d.ts +2 -0
  55. package/dist/inbox.js +15 -19
  56. package/dist/index.d.ts +13 -0
  57. package/dist/index.js +31 -67
  58. package/dist/landing.d.ts +10 -0
  59. package/dist/landing.js +95 -0
  60. package/dist/list.d.ts +1 -0
  61. package/dist/list.js +39 -61
  62. package/dist/model.d.ts +147 -0
  63. package/dist/model.js +12 -8
  64. package/dist/notifications.d.ts +30 -0
  65. package/dist/notifications.js +83 -91
  66. package/dist/open-square.d.ts +11 -0
  67. package/dist/open-square.js +4 -0
  68. package/dist/participant-identity.d.ts +1 -0
  69. package/dist/participant-identity.js +3 -0
  70. package/dist/paseo-connection.d.ts +15 -0
  71. package/dist/paseo-delivery.d.ts +14 -0
  72. package/dist/paseo-state.d.ts +11 -0
  73. package/dist/paseo-timeline.d.ts +16 -0
  74. package/dist/presence.d.ts +3 -0
  75. package/dist/presence.js +42 -0
  76. package/dist/presentation.d.ts +91 -0
  77. package/dist/presentation.js +52 -63
  78. package/dist/presented.d.ts +11 -0
  79. package/dist/registry.d.ts +58 -0
  80. package/dist/registry.js +36 -3
  81. package/dist/routes.d.ts +42 -0
  82. package/dist/runtime.d.ts +45 -0
  83. package/dist/runtime.js +48 -55
  84. package/dist/search.d.ts +11 -0
  85. package/dist/square-core.d.ts +111 -0
  86. package/dist/square-core.js +63 -9
  87. package/dist/square-facade.d.ts +86 -0
  88. package/dist/square-facade.js +1 -0
  89. package/dist/square-file-adapter.d.ts +18 -0
  90. package/dist/square-file-adapter.js +70 -0
  91. package/dist/square-storage.d.ts +20 -0
  92. package/dist/square-storage.js +171 -0
  93. package/dist/square-wiring.d.ts +19 -0
  94. package/dist/square-wiring.js +70 -0
  95. package/dist/square.d.ts +2 -0
  96. package/dist/state-cell.d.ts +14 -0
  97. package/dist/state-cell.js +1 -0
  98. package/dist/stream.d.ts +3 -0
  99. package/dist/stream.js +19 -23
  100. package/dist/time.d.ts +6 -0
  101. package/dist/views.d.ts +95 -0
  102. package/dist/views.js +81 -0
  103. package/dist/wake-attempts.d.ts +39 -0
  104. package/dist/wake-attempts.js +8 -4
  105. package/dist/wake-evidence.d.ts +12 -0
  106. package/dist/wake-evidence.js +7 -10
  107. package/dist/wake-port.d.ts +20 -0
  108. package/dist/wake-sink.d.ts +12 -0
  109. package/dist/wakes.d.ts +40 -0
  110. package/dist/wakes.js +62 -0
  111. package/dist/watch.d.ts +2 -0
  112. package/dist/watch.js +89 -210
  113. package/extensions/square-opencode.js +27 -1
  114. package/extensions/square-pi.js +13 -0
  115. package/guides/participant.md +7 -174
  116. package/package.json +5 -4
  117. package/skills/brainstorm/SKILL.md +30 -30
  118. package/skills/square/.claude-plugin/plugin.json +1 -1
  119. package/skills/square/SKILL.md +56 -29
  120. package/skills/square/hooks/hooks.json +9 -0
  121. package/skills/square-feedback/SKILL.md +9 -9
  122. package/dist/compact.js +0 -26
  123. package/dist/doctor.js +0 -36
  124. package/dist/square-application.js +0 -262
  125. package/template.md +0 -4
  126. package/templates/architect.md +0 -4
  127. package/templates/brainstorm.md +0 -4
@@ -1,21 +1,22 @@
1
- import { loadSquare } from '../artifact.js';
2
- import { runClaudeHook } from '../claude-hook.js';
3
- import { runCodexHook } from '../codex-hook.js';
4
- import { coreActivities, coreParticipants, coreStatus } from '../decisions.js';
1
+ import { runClaudeHookAsync } from '../claude-hook.js';
2
+ import { runCodexHookAsync } from '../codex-hook.js';
5
3
  import { sessionInbox } from '../inbox.js';
6
4
  import { sweepPendingNotifications } from '../notifications.js';
7
5
  import { cmdListSquares } from '../list.js';
8
- import { sameName } from '../model.js';
9
- import { commandPrefix, participantCommandPrefix, renderActivitiesView, renderGrepActivitiesView, renderVisibleEvent, withPathOutput, } from '../presentation.js';
10
- import { actId, inSquareCount, nowMs, sayNumberFor } from '../runtime.js';
6
+ import { parseActivityId, sameName } from '../model.js';
7
+ import { commandPrefix, participantCommandPrefix, participantIdentity, renderGrepActivitiesView, renderEventCli, renderAmbientEvent, withPathOutput, } from '../presentation.js';
8
+ import { actId, nowMs } from '../runtime.js';
11
9
  import { cmdStream, cmdStreamNdjson } from '../stream.js';
12
10
  import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
13
11
  import { cmdWatch } from '../watch.js';
12
+ import { openSquare } from '../square-file-adapter.js';
13
+ import { closeOpenSquare } from '../open-square.js';
14
+ import { historyPresentation, participantsPresentation, statusPresentation } from '../views.js';
14
15
  import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdinSync, requireParticipant, requireValue, usage, } from './context.js';
15
16
  export const listCommand = {
16
17
  parse: (argv) => argv,
17
- execute(argv, context) {
18
- cmdListSquares(argv, () => usage(context.command));
18
+ async execute(argv, context) {
19
+ await cmdListSquares(argv, () => usage(context.command));
19
20
  },
20
21
  present: () => { },
21
22
  };
@@ -98,10 +99,10 @@ export const catchCommand = {
98
99
  present: () => { },
99
100
  };
100
101
  function parseActRef(value, flag) {
101
- const match = value.trim().match(/^(?:act_)?(\d+)$/i);
102
- if (!match)
103
- fail(`Invalid ${flag}: expected an activity id like act_12 or 12.`);
104
- return Number(match[1]);
102
+ const index = parseActivityId(value);
103
+ if (index === undefined)
104
+ fail(`Invalid ${flag}: expected an activity id like act/12`);
105
+ return index;
105
106
  }
106
107
  function parseTimestamp(value, flag) {
107
108
  const timestamp = parseTimeOrRelative(value, nowMs());
@@ -116,7 +117,7 @@ function parseHistory(argv, context) {
116
117
  let before;
117
118
  let after;
118
119
  let afterIndex;
119
- let atIndex;
120
+ const atIndexes = [];
120
121
  let beforeContext;
121
122
  let afterContext;
122
123
  let mention;
@@ -124,10 +125,8 @@ function parseHistory(argv, context) {
124
125
  let full = false;
125
126
  let grep;
126
127
  let fixed;
127
- let ids;
128
128
  let order;
129
129
  let format;
130
- let countOnly = false;
131
130
  let json = false;
132
131
  const participants = [];
133
132
  for (let index = 0; index < argv.length; index++) {
@@ -165,7 +164,10 @@ function parseHistory(argv, context) {
165
164
  index += 1;
166
165
  }
167
166
  else if (flag === '--at') {
168
- atIndex = parseActRef(requireValue(argv, index, flag), flag);
167
+ const values = requireValue(argv, index, flag).split(',');
168
+ if (values.some((value) => value === ''))
169
+ fail(`Invalid ${flag}: expected an activity id like act/12`);
170
+ atIndexes.push(...values.map((value) => parseActRef(value, flag)));
169
171
  index += 1;
170
172
  }
171
173
  else if (flag === '-B') {
@@ -198,14 +200,6 @@ function parseHistory(argv, context) {
198
200
  fixed = requireValue(argv, index, flag);
199
201
  index += 1;
200
202
  }
201
- else if (flag === '--ids') {
202
- ids = requireValue(argv, index, flag)
203
- .split(',')
204
- .map((item) => item.trim())
205
- .filter(Boolean)
206
- .map((item) => parseActRef(item, flag));
207
- index += 1;
208
- }
209
203
  else if (flag === '--order') {
210
204
  const value = requireValue(argv, index, flag);
211
205
  if (value !== 'asc' && value !== 'desc')
@@ -217,8 +211,6 @@ function parseHistory(argv, context) {
217
211
  format = requireValue(argv, index, flag).split(',').map((item) => item.trim()).filter(Boolean);
218
212
  index += 1;
219
213
  }
220
- else if (flag === '--count')
221
- countOnly = true;
222
214
  else if (flag === '--json')
223
215
  json = true;
224
216
  else
@@ -230,7 +222,7 @@ function parseHistory(argv, context) {
230
222
  fail('--grep and --fixed cannot be combined.');
231
223
  if (grep === '' || fixed === '')
232
224
  fail('--grep and --fixed require non-empty text.');
233
- if (!lastNExplicit && (atIndex !== undefined || ids !== undefined || pending))
225
+ if (!lastNExplicit && (atIndexes.length > 0 || pending))
234
226
  lastN = null;
235
227
  return {
236
228
  lastN,
@@ -238,7 +230,7 @@ function parseHistory(argv, context) {
238
230
  before,
239
231
  after,
240
232
  afterIndex,
241
- atIndex,
233
+ atIndexes: atIndexes.length === 0 ? undefined : atIndexes,
242
234
  beforeContext,
243
235
  afterContext,
244
236
  mention,
@@ -247,14 +239,12 @@ function parseHistory(argv, context) {
247
239
  full,
248
240
  grep,
249
241
  fixed,
250
- ids,
251
242
  order,
252
243
  format,
253
- countOnly,
254
244
  json,
255
245
  };
256
246
  }
257
- function renderFields(doc, item, fields) {
247
+ function renderFields(sayNumbers, item, fields) {
258
248
  return fields.map((field) => {
259
249
  switch (field) {
260
250
  case 'id': return actId(item.index);
@@ -264,13 +254,13 @@ function renderFields(doc, item, fields) {
264
254
  case 'at': return formatTimestamp(item.at);
265
255
  case 'kind': return item.kind;
266
256
  case 'body': return 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
267
- case 'number': return item.kind === 'say' ? String(sayNumberFor(doc.acts, item)) : '';
257
+ case 'number': return item.kind === 'say' ? String(sayNumbers[item.index]) : '';
268
258
  case 'reply': return item.kind === 'say' && item.reply !== undefined ? actId(item.reply) : '';
269
259
  default: return '';
270
260
  }
271
261
  }).join('\t');
272
262
  }
273
- function jsonLine(doc, item) {
263
+ function jsonLine(sayNumbers, item) {
274
264
  const act = item;
275
265
  return JSON.stringify({
276
266
  id: actId(item.index),
@@ -280,130 +270,160 @@ function jsonLine(doc, item) {
280
270
  at: act.at,
281
271
  ts: formatTimestamp(act.at),
282
272
  body: 'body' in act && typeof act.body === 'string' ? act.body : '',
283
- number: act.kind === 'say' ? sayNumberFor(doc.acts, act) : null,
273
+ number: act.kind === 'say' ? sayNumbers[act.index] : null,
284
274
  reach: act.kind === 'say' ? act.reach ?? null : null,
285
275
  reply: act.kind === 'say' && act.reply !== undefined ? actId(act.reply) : null,
286
276
  });
287
277
  }
278
+ function renderHistoryProjection(projection, visible, full, squarePath, viewer, mode) {
279
+ const shown = visible.filter((activity) => activity.kind === 'say' || activity.kind === 'done');
280
+ const preview = full ? undefined : 200;
281
+ const chunks = [];
282
+ for (const activity of shown) {
283
+ const options = {
284
+ preview,
285
+ actNumber: activity.kind === 'say' ? projection.sayNumbers[activity.index] : undefined,
286
+ };
287
+ const rendered = mode === 'archive'
288
+ ? renderEventCli(activity, options)
289
+ : renderAmbientEvent(activity, viewer, options);
290
+ if (rendered !== '')
291
+ chunks.push(rendered);
292
+ for (const name of projection.presenceAnchors[activity.index] ?? [])
293
+ chunks.push(`→ ${participantIdentity(name)} was here`);
294
+ }
295
+ if (chunks.length === 0)
296
+ return 'latest\n ○ no public activity in this view';
297
+ if (preview !== undefined && shown.some((activity) => activity.kind === 'say' && activity.body.length > preview && (mode === 'archive' || activity.perception === 'full'))) {
298
+ chunks.push(`» ${commandPrefix(squarePath)} history --full`);
299
+ }
300
+ return chunks.join('\n\n');
301
+ }
288
302
  export const historyCommand = {
289
303
  parse(argv, context) { return parseHistory(argv, context); },
290
- execute(options, context) {
291
- const doc = loadSquare(context.squarePath);
292
- let events = coreActivities(doc, options);
293
- const searching = options.grep !== undefined || options.fixed !== undefined;
294
- const totalMatches = searching ? events.length : 0;
295
- if (options.lastN != null) {
296
- events = options.order === 'desc'
297
- ? events.slice(0, options.lastN)
298
- : events.slice(-options.lastN);
304
+ async execute(options, context) {
305
+ const square = await openSquare(context.squarePath, { clock: nowMs });
306
+ try {
307
+ const projection = await historyPresentation(square, options);
308
+ let events = [...projection.activities];
309
+ const searching = options.grep !== undefined || options.fixed !== undefined;
310
+ const totalMatches = searching ? events.length : 0;
311
+ if (options.lastN != null) {
312
+ events = options.order === 'desc'
313
+ ? events.slice(0, options.lastN)
314
+ : events.slice(-options.lastN);
315
+ }
316
+ if (options.json)
317
+ return events.map((item) => jsonLine(projection.sayNumbers, item)).join('\n') + (events.length > 0 ? '\n' : '');
318
+ if (options.format !== undefined && options.format.length > 0) {
319
+ return events.map((item) => renderFields(projection.sayNumbers, item, options.format)).join('\n') + (events.length > 0 ? '\n' : '');
320
+ }
321
+ const pattern = options.grep ?? options.fixed;
322
+ const anonymous = options.viewer === undefined;
323
+ const archive = (options.atIndexes !== undefined && options.atIndexes.length > 0)
324
+ || (options.lastN == null && options.full === true)
325
+ || anonymous;
326
+ const output = pattern === undefined || pattern === ''
327
+ ? renderHistoryProjection(projection, events, options.full === true || anonymous, context.squarePath, options.viewer ?? '', archive ? 'archive' : 'ambient')
328
+ : renderGrepActivitiesView(events, totalMatches, options.full, context.squarePath, pattern, options.fixed !== undefined);
329
+ return withPathOutput(context.squarePath, output, { participantCount: projection.participantCount });
299
330
  }
300
- if (options.countOnly)
301
- return `${searching ? totalMatches : events.length}\n`;
302
- if (options.json)
303
- return events.map((item) => jsonLine(doc, item)).join('\n') + (events.length > 0 ? '\n' : '');
304
- if (options.format !== undefined && options.format.length > 0) {
305
- return events.map((item) => renderFields(doc, item, options.format)).join('\n') + (events.length > 0 ? '\n' : '');
331
+ finally {
332
+ await closeOpenSquare(square);
306
333
  }
307
- const pattern = options.grep ?? options.fixed;
308
- const output = pattern === undefined || pattern === ''
309
- ? renderActivitiesView(doc, events, null, options.full, context.squarePath, options.viewer ?? '')
310
- : renderGrepActivitiesView(events, totalMatches, options.full, context.squarePath, pattern, options.fixed !== undefined);
311
- return withPathOutput(context.squarePath, output, { participantCount: inSquareCount(doc) });
312
- },
313
- present: (result) => process.stdout.write(result),
314
- };
315
- export const warmupCommand = {
316
- parse(argv, context) { if (argv.length > 0)
317
- usage(context.command); return undefined; },
318
- execute(_intent, context) {
319
- const doc = loadSquare(context.squarePath);
320
- return withPathOutput(context.squarePath, doc.warmup.join('\n'), {
321
- participantCount: inSquareCount(doc),
322
- });
323
334
  },
324
335
  present: (result) => process.stdout.write(result),
325
336
  };
326
337
  export const participantsCommand = {
327
338
  parse(argv, context) { if (argv.length > 0)
328
339
  usage(context.command); return undefined; },
329
- execute(_intent, context) {
330
- const doc = loadSquare(context.squarePath);
331
- const now = nowMs();
332
- const participants = coreParticipants(doc, now);
333
- const lines = participants.map((participant) => {
334
- const glyph = participant.state === 'done' ? '×' : participant.presence === 'watching' ? '◎' : participant.activityCount > 0 ? '●' : '○';
335
- const state = participant.state === 'done' ? 'done' : participant.presence === 'watching' ? 'catching' : participant.state;
336
- const last = participant.lastActiveAt === undefined ? '' : formatRelativeTime(participant.lastActiveAt, now);
337
- return ` ${glyph} ${participant.name} · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`;
338
- });
339
- const participantCount = participants.filter((participant) => participant.state === 'active').length;
340
- return withPathOutput(context.squarePath, ['participants', ...lines].join('\n'), {
341
- participantCount,
342
- });
340
+ async execute(_intent, context) {
341
+ const square = await openSquare(context.squarePath, { clock: nowMs });
342
+ try {
343
+ const now = nowMs();
344
+ const participants = await participantsPresentation(square);
345
+ const lines = participants.map((participant) => {
346
+ const glyph = participant.state === 'done' ? '' : participant.presence === 'watching' ? '' : participant.activityCount > 0 ? '●' : '○';
347
+ const state = participant.state === 'done' ? 'done' : participant.presence === 'watching' ? 'catching' : participant.state;
348
+ const last = participant.lastActiveAt === undefined ? '' : formatRelativeTime(participant.lastActiveAt, now);
349
+ return ` ${glyph} ${participantIdentity(participant.name)} · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`;
350
+ });
351
+ const participantCount = participants.filter((participant) => participant.state === 'active').length;
352
+ return withPathOutput(context.squarePath, ['participants', ...lines].join('\n'), {
353
+ participantCount,
354
+ });
355
+ }
356
+ finally {
357
+ await closeOpenSquare(square);
358
+ }
343
359
  },
344
360
  present: (result) => process.stdout.write(result),
345
361
  };
346
362
  export const statusCommand = {
347
363
  parse(argv, context) { if (argv.length > 0)
348
364
  usage(context.command); return undefined; },
349
- execute(_intent, context) {
350
- const doc = loadSquare(context.squarePath);
351
- const result = coreStatus(doc, nowMs());
352
- const active = result.participants.filter((participant) => participant.state === 'active').sort((a, b) => {
353
- const aViewer = context.name !== undefined && sameName(a.name, context.name);
354
- const bViewer = context.name !== undefined && sameName(b.name, context.name);
355
- if (aViewer !== bViewer)
356
- return aViewer ? -1 : 1;
357
- return (b.lastActiveAt ?? -Infinity) - (a.lastActiveAt ?? -Infinity) || a.name.localeCompare(b.name);
358
- });
359
- const people = active.length === 0 ? [' ○ nobody in the square'] : active.map((participant) => {
360
- const glyph = participant.presence === 'watching'
361
- ? ''
362
- : participant.activityCount > 0 ? '' : '○';
363
- const summary = participant.activityCount > 0
364
- ? `${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${participant.lastActiveAt === undefined
365
+ async execute(_intent, context) {
366
+ const square = await openSquare(context.squarePath, { clock: nowMs });
367
+ try {
368
+ const presentation = await statusPresentation(square);
369
+ const result = presentation.status;
370
+ const active = result.participants.filter((participant) => participant.state === 'active').sort((a, b) => {
371
+ const aViewer = context.name !== undefined && sameName(a.name, context.name);
372
+ const bViewer = context.name !== undefined && sameName(b.name, context.name);
373
+ if (aViewer !== bViewer)
374
+ return aViewer ? -1 : 1;
375
+ return (b.lastActiveAt ?? -Infinity) - (a.lastActiveAt ?? -Infinity) || a.name.localeCompare(b.name);
376
+ });
377
+ const people = active.length === 0 ? [' ○ nobody in the square'] : active.map((participant) => {
378
+ const glyph = participant.presence === 'watching'
379
+ ? '◎'
380
+ : participant.activityCount > 0 ? '' : '';
381
+ const summary = participant.activityCount > 0
382
+ ? `${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${participant.lastActiveAt === undefined
383
+ ? 'just now'
384
+ : formatRelativeTime(participant.lastActiveAt, result.now)}`
385
+ : `quiet · ${participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, result.now)}`;
386
+ const showAttention = context.name === undefined || sameName(participant.name, context.name);
387
+ const attention = !showAttention
388
+ ? ''
389
+ : participant.pendingMentionCount > 0
390
+ ? `${participant.pendingMentionCount} mention${participant.pendingMentionCount === 1 ? '' : 's'} waiting`
391
+ : participant.unreadActivityCount > 0
392
+ ? `${participant.unreadActivityCount} change${participant.unreadActivityCount === 1 ? '' : 's'} waiting`
393
+ : 'caught up';
394
+ return ` ${glyph} ${participantIdentity(participant.name)} · ${summary}${attention === '' ? '' : ` · ${attention}`}`;
395
+ });
396
+ const cap = result.hardCap === null ? 'unlimited' : String(result.hardCap);
397
+ const hold = result.holdActive
398
+ ? `· ${result.holdActor === undefined ? 'someone' : participantIdentity(result.holdActor)} raised a hand${result.holdReason ? ` — ${result.holdReason}` : ''} · ${result.holdAt === undefined
365
399
  ? 'just now'
366
- : formatRelativeTime(participant.lastActiveAt, result.now)}`
367
- : `quiet · ${participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, result.now)}`;
368
- const showAttention = context.name === undefined || sameName(participant.name, context.name);
369
- const attention = !showAttention
400
+ : formatRelativeTime(result.holdAt, result.now)}`
401
+ : undefined;
402
+ const visible = result.latestAct === undefined
370
403
  ? ''
371
- : participant.pendingMentionCount > 0
372
- ? `${participant.pendingMentionCount} mention${participant.pendingMentionCount === 1 ? '' : 's'} waiting`
373
- : participant.unreadActivityCount > 0
374
- ? `${participant.unreadActivityCount} change${participant.unreadActivityCount === 1 ? '' : 's'} waiting`
375
- : 'caught up';
376
- return ` ${glyph} ${participant.name} · ${summary}${attention === '' ? '' : ` · ${attention}`}`;
377
- });
378
- const cap = result.hardCap === null ? 'unlimited' : String(result.hardCap);
379
- const hold = result.holdActive
380
- ? ${result.holdActor ?? 'someone'} raised a hand${result.holdReason ? ` — ${result.holdReason}` : ''} · ${result.holdAt === undefined
381
- ? 'just now'
382
- : formatRelativeTime(result.holdAt, result.now)}`
383
- : undefined;
384
- const visible = result.latestAct === undefined
385
- ? ''
386
- : renderVisibleEvent(doc.acts, result.latestAct, context.name ?? '', {
387
- now: result.now,
388
- preview: 200,
389
- actNumber: result.latestAct.kind === 'say'
390
- ? sayNumberFor(doc.acts, result.latestAct)
391
- : undefined,
392
- });
393
- const latest = visible === ''
394
- ? [result.latestAct === undefined
395
- ? ' ○ no public activity yet'
396
- : ' · latest activity is private to another participant']
397
- : [` ${visible.replace(/\n/g, '\n ')}`];
398
- if (visible.includes('more chars') && result.latestAct !== undefined) {
399
- const prefix = context.name === undefined ? commandPrefix(context.squarePath) : participantCommandPrefix(context.squarePath, context.name);
400
- latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --full`);
404
+ : renderAmbientEvent(result.latestAct, context.name ?? '', {
405
+ now: result.now,
406
+ preview: 200,
407
+ actNumber: presentation.latestActNumber,
408
+ });
409
+ const latest = visible === ''
410
+ ? [result.latestAct === undefined
411
+ ? ' ○ no public activity yet'
412
+ : ' · latest activity is private to another participant']
413
+ : [` ${visible.replace(/\n/g, '\n ')}`];
414
+ if (visible.includes('more chars') && result.latestAct !== undefined) {
415
+ const prefix = context.name === undefined ? commandPrefix(context.squarePath) : participantCommandPrefix(context.squarePath, context.name);
416
+ latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --full`);
417
+ }
418
+ const output = [
419
+ `${result.activeCount} active · ${result.doneCount} done · cap ${cap} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`,
420
+ ...(hold === undefined ? [] : ['', hold]), '', 'around the square', ...people, '', 'latest', ...latest,
421
+ ].join('\n');
422
+ return withPathOutput(context.squarePath, output, { participantCount: result.activeCount, held: result.holdActive });
423
+ }
424
+ finally {
425
+ await closeOpenSquare(square);
401
426
  }
402
- const output = [
403
- `${result.activeCount} active · ${result.doneCount} done · cap ${cap} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`,
404
- ...(hold === undefined ? [] : ['', hold]), '', 'around the square', ...people, '', 'latest', ...latest,
405
- ].join('\n');
406
- return withPathOutput(context.squarePath, output, { participantCount: result.activeCount, held: result.holdActive });
407
427
  },
408
428
  present: (result) => process.stdout.write(result),
409
429
  };
@@ -425,8 +445,8 @@ export const inboxCommand = {
425
445
  fail('inbox requires --for-session <session-id>.');
426
446
  return { sessionId, json };
427
447
  },
428
- execute(intent) {
429
- const inbox = sessionInbox(intent.sessionId);
448
+ async execute(intent) {
449
+ const inbox = await sessionInbox(intent.sessionId);
430
450
  return intent.json ? `${JSON.stringify(inbox)}\n` : inbox.map((membership) => `${membership.name}\t${membership.squarePath}\t${membership.notifications.length}\n`).join('');
431
451
  },
432
452
  present: (result) => process.stdout.write(result),
@@ -439,5 +459,5 @@ function hookCommand(runHook) {
439
459
  present: (result) => process.stdout.write(result),
440
460
  };
441
461
  }
442
- export const claudeHookCommand = hookCommand(runClaudeHook);
443
- export const codexHookCommand = hookCommand(runCodexHook);
462
+ export const claudeHookCommand = hookCommand(runClaudeHookAsync);
463
+ export const codexHookCommand = hookCommand(runCodexHookAsync);
@@ -0,0 +1,2 @@
1
+ /** Parse global flags, select an executable adapter, and leave all command work to the registry. */
2
+ export declare function runCli(rawArgs?: string[]): Promise<void>;
@@ -1,14 +1,9 @@
1
1
  import { helpRequest } from '../help.js';
2
- import { SquareError } from '../model.js';
2
+ import { isSquareError } from '../model.js';
3
3
  import { defaultContext, parseGlobalArgs } from './context.js';
4
4
  import { executeRegisteredCommand, findCommand } from './registry.js';
5
- function isMutatingCommand(command, argv) {
6
- if (['build', 'join', 'catch', 'express', 'done', 'hold', 'resume', 'compact'].includes(command))
7
- return true;
8
- return command === 'doctor' && argv.includes('--fix');
9
- }
10
5
  function handleSquareError(error) {
11
- if (error instanceof SquareError) {
6
+ if (isSquareError(error)) {
12
7
  process.stderr.write(`${error.message}\n`);
13
8
  process.exit(error.code === 'not_found' ? 1 : 2);
14
9
  }
@@ -19,7 +14,7 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
19
14
  try {
20
15
  const requestedHelp = helpRequest(rawArgs);
21
16
  if (requestedHelp !== undefined) {
22
- await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help', '.square/SQUARE.md'));
17
+ await executeRegisteredCommand('help', requestedHelp.command === undefined ? [] : [requestedHelp.command], defaultContext('help', '.square/SQUARE.square'));
23
18
  return;
24
19
  }
25
20
  const parsed = parseGlobalArgs(rawArgs);
@@ -32,10 +27,6 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
32
27
  process.stderr.write(`unknown command: ${command}\nrun 'square' for usage\n`);
33
28
  process.exit(2);
34
29
  }
35
- if (!parsed.explicitSquarePath && parsed.multipleSquares && isMutatingCommand(command, parsed.args.slice(1))) {
36
- process.stderr.write('✕ more than one square is active here; choose the path before changing or consuming activity.\n» square list\n');
37
- process.exit(2);
38
- }
39
30
  await executeRegisteredCommand(command, parsed.args.slice(1), defaultContext(command, parsed.squarePath, parsed.name));
40
31
  }
41
32
  catch (error) {
@@ -0,0 +1,9 @@
1
+ import { type CommandContext, type CommandSpec } from './context.js';
2
+ export interface RegisteredCommand {
3
+ names: readonly string[];
4
+ spec: CommandSpec<any, any>;
5
+ }
6
+ /** Every public command is an executable adapter, including aliases and utility commands. */
7
+ export declare const commandRegistry: readonly RegisteredCommand[];
8
+ export declare function findCommand(name: string): RegisteredCommand | undefined;
9
+ export declare function executeRegisteredCommand(name: string, argv: string[], context: CommandContext): Promise<void>;
@@ -1,8 +1,8 @@
1
- import { buildCommand, compactCommand, doneCommand, expressCommand, holdCommand, joinCommand, resumeCommand } from './square-commands.js';
1
+ import { buildCommand, doneCommand, expressCommand, holdCommand, joinCommand, resumeCommand } from './square-commands.js';
2
2
  import { doctorCommand } from './maintenance-commands.js';
3
3
  import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
4
4
  import { helpCommand, versionCommand } from './meta-commands.js';
5
- import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, warmupCommand, } from './observation-commands.js';
5
+ import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, } from './observation-commands.js';
6
6
  /** Every public command is an executable adapter, including aliases and utility commands. */
7
7
  export const commandRegistry = [
8
8
  { names: ['build'], spec: buildCommand },
@@ -20,10 +20,8 @@ export const commandRegistry = [
20
20
  { names: ['install'], spec: installCommand },
21
21
  { names: ['uninstall'], spec: uninstallCommand },
22
22
  { names: ['harness'], spec: harnessCommand },
23
- { names: ['compact'], spec: compactCommand },
24
23
  { names: ['doctor'], spec: doctorCommand },
25
24
  { names: ['history'], spec: historyCommand },
26
- { names: ['warmup'], spec: warmupCommand },
27
25
  { names: ['status'], spec: statusCommand },
28
26
  { names: ['participants'], spec: participantsCommand },
29
27
  { names: ['help'], spec: helpCommand },
@@ -0,0 +1,34 @@
1
+ import { type BuildOptions, type HardCap, type Reach } from '../model.js';
2
+ import { type CommandSpec } from './context.js';
3
+ interface BuildIntent {
4
+ options: BuildOptions & {
5
+ hardCap: HardCap;
6
+ };
7
+ snippet: string;
8
+ }
9
+ interface JoinIntent {
10
+ name: string;
11
+ lastN: number | null;
12
+ kick: boolean;
13
+ }
14
+ interface ActivityIntent {
15
+ name: string;
16
+ activity: string;
17
+ force: boolean;
18
+ noWait: boolean;
19
+ reach?: Reach;
20
+ reply?: number;
21
+ }
22
+ interface BodyIntent {
23
+ name: string;
24
+ body?: string;
25
+ }
26
+ export declare const buildCommand: CommandSpec<BuildIntent, string>;
27
+ export declare const joinCommand: CommandSpec<JoinIntent, string>;
28
+ export declare const expressCommand: CommandSpec<ActivityIntent>;
29
+ export declare const doneCommand: CommandSpec<BodyIntent, string>;
30
+ export declare const holdCommand: CommandSpec<BodyIntent, string>;
31
+ export declare const resumeCommand: CommandSpec<{
32
+ name: string;
33
+ }, string>;
34
+ export {};