@astrosheep/square 0.3.70 → 0.3.72

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 (126) hide show
  1. package/dist/activity-feed.d.ts +4 -5
  2. package/dist/activity-feed.js +14 -10
  3. package/dist/artifact.d.ts +26 -33
  4. package/dist/artifact.js +346 -317
  5. package/dist/attention-presentation.js +2 -9
  6. package/dist/automatic-session.js +6 -6
  7. package/dist/boundary-presentation.js +3 -3
  8. package/dist/catch-decisions.d.ts +19 -12
  9. package/dist/catch-decisions.js +32 -68
  10. package/dist/claude-delivery.js +7 -7
  11. package/dist/claude-mod.js +18 -7
  12. package/dist/cli/catch-presentation.d.ts +7 -0
  13. package/dist/cli/catch-presentation.js +55 -0
  14. package/dist/cli/context.d.ts +1 -1
  15. package/dist/cli/context.js +5 -23
  16. package/dist/cli/express-presentation.d.ts +10 -0
  17. package/dist/cli/express-presentation.js +196 -0
  18. package/dist/cli/maintenance-commands.d.ts +4 -0
  19. package/dist/cli/maintenance-commands.js +29 -6
  20. package/dist/cli/observation-commands.d.ts +7 -5
  21. package/dist/cli/observation-commands.js +275 -262
  22. package/dist/cli/program.d.ts +1 -1
  23. package/dist/cli/program.js +43 -56
  24. package/dist/cli/registry.js +2 -1
  25. package/dist/cli/square-commands.d.ts +3 -9
  26. package/dist/cli/square-commands.js +134 -255
  27. package/dist/codex-queue.js +4 -1
  28. package/dist/decisions.d.ts +26 -74
  29. package/dist/decisions.js +190 -335
  30. package/dist/delivery-health.js +13 -22
  31. package/dist/delivery-operations.d.ts +0 -11
  32. package/dist/delivery-operations.js +44 -33
  33. package/dist/delivery.d.ts +33 -32
  34. package/dist/delivery.js +70 -81
  35. package/dist/help.js +6 -1
  36. package/dist/inbox.d.ts +27 -0
  37. package/dist/inbox.js +55 -18
  38. package/dist/index.d.ts +2 -4
  39. package/dist/index.js +1 -1
  40. package/dist/list.d.ts +7 -1
  41. package/dist/list.js +49 -122
  42. package/dist/mcp.d.ts +1 -1
  43. package/dist/mcp.js +7 -37
  44. package/dist/model.d.ts +4 -44
  45. package/dist/model.js +1 -3
  46. package/dist/notifications.d.ts +1 -1
  47. package/dist/notifications.js +13 -7
  48. package/dist/open-square.d.ts +16 -2
  49. package/dist/open-square.js +40 -1
  50. package/dist/packages/agent-delivery/src/claude.d.ts +2 -11
  51. package/dist/packages/agent-delivery/src/index.d.ts +2 -42
  52. package/dist/packages/agent-delivery/src/opencode.d.ts +2 -11
  53. package/dist/packages/agent-delivery/src/paseo.d.ts +2 -13
  54. package/dist/packages/agent-delivery/src/pi-client.d.ts +2 -9
  55. package/dist/packages/agent-delivery/src/types.d.ts +42 -0
  56. package/dist/participant-host.d.ts +6 -3
  57. package/dist/participant-host.js +44 -38
  58. package/dist/perception-projection.d.ts +6 -15
  59. package/dist/perception-projection.js +6 -26
  60. package/dist/plugins/opencode/delivery.js +9 -9
  61. package/dist/plugins/opencode/index.js +3 -3
  62. package/dist/ports.d.ts +156 -11
  63. package/dist/presentation-operations.d.ts +1 -1
  64. package/dist/presentation-operations.js +35 -44
  65. package/dist/presentation.d.ts +12 -19
  66. package/dist/presentation.js +33 -54
  67. package/dist/registry.d.ts +2 -0
  68. package/dist/registry.js +1 -1
  69. package/dist/routes.d.ts +7 -14
  70. package/dist/routes.js +34 -72
  71. package/dist/runtime.d.ts +12 -39
  72. package/dist/runtime.js +33 -164
  73. package/dist/square-actions.d.ts +11 -21
  74. package/dist/square-actions.js +171 -275
  75. package/dist/square-application.d.ts +31 -55
  76. package/dist/square-application.js +625 -141
  77. package/dist/square-core.d.ts +3 -94
  78. package/dist/square-core.js +8 -353
  79. package/dist/square-domain.d.ts +24 -0
  80. package/dist/square-domain.js +77 -0
  81. package/dist/square-facade.d.ts +360 -9
  82. package/dist/square-migration.d.ts +24 -0
  83. package/dist/square-migration.js +211 -0
  84. package/dist/square-projections.d.ts +2 -42
  85. package/dist/square-projections.js +1 -37
  86. package/dist/square-sqlite.d.ts +59 -0
  87. package/dist/square-sqlite.js +702 -0
  88. package/dist/stream.d.ts +3 -10
  89. package/dist/stream.js +81 -52
  90. package/dist/views.d.ts +27 -95
  91. package/dist/views.js +217 -87
  92. package/dist/wake-eligibility.d.ts +15 -5
  93. package/dist/wake-eligibility.js +32 -16
  94. package/dist/wakes.d.ts +3 -3
  95. package/dist/wakes.js +31 -27
  96. package/guides/application-facade-delivery-evidence.md +200 -0
  97. package/guides/application-facade-design.md +95 -0
  98. package/guides/change-waits.md +4 -4
  99. package/guides/migration.md +77 -0
  100. package/guides/postcommit-simplification-performance.md +36 -0
  101. package/guides/primitive-catch-consolidation.md +85 -0
  102. package/guides/primitive-history-consolidation.md +76 -0
  103. package/guides/primitive-status-consolidation.md +87 -0
  104. package/guides/primitive-storage-consolidation.md +128 -0
  105. package/guides/row-runtime-delivery-evidence.md +160 -0
  106. package/guides/row-storage-design.md +567 -0
  107. package/package.json +1 -1
  108. package/plugins/claude/.claude-plugin/plugin.json +1 -1
  109. package/plugins/codex/.codex-plugin/plugin.json +1 -1
  110. package/plugins/opencode/delivery.ts +11 -11
  111. package/plugins/opencode/index.ts +3 -3
  112. package/plugins/paseo/client/square-message.tsx +77 -64
  113. package/plugins/paseo/shared/message.ts +16 -0
  114. package/plugins/pi/square-pi.js +5 -1
  115. package/dist/activity.d.ts +0 -14
  116. package/dist/activity.js +0 -248
  117. package/dist/square-file-adapter.d.ts +0 -26
  118. package/dist/square-file-adapter.js +0 -85
  119. package/dist/square-state.d.ts +0 -6
  120. package/dist/square-state.js +0 -138
  121. package/dist/square-storage.d.ts +0 -14
  122. package/dist/square-storage.js +0 -188
  123. package/dist/square-wiring.d.ts +0 -33
  124. package/dist/square-wiring.js +0 -109
  125. package/dist/watch.d.ts +0 -8
  126. package/dist/watch.js +0 -252
@@ -1,2 +1,2 @@
1
- /** Parse global flags, select an executable adapter, and leave all command work to the registry. */
1
+ /** Parse global flags, select an executable adapter, and leave operations to the application. */
2
2
  export declare function runCli(rawArgs?: string[]): Promise<void>;
@@ -1,58 +1,46 @@
1
1
  import { helpRequest } from '../help.js';
2
2
  import { isSquareError } from '../model.js';
3
- import { commandPrefix, formatRefusal, joinRecoveryCommand, participantsRecoveryCommand, takeoverRecoveryLines } from '../presentation.js';
4
- import { inSquareCount, nowMs } from '../runtime.js';
5
- import { openSquare } from '../square-file-adapter.js';
6
- import { closeOpenSquare } from '../open-square.js';
7
- import { defaultContext, parseGlobalArgs } from './context.js';
3
+ import { commandPrefix, formatRefusal, refusalText, joinRecoveryCommand, participantIdentity, participantsRecoveryCommand, takeoverRecoveryLines, } from '../presentation.js';
4
+ import { applicationFor, defaultContext, parseGlobalArgs } from './context.js';
8
5
  import { executeRegisteredCommand, findCommand } from './registry.js';
9
- async function handleSquareError(error, squarePath, name) {
10
- if (isSquareError(error)) {
11
- const existing = [error.message];
12
- if (squarePath !== undefined && name !== undefined && error.code === 'already_joined') {
13
- existing.push(...takeoverRecoveryLines(squarePath, name));
14
- }
15
- else if (squarePath !== undefined && error.facts?.reason === 'never_joined') {
16
- // The caller's name has never joined this square; only a join admits it.
17
- if (name !== undefined)
18
- existing.push(joinRecoveryCommand(squarePath, name));
19
- else
20
- existing.push(participantsRecoveryCommand(squarePath));
21
- }
22
- else if (squarePath !== undefined && error.facts?.reason === 'not_standing') {
23
- existing.push(participantsRecoveryCommand(squarePath));
24
- }
25
- else if (squarePath !== undefined && error.facts?.reason === 'not_addressed' && typeof error.facts.activityId === 'string') {
26
- // An anchor only works for an activity the artifact actually holds.
27
- existing.push(error.facts.activityExists === true
28
- ? `${commandPrefix(squarePath)} history --at ${error.facts.activityId} -C 2`
29
- : `${commandPrefix(squarePath)} history --limit 10`);
30
- }
31
- if (squarePath === undefined) {
32
- process.stderr.write(`${existing.join('\n')}\n`);
33
- }
34
- else {
35
- let participantCount;
36
- try {
37
- const square = await openSquare(squarePath, { clock: nowMs });
38
- try {
39
- participantCount = inSquareCount((await square.artifact.read()).state);
40
- }
41
- finally {
42
- await closeOpenSquare(square);
43
- }
44
- }
45
- catch { /* the header count is best effort */ }
46
- process.stderr.write(formatRefusal(squarePath, existing, participantCount === undefined ? {} : { participantCount }));
47
- }
48
- process.exit(error.code === 'not_found' ? 1 : 2);
6
+ async function handleSquareError(error, context) {
7
+ if (!isSquareError(error))
8
+ throw error;
9
+ const detail = error.operation;
10
+ const squarePath = context?.squarePath;
11
+ const recovery = context === undefined || squarePath === undefined ? undefined
12
+ : await applicationFor({ ...context, name: detail?.participant ?? context.name }).recovery().catch(() => undefined);
13
+ const name = detail?.participant ?? recovery?.data.participant ?? context?.name;
14
+ const existing = [refusalText(error)];
15
+ if (squarePath !== undefined && name !== undefined && error.code === 'already_joined') {
16
+ existing.splice(0, 1, `✕ ${participantIdentity(name)} shoos you out of the square`, ` · ${participantIdentity(name)} already stands here — another session holds the name`);
17
+ existing.push(...takeoverRecoveryLines(squarePath, name));
49
18
  }
50
- throw error;
19
+ else if (squarePath !== undefined && error.facts?.reason === 'never_joined') {
20
+ existing.push(detail?.subject !== 'filter' && name !== undefined
21
+ ? joinRecoveryCommand(squarePath, name) : participantsRecoveryCommand(squarePath));
22
+ }
23
+ else if (squarePath !== undefined && error.facts?.reason === 'not_standing') {
24
+ existing.push(participantsRecoveryCommand(squarePath));
25
+ }
26
+ else if (squarePath !== undefined && error.facts?.reason === 'not_addressed' && typeof error.facts.activityId === 'string') {
27
+ existing.push(error.facts.activityExists === true
28
+ ? `${commandPrefix(squarePath)} history --at ${error.facts.activityId} -C 2`
29
+ : `${commandPrefix(squarePath)} history --limit 10`);
30
+ }
31
+ if (error.facts?.reason === 'invalid_done_body')
32
+ existing.push('square done --help');
33
+ if (squarePath === undefined)
34
+ process.stderr.write(`${existing.join('\n')}\n`);
35
+ else
36
+ process.stderr.write(formatRefusal(squarePath, existing, {
37
+ participantCount: recovery?.data.standingCount, held: recovery?.data.held,
38
+ }));
39
+ process.exit(error.code === 'not_found' ? 1 : 2);
51
40
  }
52
- /** Parse global flags, select an executable adapter, and leave all command work to the registry. */
41
+ /** Parse global flags, select an executable adapter, and leave operations to the application. */
53
42
  export async function runCli(rawArgs = process.argv.slice(2)) {
54
- let squarePath;
55
- let requestedName;
43
+ let context;
56
44
  try {
57
45
  const requestedHelp = helpRequest(rawArgs);
58
46
  if (requestedHelp !== undefined) {
@@ -60,20 +48,19 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
60
48
  return;
61
49
  }
62
50
  const parsed = await parseGlobalArgs(rawArgs);
63
- squarePath = parsed.squarePath;
64
- requestedName = parsed.name;
65
- if (parsed.args.length === 0 || parsed.args[0] === '--help' || parsed.args[0] === '-h') {
66
- await executeRegisteredCommand('help', [], defaultContext('help', parsed.squarePath, parsed.name));
51
+ const command = parsed.args[0];
52
+ context = defaultContext(command ?? 'help', parsed.squarePath, parsed.name);
53
+ if (command === undefined || command === '--help' || command === '-h') {
54
+ await executeRegisteredCommand('help', [], context);
67
55
  return;
68
56
  }
69
- const command = parsed.args[0];
70
57
  if (findCommand(command) === undefined) {
71
58
  process.stderr.write(`unknown command: ${command}\nrun 'square' for usage\n`);
72
59
  process.exit(2);
73
60
  }
74
- await executeRegisteredCommand(command, parsed.args.slice(1), defaultContext(command, parsed.squarePath, parsed.name));
61
+ await executeRegisteredCommand(command, parsed.args.slice(1), context);
75
62
  }
76
63
  catch (error) {
77
- await handleSquareError(error, squarePath, requestedName);
64
+ await handleSquareError(error, context);
78
65
  }
79
66
  }
@@ -1,5 +1,5 @@
1
1
  import { buildCommand, doneCommand, expressCommand, holdCommand, ignoreCommand, joinCommand, listenCommand, listeningCommand, resumeCommand } from './square-commands.js';
2
- import { doctorCommand } from './maintenance-commands.js';
2
+ import { doctorCommand, migrateCommand } from './maintenance-commands.js';
3
3
  import { harnessCommand, installCommand, uninstallCommand } from './harness-command.js';
4
4
  import { helpCommand, versionCommand } from './meta-commands.js';
5
5
  import { mcpServerCommand } from './mcp-server-command.js';
@@ -25,6 +25,7 @@ export const commandRegistry = [
25
25
  { names: ['uninstall'], spec: uninstallCommand },
26
26
  { names: ['harness'], spec: harnessCommand },
27
27
  { names: ['doctor'], spec: doctorCommand },
28
+ { names: ['migrate'], spec: migrateCommand },
28
29
  { names: ['history'], spec: historyCommand },
29
30
  { names: ['status'], spec: statusCommand },
30
31
  { names: ['participants'], spec: participantsCommand },
@@ -1,18 +1,16 @@
1
- import { type BuildOptions, type HardCap, type Reach } from '../model.js';
1
+ import { type BuildOptions, type HardCap } from '../model.js';
2
+ import type { Reach } from '../square-facade.js';
2
3
  import { type CommandSpec } from './context.js';
3
4
  interface BuildIntent {
4
5
  options: BuildOptions & {
5
6
  hardCap: HardCap;
6
7
  };
7
- snippet: string;
8
8
  }
9
9
  interface JoinIntent {
10
- name: string;
11
10
  lastN: number;
12
11
  kick: boolean;
13
12
  }
14
13
  interface ActivityIntent {
15
- name: string;
16
14
  activity: string;
17
15
  force: boolean;
18
16
  noWait: boolean;
@@ -22,11 +20,9 @@ interface ActivityIntent {
22
20
  reply?: string;
23
21
  }
24
22
  interface BodyIntent {
25
- name: string;
26
23
  body?: string;
27
24
  }
28
25
  interface ListenerIntent {
29
- name: string;
30
26
  target?: string;
31
27
  }
32
28
  export declare const buildCommand: CommandSpec<BuildIntent, string>;
@@ -37,7 +33,5 @@ export declare const ignoreCommand: CommandSpec<ListenerIntent, string>;
37
33
  export declare const listeningCommand: CommandSpec<ListenerIntent, string>;
38
34
  export declare const doneCommand: CommandSpec<BodyIntent, string>;
39
35
  export declare const holdCommand: CommandSpec<BodyIntent, string>;
40
- export declare const resumeCommand: CommandSpec<{
41
- name: string;
42
- }, string>;
36
+ export declare const resumeCommand: CommandSpec<Record<string, never>, string>;
43
37
  export {};
@@ -1,149 +1,98 @@
1
- import { cmdActivity } from '../activity.js';
2
- import { scanMentionCandidates } from '../mention-parse.js';
3
- import { validateDoneBody } from '../decisions.js';
4
- import { formatHardCap, sameName, } from '../model.js';
5
- import { formatRefusal, participantCommandPrefix, participantIdentity, takeoverRecoveryLines, renderRoomChangeText, renderAmbientEvent, withPathOutput, } from '../presentation.js';
6
- import { hostLedgerForEnv, hasAutomaticDeliveryIdentity, } from '../registry.js';
7
- import { actId, nowMs } from '../runtime.js';
8
- import { createSquare, openSquare } from '../square-file-adapter.js';
9
- import { closeOpenSquare } from '../open-square.js';
10
- import { Square } from '../square-wiring.js';
11
- import { createSquareApplication } from '../square-application.js';
12
- import { entryPresentation, eventPresentation } from '../views.js';
13
- import { createDefaultWakeTransport } from '../notifications.js';
1
+ import { formatHardCap } from '../model.js';
2
+ import { commandPrefix, participantCommandPrefix, participantIdentity, renderRoomChangeText, renderAmbientEvent, withPathOutput, } from '../presentation.js';
3
+ import { formatActivityId } from '../square-application.js';
14
4
  import { style } from '../tty-style.js';
15
- import { fail, parseBoundedLimit, parseHardCap, parsePositiveInteger, readStdin, requireParticipant, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
5
+ import { executeExpression } from './express-presentation.js';
6
+ import { applicationFor, fail, parseBoundedLimit, parseHardCap, parsePositiveInteger, readStdin, requireSquarePath, requireValue, resolveBody, usage, } from './context.js';
16
7
  const JOIN_DEFAULT_LAST = 10;
17
8
  const JOIN_MAX_LAST = 100;
18
- function parseBuild(argv) {
19
- const options = { force: false, hardCap: null };
20
- for (let index = 0; index < argv.length; index++) {
21
- const flag = argv[index];
22
- switch (flag) {
23
- case '--cap':
24
- options.hardCap = parseHardCap(requireValue(argv, index, flag));
25
- index += 1;
26
- break;
27
- case '--template':
28
- options.template = requireValue(argv, index, flag);
29
- index += 1;
30
- break;
31
- case '--throttle':
32
- case '--throttle-per-minute':
33
- options.throttlePerMinute = parsePositiveInteger(requireValue(argv, index, flag), flag);
34
- index += 1;
35
- break;
36
- case '--force':
37
- case '-f':
38
- options.force = true;
39
- break;
40
- default:
41
- fail(`Unknown build option: ${flag}`);
42
- }
43
- }
44
- if (options.template !== undefined && !/^[a-zA-Z0-9-]+$/.test(options.template)) {
45
- fail('Invalid template name: only letters, digits, and hyphens allowed.');
46
- }
47
- if (options.throttlePerMinute !== undefined && options.throttlePerMinute <= 0) {
48
- fail('Invalid build option: --throttle must be a positive integer.');
49
- }
50
- return { options, snippet: '' };
51
- }
52
9
  export const buildCommand = {
53
- parse: (argv) => parseBuild(argv),
10
+ parse(argv) {
11
+ const options = { force: false, hardCap: null };
12
+ for (let index = 0; index < argv.length; index++) {
13
+ const flag = argv[index];
14
+ switch (flag) {
15
+ case '--cap':
16
+ options.hardCap = parseHardCap(requireValue(argv, index, flag));
17
+ index += 1;
18
+ break;
19
+ case '--template':
20
+ options.template = requireValue(argv, index, flag);
21
+ index += 1;
22
+ break;
23
+ case '--throttle':
24
+ case '--throttle-per-minute':
25
+ options.throttlePerMinute = parsePositiveInteger(requireValue(argv, index, flag), flag);
26
+ index += 1;
27
+ break;
28
+ case '--force':
29
+ case '-f':
30
+ options.force = true;
31
+ break;
32
+ default:
33
+ fail(`Unknown build option: ${flag}`);
34
+ }
35
+ }
36
+ if (options.template !== undefined && !/^[a-zA-Z0-9-]+$/.test(options.template)) {
37
+ fail('Invalid template name: only letters, digits, and hyphens allowed.');
38
+ }
39
+ return { options };
40
+ },
54
41
  async execute(intent, context) {
55
- const squarePath = requireSquarePath(context);
56
- const snippet = await readStdin();
57
- if (snippet.trim() === '')
42
+ const markdown = await readStdin();
43
+ if (markdown.trim() === '')
58
44
  fail('Missing Markdown body snippet on stdin.');
59
- await createSquare(squarePath, intent.options, snippet);
60
- const cap = intent.options.hardCap === null ? 'unlimited' : formatHardCap(intent.options.hardCap);
61
- const throttle = intent.options.throttlePerMinute === undefined ? [] : [` · throttle ${intent.options.throttlePerMinute}/min`];
62
- return withPathOutput(squarePath, ['· the square stands ready', ` · cap ${cap}`, ...throttle].join('\n'), { participantCount: 0 });
45
+ const { data, projection } = await applicationFor(context).build({ ...intent.options, markdown });
46
+ const cap = data.hardCap === null ? 'unlimited' : formatHardCap(data.hardCap);
47
+ const throttle = data.throttlePerMinute === undefined ? [] : [` · throttle ${data.throttlePerMinute}/min`];
48
+ return withPathOutput(data.location, ['· the square stands ready', ` · cap ${cap}`, ...throttle].join('\n'), {
49
+ participantCount: projection.standingCount, held: projection.held,
50
+ });
63
51
  },
64
52
  present: (result) => process.stdout.write(result),
65
53
  };
66
- function parseJoin(argv, context) {
67
- const name = requireParticipant(context.name);
68
- const squarePath = requireSquarePath(context);
69
- let lastN = JOIN_DEFAULT_LAST;
70
- let kick = false;
71
- for (let index = 0; index < argv.length; index++) {
72
- if (argv[index] === '--last') {
73
- lastN = parseBoundedLimit(argv[index + 1], '--last', JOIN_MAX_LAST, `${participantCommandPrefix(squarePath, name)} join --last ${JOIN_MAX_LAST}`);
74
- index += 1;
75
- }
76
- else if (argv[index] === '--kick') {
77
- kick = true;
78
- }
79
- else {
80
- usage(context.command);
81
- }
82
- }
83
- return { name, lastN, kick };
84
- }
85
54
  export const joinCommand = {
86
- parse: parseJoin,
87
- async execute(intent, context) {
88
- const squarePath = requireSquarePath(context);
89
- const hostLedger = hostLedgerForEnv(context.env);
90
- const beforeSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger });
91
- const before = await entryPresentation(beforeSquare, intent.name, intent.lastN);
92
- await closeOpenSquare(beforeSquare);
93
- const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs, hostLedger, wakeTransport: await createDefaultWakeTransport(hostLedger, nowMs) });
94
- let joinedName;
95
- let joinKind;
96
- try {
97
- const result = await application.join({ takeover: intent.kick });
98
- joinedName = result.participant;
99
- joinKind = result.kind;
100
- }
101
- catch (error) {
102
- if (error instanceof Error && 'code' in error && error.code === 'already_joined') {
103
- process.stderr.write(formatRefusal(squarePath, [
104
- `✕ ${participantIdentity(intent.name)} shoos you out of the square`,
105
- ' · a same-named participant stands here — the name is taken',
106
- ...takeoverRecoveryLines(squarePath, intent.name),
107
- ], { participantCount: before.participantCount }));
108
- process.exit(2);
55
+ parse(argv, context) {
56
+ let lastN = JOIN_DEFAULT_LAST;
57
+ let kick = false;
58
+ for (let index = 0; index < argv.length; index++) {
59
+ if (argv[index] === '--last') {
60
+ lastN = parseBoundedLimit(argv[index + 1], '--last', JOIN_MAX_LAST, `${context.name === undefined ? commandPrefix(requireSquarePath(context)) : participantCommandPrefix(requireSquarePath(context), context.name)} join --last ${JOIN_MAX_LAST}`);
61
+ index += 1;
109
62
  }
110
- throw error;
63
+ else if (argv[index] === '--kick')
64
+ kick = true;
65
+ else
66
+ usage(context.command);
111
67
  }
112
- const afterSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger });
113
- const after = await entryPresentation(afterSquare, joinedName, intent.lastN);
114
- await closeOpenSquare(afterSquare);
115
- const activities = after.recentActivities.map((event) => renderAmbientEvent(event, joinedName, {
116
- now: nowMs(),
117
- preview: intent.lastN === null ? undefined : 200,
68
+ return { lastN, kick };
69
+ },
70
+ async execute(intent, context) {
71
+ const { data, projection: after } = await applicationFor(context).join({ takeover: intent.kick, last: intent.lastN });
72
+ const name = data.participant;
73
+ const activities = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
74
+ now: after.now, preview: 200,
118
75
  actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
119
- squareState: after.state,
76
+ perception: after.perceptions[event.index],
120
77
  })).filter(Boolean).join('\n\n');
121
- const contextText = after.joinContext;
122
- const fallback = hasAutomaticDeliveryIdentity()
123
- ? []
124
- : ['', `${participantCommandPrefix(squarePath, joinedName)} catch --idle 30m`, ' the square has no way to call you — keep this catch open and stay within earshot'];
125
- const scene = after.scene;
126
- const entryLine = joinKind === 'joined'
127
- ? '● you stepped into the square'
128
- : joinKind === 'reconnected'
129
- ? '● you are already in the square'
130
- : `✓ you banished the original ${participantIdentity(joinedName)} — the name is yours`;
131
- const isRejoin = joinKind !== 'joined';
132
- const reconnect = joinKind === 'reconnected';
133
- // The long scene belongs to a name's first join here; a return reads its own line.
134
- const joinedBefore = after.state.acts.filter((act) => act.kind === 'join' && sameName(act.actor, joinedName)).length > 1;
135
- const rejoinScene = '*the flagstones are where you left them. so is your shadow.*';
136
- const entryScene = joinKind !== 'joined' ? '' : joinedBefore ? rejoinScene : scene;
137
- const output = [
138
- entryLine,
139
- '',
78
+ const fallback = after.automaticDeliveryAvailable ? [] : [
79
+ '', `${participantCommandPrefix(after.location, name)} catch --idle 30m`,
80
+ ' the square has no way to call you — keep this catch open and stay within earshot',
81
+ ];
82
+ const entryLine = data.kind === 'joined' ? '● you stepped into the square'
83
+ : data.kind === 'reconnected' ? '● you are already in the square'
84
+ : `✓ you banished the original ${participantIdentity(name)} — the name is yours`;
85
+ const isRejoin = data.kind !== 'joined';
86
+ const entryScene = isRejoin ? '' : after.joinedBefore
87
+ ? '*the flagstones are where you left them. so is your shadow.*' : after.scene;
88
+ return withPathOutput(after.location, [
89
+ entryLine, '',
140
90
  "· carved into the fountain's edge: every word here lands on a real ear — speak when someone needs it.",
141
- ...(reconnect || entryScene === '' ? [] : ['', entryScene]),
142
- ...(isRejoin || contextText === '' ? [] : ['', style('dim', 'context'), contextText]),
91
+ ...(entryScene === '' ? [] : ['', entryScene]),
92
+ ...(isRejoin || after.joinContext === '' ? [] : ['', style('dim', 'context'), after.joinContext]),
143
93
  ...(isRejoin || activities === '' ? [] : ['', style('dim', 'recent activity'), activities]),
144
94
  ...fallback,
145
- ].join('\n');
146
- return withPathOutput(squarePath, output, { participantCount: after.participantCount });
95
+ ].join('\n'), { participantCount: after.standingCount, held: after.held });
147
96
  },
148
97
  present: (result) => process.stdout.write(result),
149
98
  };
@@ -184,31 +133,20 @@ function parseActivity(argv, context) {
184
133
  if (noMention && mentions.length > 0)
185
134
  fail('✕ --no-mention cannot be combined with --mention\nsquare express --help');
186
135
  if (bodyArgs.length !== 1) {
187
- if (bodyArgs.length === 0) {
188
- if (!process.stdin.isTTY)
189
- return { name: requireParticipant(context.name), activity: '-', force, noWait, noMention, mentions, reach, reply };
190
- }
191
- fail("express requires a body argument (a quoted string or '-' with piped stdin)");
136
+ if (bodyArgs.length === 0 && !process.stdin.isTTY)
137
+ bodyArgs.push('-');
138
+ else
139
+ fail("express requires a body argument (a quoted string or '-' with piped stdin)");
192
140
  }
193
- return { name: requireParticipant(context.name), activity: bodyArgs[0], force, noWait, noMention, mentions, reach, reply };
141
+ return { activity: bodyArgs[0], force, noWait, noMention, mentions, reach, reply };
194
142
  }
195
143
  export const expressCommand = {
196
144
  parse: parseActivity,
197
145
  async execute(intent, context) {
198
- const squarePath = requireSquarePath(context);
199
- const body = await resolveBody(intent.activity);
200
- if (intent.reach === undefined && !intent.noMention && intent.mentions.length === 0 && intent.reply === undefined && scanMentionCandidates(body).length === 0) {
201
- fail("✕ express needs --mention <name>, --no-mention, --bell, or an @name in the body\n · --reply <activity-id> also reaches the author it answers\nsquare express --help");
202
- }
203
- await cmdActivity(squarePath, intent.name, body, (value) => value, {
204
- force: intent.force,
205
- noWait: intent.noWait,
206
- noMention: intent.noMention,
207
- mentions: intent.mentions,
208
- reach: intent.reach,
209
- reply: intent.reply,
210
- cwd: context.cwd,
211
- env: context.env,
146
+ const rawInput = await resolveBody(intent.activity);
147
+ await executeExpression(applicationFor(context), requireSquarePath(context), context.name, rawInput, {
148
+ force: intent.force, noWait: intent.noWait, noMention: intent.noMention,
149
+ mentions: intent.mentions, reach: intent.reach, reply: intent.reply,
212
150
  });
213
151
  },
214
152
  present: () => { },
@@ -216,117 +154,63 @@ export const expressCommand = {
216
154
  function parseListener(argv, context, targetRequired) {
217
155
  if (targetRequired ? argv.length !== 1 : argv.length !== 0)
218
156
  usage(context.command);
219
- return { name: requireParticipant(context.name), ...(targetRequired ? { target: argv[0] } : {}) };
157
+ return targetRequired ? { target: argv[0] } : {};
220
158
  }
221
- function listenerPresentation(squarePath, actor, target, verb, activity, participantCount) {
222
- if (activity !== null) {
223
- const action = verb === 'listen'
224
- ? `${participantIdentity(actor)} turns an ear toward ${participantIdentity(target)}`
225
- : `${participantIdentity(actor)} turns away from ${participantIdentity(target)}`;
226
- return withPathOutput(squarePath, `· ${action}`, { participantCount });
227
- }
228
- const action = verb === 'listen'
229
- ? `${participantIdentity(actor)} already turns an ear toward ${participantIdentity(target)}`
230
- : `${participantIdentity(actor)} is not turned toward ${participantIdentity(target)}`;
231
- return withPathOutput(squarePath, `○ ${action}`, { participantCount });
159
+ function listenerCommand(verb) {
160
+ return {
161
+ parse(argv, context) { return parseListener(argv, context, true); },
162
+ async execute(intent, context) {
163
+ const { data, projection } = await applicationFor(context)[verb](intent.target);
164
+ const actor = participantIdentity(projection.participant);
165
+ const target = participantIdentity(projection.target);
166
+ const action = data.activity !== null
167
+ ? verb === 'listen' ? `${actor} turns an ear toward ${target}` : `${actor} turns away from ${target}`
168
+ : verb === 'listen' ? `${actor} already turns an ear toward ${target}` : `${actor} is not turned toward ${target}`;
169
+ return withPathOutput(projection.location, `${data.activity === null ? '○' : '·'} ${action}`, {
170
+ participantCount: projection.standingCount, held: projection.held,
171
+ });
172
+ },
173
+ present: (result) => process.stdout.write(result),
174
+ };
232
175
  }
233
- export const listenCommand = {
234
- parse(argv, context) { return parseListener(argv, context, true); },
235
- async execute(intent, context) {
236
- const squarePath = requireSquarePath(context);
237
- const square = await Square.at({ path: squarePath, clock: nowMs });
238
- try {
239
- const result = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).listen(intent.target);
240
- const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
241
- return listenerPresentation(squarePath, intent.name, intent.target, 'listen', result.activity, participantCount);
242
- }
243
- finally {
244
- await square.close();
245
- }
246
- },
247
- present: (result) => process.stdout.write(result),
248
- };
249
- export const ignoreCommand = {
250
- parse(argv, context) { return parseListener(argv, context, true); },
251
- async execute(intent, context) {
252
- const squarePath = requireSquarePath(context);
253
- const square = await Square.at({ path: squarePath, clock: nowMs });
254
- try {
255
- const result = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).ignore(intent.target);
256
- const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
257
- return listenerPresentation(squarePath, intent.name, intent.target, 'ignore', result.activity, participantCount);
258
- }
259
- finally {
260
- await square.close();
261
- }
262
- },
263
- present: (result) => process.stdout.write(result),
264
- };
176
+ export const listenCommand = listenerCommand('listen');
177
+ export const ignoreCommand = listenerCommand('ignore');
265
178
  export const listeningCommand = {
266
179
  parse(argv, context) { return parseListener(argv, context, false); },
267
- async execute(intent, context) {
268
- const squarePath = requireSquarePath(context);
269
- const square = await Square.at({ path: squarePath, clock: nowMs });
270
- try {
271
- const participant = { name: intent.name };
272
- const targets = await createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, participant: intent.name, clock: nowMs }).listening();
273
- const participantCount = (await square.snapshot()).participants.filter((item) => item.state === 'joined').length;
274
- const body = targets.length === 0
275
- ? `○ ${participantIdentity(participant.name)} is not turned toward anyone`
276
- : ['listening', ...targets.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
277
- return withPathOutput(squarePath, body, { participantCount });
278
- }
279
- finally {
280
- await square.close();
281
- }
180
+ async execute(_intent, context) {
181
+ const { data, projection } = await applicationFor(context).listening();
182
+ const body = data.listening.length === 0
183
+ ? `○ ${participantIdentity(projection.participant)} is not turned toward anyone`
184
+ : ['listening', ...data.listening.map((target) => ` · ${participantIdentity(target)}`)].join('\n');
185
+ return withPathOutput(projection.location, body, { participantCount: projection.standingCount, held: projection.held });
282
186
  },
283
187
  present: (result) => process.stdout.write(result),
284
188
  };
285
- function validateDoneInput(body) {
286
- try {
287
- validateDoneBody(body);
288
- }
289
- catch (error) {
290
- fail(`${error instanceof Error ? error.message : String(error)}\nsquare done --help`);
291
- }
292
- }
293
- function parseDone(argv, context) {
294
- if (argv.length > 1)
295
- usage(context.command);
296
- if (argv[0] !== '-')
297
- validateDoneInput(argv[0]);
298
- return { name: requireParticipant(context.name), body: argv.length === 1 ? argv[0] : undefined };
299
- }
300
189
  export const doneCommand = {
301
- parse: parseDone,
190
+ parse(argv, context) {
191
+ if (argv.length > 1)
192
+ usage(context.command);
193
+ return { body: argv[0] };
194
+ },
302
195
  async execute(intent, context) {
303
- const squarePath = requireSquarePath(context);
304
- const body = intent.body === '-' || !process.stdin.isTTY ? await readStdin() : '';
305
- validateDoneInput(body);
306
- const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
307
- const result = await application.done(body);
308
- const name = result.activity.actor;
309
- const presentation = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
310
- const participantCount = (await entryPresentation(presentation, name).finally(() => closeOpenSquare(presentation))).participantCount;
311
- return withPathOutput(squarePath, `○ you stepped out — your footsteps fade · ${result.activity.id}`, { participantCount });
196
+ const body = intent.body === '-' || ((intent.body === undefined || intent.body === '') && !process.stdin.isTTY)
197
+ ? await readStdin() : intent.body;
198
+ const { data, projection } = await applicationFor(context).done(body);
199
+ return withPathOutput(projection.location, `○ you stepped out — your footsteps fade · ${data.activity.id}`, {
200
+ participantCount: projection.standingCount, held: projection.held,
201
+ });
312
202
  },
313
203
  present: (result) => process.stdout.write(result),
314
204
  };
315
- function parseHold(argv, context) {
316
- if (argv.length > 1)
317
- usage(context.command);
318
- return { name: requireParticipant(context.name), body: argv[0] };
319
- }
320
205
  export const holdCommand = {
321
- parse: parseHold,
206
+ parse(argv, context) {
207
+ if (argv.length > 1)
208
+ usage(context.command);
209
+ return { body: argv[0] };
210
+ },
322
211
  async execute(intent, context) {
323
- const squarePath = requireSquarePath(context);
324
- const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
325
- const result = await application.hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
326
- const presentationSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
327
- const presentation = await eventPresentation(presentationSquare, result.activity.id);
328
- await closeOpenSquare(presentationSquare);
329
- return withPathOutput(squarePath, `· ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount, held: true });
212
+ const { projection } = await applicationFor(context).hold((await resolveBody(intent.body ?? '')).replace(/\r\n/g, '\n').trim());
213
+ return withPathOutput(projection.location, `· ${renderRoomChangeText(projection.activity)} · ${formatActivityId(projection.activity.index)} · just now`, { participantCount: projection.standingCount, held: projection.held });
330
214
  },
331
215
  present: (result) => process.stdout.write(result),
332
216
  };
@@ -334,16 +218,11 @@ export const resumeCommand = {
334
218
  parse(argv, context) {
335
219
  if (argv.length !== 0)
336
220
  usage(context.command);
337
- return { name: requireParticipant(context.name) };
221
+ return {};
338
222
  },
339
- async execute(intent, context) {
340
- const squarePath = requireSquarePath(context);
341
- const application = createSquareApplication({ cwd: context.cwd, env: context.env, squarePath, clock: nowMs, participant: intent.name });
342
- const result = await application.resume();
343
- const presentationSquare = await openSquare(squarePath, { clock: nowMs, env: context.env, hostLedger: hostLedgerForEnv(context.env) });
344
- const presentation = await eventPresentation(presentationSquare, result.activity.id);
345
- await closeOpenSquare(presentationSquare);
346
- return withPathOutput(squarePath, `✓ ${renderRoomChangeText(presentation.activity)} · ${actId(presentation.activity.index)} · just now`, { participantCount: presentation.participantCount });
223
+ async execute(_intent, context) {
224
+ const { projection } = await applicationFor(context).resume();
225
+ return withPathOutput(projection.location, `✓ ${renderRoomChangeText(projection.activity)} · ${formatActivityId(projection.activity.index)} · just now`, { participantCount: projection.standingCount, held: projection.held });
347
226
  },
348
227
  present: (result) => process.stdout.write(result),
349
228
  };