@astrosheep/square 0.3.5 → 0.3.7
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/codex-plugin/hooks/hooks.json +3 -14
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +10 -10
- package/dist/artifact.js +138 -203
- package/dist/boundary-presentation.js +77 -0
- package/dist/claude-hook.js +4 -94
- package/dist/cli/context.js +7 -7
- package/dist/cli/maintenance-commands.js +10 -26
- package/dist/cli/meta-commands.js +3 -6
- package/dist/cli/observation-commands.js +48 -53
- package/dist/cli/program.js +4 -4
- package/dist/cli/registry.js +5 -5
- package/dist/cli/square-commands.js +27 -20
- package/dist/cmd/notify-once.js +23 -21
- package/dist/codex-hook.js +22 -0
- package/dist/compact.js +1 -1
- package/dist/decisions.js +61 -88
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +38 -245
- package/dist/harness-codex.js +82 -616
- package/dist/harness-stage.js +36 -0
- package/dist/harness.js +3 -5
- package/dist/help.js +43 -35
- package/dist/inbox.js +12 -11
- package/dist/index.js +10 -121
- 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-delivery.js +160 -0
- package/dist/paseo-state.js +31 -0
- package/dist/paseo-timeline.js +58 -188
- package/dist/presentation.js +57 -64
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +27 -84
- package/dist/square-application.js +135 -130
- package/dist/square-core.js +3 -11
- package/dist/stream.js +27 -126
- package/dist/wake-sink.js +3 -214
- package/dist/watch.js +65 -122
- package/extensions/square-opencode.js +8 -73
- package/extensions/square-pi.js +8 -132
- 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/hooks/hooks.json +2 -13
- package/skills/square-feedback/SKILL.md +4 -4
- package/dist/harness-lifecycle.js +0 -102
- package/dist/square-store.js +0 -111
- package/dist/terminal.js +0 -125
package/dist/claude-hook.js
CHANGED
|
@@ -1,88 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { notificationMessageId } from './delivery-health.js';
|
|
1
|
+
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
3
2
|
import { sessionInbox } from './inbox.js';
|
|
4
|
-
|
|
5
|
-
import { presentOnce } from './presented.js';
|
|
6
|
-
function pendingCount(inbox) {
|
|
7
|
-
return inbox.reduce((total, membership) => total + membership.notifications.length, 0);
|
|
8
|
-
}
|
|
9
|
-
const INJECT_BODY_MAX = 2048;
|
|
10
|
-
/** Let a fresh blocking catch own notifications it can deliver; hook injection remains the fallback. */
|
|
11
|
-
export function deferToActiveCatch(inbox) {
|
|
12
|
-
return inbox
|
|
13
|
-
.map((membership) => {
|
|
14
|
-
const lease = membership.catchLease;
|
|
15
|
-
if (lease === undefined)
|
|
16
|
-
return membership;
|
|
17
|
-
return {
|
|
18
|
-
...membership,
|
|
19
|
-
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, notification)),
|
|
20
|
-
};
|
|
21
|
-
})
|
|
22
|
-
.filter((membership) => membership.notifications.length > 0);
|
|
23
|
-
}
|
|
24
|
-
function injectBodyPreview(body, squarePath, name, actIndex) {
|
|
25
|
-
const compact = body.replace(/\r\n/g, '\n');
|
|
26
|
-
if (compact.length <= INJECT_BODY_MAX)
|
|
27
|
-
return compact;
|
|
28
|
-
const pointer = `${participantCommandPrefix(squarePath, name)} echo --ids act_${actIndex} --full`;
|
|
29
|
-
return `${compact.slice(0, INJECT_BODY_MAX).trimEnd()}\n… [truncated] full echo: ${pointer}`;
|
|
30
|
-
}
|
|
31
|
-
export function renderClaudeInboxContext(inbox) {
|
|
32
|
-
const count = pendingCount(inbox);
|
|
33
|
-
const noun = count === 1 ? 'notification' : 'notifications';
|
|
34
|
-
return [
|
|
35
|
-
`<system-reminder source="square">You have ${count} unread Square ${noun}.`,
|
|
36
|
-
...inbox.flatMap((membership) => {
|
|
37
|
-
const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
|
|
38
|
-
return membership.notifications.map((notification) => {
|
|
39
|
-
const id = notificationMessageId(membership.squarePath, notification.actIndex);
|
|
40
|
-
const body = injectBodyPreview(notification.body, membership.squarePath, membership.name, notification.actIndex);
|
|
41
|
-
return [
|
|
42
|
-
`${id} · ${membership.squarePath}: @${membership.name} from @${notification.actor} (${notification.via})`,
|
|
43
|
-
body,
|
|
44
|
-
`Ack with: ${command}`,
|
|
45
|
-
].join('\n');
|
|
46
|
-
});
|
|
47
|
-
}),
|
|
48
|
-
// Body here is a cache only. Delivered is written solely by catch.
|
|
49
|
-
'Ids are stable across turns. If you already acted on an id, do not repeat the action; still run catch --now to mark delivered.',
|
|
50
|
-
'Read and respond in the square before finishing the current turn.</system-reminder>',
|
|
51
|
-
].join('\n');
|
|
52
|
-
}
|
|
53
|
-
function nativeHookResponse(input, lookup, env) {
|
|
3
|
+
export function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
54
4
|
if (typeof input.session_id !== 'string' || input.session_id === '')
|
|
55
5
|
return undefined;
|
|
56
|
-
if (input.hook_event_name !== '
|
|
57
|
-
return undefined;
|
|
58
|
-
}
|
|
59
|
-
if (input.hook_event_name === 'Stop' && input.stop_hook_active === true)
|
|
6
|
+
if (input.hook_event_name !== 'PostToolBatch')
|
|
60
7
|
return undefined;
|
|
61
|
-
|
|
62
|
-
// Inherited PASEO_AGENT_ID proves process ancestry, not conversational ownership.
|
|
63
|
-
// Stop is the guaranteed "don't leave while undelivered" nudge; it does not consume presentation.
|
|
64
|
-
if (input.hook_event_name === 'Stop') {
|
|
65
|
-
const pending = lookup(input.session_id).filter((membership) => membership.notifications.length > 0);
|
|
66
|
-
if (pendingCount(pending) === 0)
|
|
67
|
-
return undefined;
|
|
68
|
-
return { decision: 'block', reason: renderClaudeInboxContext(pending) };
|
|
69
|
-
}
|
|
70
|
-
return presentOnce(input.session_id, (sessionId) => deferToActiveCatch(lookup(sessionId)), (inbox) => ({
|
|
71
|
-
hookSpecificOutput: {
|
|
72
|
-
hookEventName: 'UserPromptSubmit',
|
|
73
|
-
additionalContext: renderClaudeInboxContext(inbox),
|
|
74
|
-
},
|
|
75
|
-
}), env);
|
|
76
|
-
}
|
|
77
|
-
export function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
78
|
-
return nativeHookResponse(input, lookup, env);
|
|
79
|
-
}
|
|
80
|
-
/** Codex shares Claude's turn-boundary protocol; keep a dedicated command for churn isolation. */
|
|
81
|
-
export function codexHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
82
|
-
return nativeHookResponse(input, lookup, env);
|
|
83
|
-
}
|
|
84
|
-
export function opencodeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
85
|
-
return nativeHookResponse(input, lookup, env);
|
|
8
|
+
return presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: context } }), lookup, env);
|
|
86
9
|
}
|
|
87
10
|
export function runClaudeHook(inputText, env = process.env) {
|
|
88
11
|
let input;
|
|
@@ -97,16 +20,3 @@ export function runClaudeHook(inputText, env = process.env) {
|
|
|
97
20
|
const response = claudeHookResponse(input, sessionInbox, env);
|
|
98
21
|
return response === undefined ? '' : `${JSON.stringify(response)}\n`;
|
|
99
22
|
}
|
|
100
|
-
export function runCodexHook(inputText, env = process.env) {
|
|
101
|
-
let input;
|
|
102
|
-
try {
|
|
103
|
-
input = JSON.parse(inputText);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return '';
|
|
107
|
-
}
|
|
108
|
-
if (input === null || typeof input !== 'object')
|
|
109
|
-
return '';
|
|
110
|
-
const response = codexHookResponse(input, sessionInbox, env);
|
|
111
|
-
return response === undefined ? '' : `${JSON.stringify(response)}\n`;
|
|
112
|
-
}
|
package/dist/cli/context.js
CHANGED
|
@@ -3,7 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { loadSquare } from '../artifact.js';
|
|
5
5
|
import { commandUsageHint } from '../help.js';
|
|
6
|
-
import { parseParticipantList,
|
|
6
|
+
import { parseParticipantList, validateName } from '../model.js';
|
|
7
7
|
export const DEFAULT_SQUARE_PATH = '.square/SQUARE.md';
|
|
8
8
|
export function readStdinSync() {
|
|
9
9
|
try {
|
|
@@ -65,13 +65,13 @@ export function parseNonNegativeInteger(value, flag) {
|
|
|
65
65
|
return parsed;
|
|
66
66
|
}
|
|
67
67
|
export function parseHardCap(value) {
|
|
68
|
-
if (value === '
|
|
68
|
+
if (value === 'unlimited')
|
|
69
69
|
return null;
|
|
70
70
|
if (!/^[1-9]\d*$/.test(value))
|
|
71
|
-
fail('Invalid build option: --cap must be a positive integer or
|
|
71
|
+
fail('Invalid build option: --cap must be a positive integer or unlimited.');
|
|
72
72
|
const parsed = Number(value);
|
|
73
73
|
if (!Number.isSafeInteger(parsed))
|
|
74
|
-
fail('Invalid build option: --cap must be a positive integer or
|
|
74
|
+
fail('Invalid build option: --cap must be a positive integer or unlimited.');
|
|
75
75
|
return parsed;
|
|
76
76
|
}
|
|
77
77
|
export function parseNameList(value, flag) {
|
|
@@ -79,13 +79,13 @@ export function parseNameList(value, flag) {
|
|
|
79
79
|
if (names.length === 0)
|
|
80
80
|
fail(`Invalid ${flag}: expected at least one participant name.`);
|
|
81
81
|
for (const name of names)
|
|
82
|
-
|
|
82
|
+
validateName(name);
|
|
83
83
|
return names;
|
|
84
84
|
}
|
|
85
85
|
export function requireParticipant(name) {
|
|
86
86
|
if (!name)
|
|
87
87
|
fail('Missing required option: --as <name>.');
|
|
88
|
-
|
|
88
|
+
validateName(name);
|
|
89
89
|
return name;
|
|
90
90
|
}
|
|
91
91
|
function resolveDefaultSquarePath() {
|
|
@@ -130,7 +130,7 @@ export function parseGlobalArgs(rawArgs) {
|
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
if (name !== undefined)
|
|
133
|
-
|
|
133
|
+
validateName(name);
|
|
134
134
|
const explicitSquarePath = requestedPath !== undefined;
|
|
135
135
|
const command = args[0];
|
|
136
136
|
const resolved = !explicitSquarePath && !['ls', 'list', 'version'].includes(command ?? '')
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { diagnoseSquare, loadSquare } from '../artifact.js';
|
|
3
|
-
import { doctorDeliveryHealth, findStalePendingMentions } from '../delivery-health.js';
|
|
4
3
|
import { renderDoctorClean, renderDoctorProblems, renderDoctorRepaired, renderDoctorUnfixable, withPathOutput, } from '../presentation.js';
|
|
5
4
|
import { inSquareCount } from '../runtime.js';
|
|
6
|
-
import {
|
|
5
|
+
import { repairSquare } from '../square-application.js';
|
|
7
6
|
import { SquareError } from '../model.js';
|
|
8
|
-
import {
|
|
7
|
+
import { pruneRegistry } from '../registry.js';
|
|
8
|
+
import { usage } from './context.js';
|
|
9
9
|
function readSquareText(squarePath) {
|
|
10
10
|
try {
|
|
11
11
|
return fs.readFileSync(squarePath, 'utf8');
|
|
@@ -22,13 +22,10 @@ function quarantinePath(squarePath) {
|
|
|
22
22
|
export const doctorCommand = {
|
|
23
23
|
parse(argv, context) {
|
|
24
24
|
let fix = false;
|
|
25
|
-
let reconcileBacklog = false;
|
|
26
25
|
for (let index = 0; index < argv.length; index++) {
|
|
27
26
|
const argument = argv[index];
|
|
28
27
|
if (argument === '--fix')
|
|
29
28
|
fix = true;
|
|
30
|
-
else if (argument === 'reconcile-backlog')
|
|
31
|
-
reconcileBacklog = true;
|
|
32
29
|
else if (argument === '--before') {
|
|
33
30
|
index += 1;
|
|
34
31
|
if (argv[index] === undefined)
|
|
@@ -37,9 +34,7 @@ export const doctorCommand = {
|
|
|
37
34
|
else
|
|
38
35
|
usage(context.command);
|
|
39
36
|
}
|
|
40
|
-
|
|
41
|
-
fail('doctor reconcile-backlog requires --fix.');
|
|
42
|
-
return { fix, reconcileBacklog };
|
|
37
|
+
return { fix };
|
|
43
38
|
},
|
|
44
39
|
async execute(intent, context) {
|
|
45
40
|
if (!intent.fix) {
|
|
@@ -51,24 +46,9 @@ export const doctorCommand = {
|
|
|
51
46
|
};
|
|
52
47
|
}
|
|
53
48
|
const summary = diagnosis.problems.length === 0 ? renderDoctorClean() : renderDoctorProblems(diagnosis.problems);
|
|
54
|
-
const delivery = doctorDeliveryHealth(context.squarePath).join('\n');
|
|
55
|
-
const stale = findStalePendingMentions(context.squarePath);
|
|
56
49
|
return {
|
|
57
|
-
output: withPathOutput(context.squarePath,
|
|
58
|
-
exitCode: diagnosis.problems.length === 0
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
if (intent.reconcileBacklog) {
|
|
62
|
-
const result = await reconcileBacklog(context.squarePath);
|
|
63
|
-
const delivery = doctorDeliveryHealth(context.squarePath).join('\n');
|
|
64
|
-
return {
|
|
65
|
-
output: withPathOutput(context.squarePath, [
|
|
66
|
-
`✓ reconciled ${result.reconciled} backlog receipt(s) as delivered(reason=reconciled)`,
|
|
67
|
-
result.skippedRecent > 0 ? `· left ${result.skippedRecent} recent liveness failure(s) untouched` : '· no recent liveness failures present',
|
|
68
|
-
'',
|
|
69
|
-
delivery,
|
|
70
|
-
].join('\n'), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
71
|
-
exitCode: result.skippedRecent > 0 ? 1 : 0,
|
|
50
|
+
output: withPathOutput(context.squarePath, summary, { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
51
|
+
exitCode: diagnosis.problems.length === 0 ? 0 : 1,
|
|
72
52
|
};
|
|
73
53
|
}
|
|
74
54
|
const repair = await repairSquare(context.squarePath);
|
|
@@ -79,6 +59,10 @@ export const doctorCommand = {
|
|
|
79
59
|
};
|
|
80
60
|
}
|
|
81
61
|
const repaired = repair.repaired;
|
|
62
|
+
const registry = pruneRegistry();
|
|
63
|
+
if (registry.removed > 0) {
|
|
64
|
+
repaired.actions.push({ message: `pruned ${registry.removed} obsolete registry membership(s)` });
|
|
65
|
+
}
|
|
82
66
|
const sidecar = quarantinePath(context.squarePath);
|
|
83
67
|
return {
|
|
84
68
|
output: withPathOutput(context.squarePath, renderDoctorRepaired(repaired.actions, repaired.quarantinedBlocks.length, repaired.quarantinedBlocks.length > 0 ? sidecar : undefined), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { fileURLToPath } from 'node:url';
|
|
3
1
|
import { renderGlobalHelp, renderSubcommandHelp } from '../help.js';
|
|
2
|
+
import { SQUARE_IDENTITY } from '../identity.js';
|
|
4
3
|
import { fail } from './context.js';
|
|
5
4
|
export const helpCommand = {
|
|
6
5
|
parse(argv) {
|
|
@@ -13,7 +12,7 @@ export const helpCommand = {
|
|
|
13
12
|
return renderGlobalHelp();
|
|
14
13
|
const rendered = renderSubcommandHelp(intent.command);
|
|
15
14
|
if (rendered === undefined)
|
|
16
|
-
fail(`unknown command: ${intent.command}\nrun 'square help' to list
|
|
15
|
+
fail(`unknown command: ${intent.command}\nrun 'square help' to list available commands`);
|
|
17
16
|
return rendered;
|
|
18
17
|
},
|
|
19
18
|
present: (result) => process.stdout.write(result),
|
|
@@ -23,9 +22,7 @@ export const versionCommand = {
|
|
|
23
22
|
return undefined;
|
|
24
23
|
},
|
|
25
24
|
execute() {
|
|
26
|
-
|
|
27
|
-
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
28
|
-
return `${packageJson.version ?? 'unknown'}\n`;
|
|
25
|
+
return `${SQUARE_IDENTITY.packageVersion}\n`;
|
|
29
26
|
},
|
|
30
27
|
present: (result) => process.stdout.write(result),
|
|
31
28
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadSquare } from '../artifact.js';
|
|
2
|
-
import { runClaudeHook
|
|
2
|
+
import { runClaudeHook } from '../claude-hook.js';
|
|
3
|
+
import { runCodexHook } from '../codex-hook.js';
|
|
3
4
|
import { coreActivities, coreParticipants, coreStatus } from '../decisions.js';
|
|
4
5
|
import { sessionInbox } from '../inbox.js';
|
|
5
6
|
import { cmdListSquares } from '../list.js';
|
|
@@ -10,7 +11,7 @@ import { actId, inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName, sayN
|
|
|
10
11
|
import { cmdStream, cmdStreamNdjson } from '../stream.js';
|
|
11
12
|
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
12
13
|
import { cmdWatch } from '../watch.js';
|
|
13
|
-
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger,
|
|
14
|
+
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdinSync, requireParticipant, requireValue, usage, } from './context.js';
|
|
14
15
|
export const listCommand = {
|
|
15
16
|
parse: (argv) => argv,
|
|
16
17
|
execute(argv, context) {
|
|
@@ -47,19 +48,13 @@ export const streamCommand = {
|
|
|
47
48
|
export const catchCommand = {
|
|
48
49
|
parse(argv, context) {
|
|
49
50
|
const name = requireParticipant(context.name);
|
|
50
|
-
let activityCount = 1;
|
|
51
51
|
let idleMs;
|
|
52
52
|
let mention;
|
|
53
|
-
let
|
|
53
|
+
let replace = false;
|
|
54
54
|
let now = false;
|
|
55
|
-
let follow = false;
|
|
56
55
|
const participants = [];
|
|
57
56
|
for (let index = 0; index < argv.length; index++) {
|
|
58
|
-
if (argv[index] === '--
|
|
59
|
-
activityCount = parsePositiveInteger(requireValue(argv, index, argv[index]), argv[index]);
|
|
60
|
-
index += 1;
|
|
61
|
-
}
|
|
62
|
-
else if (argv[index] === '--by') {
|
|
57
|
+
if (argv[index] === '--from') {
|
|
63
58
|
participants.push(...parseNameList(requireValue(argv, index, argv[index]), argv[index]));
|
|
64
59
|
index += 1;
|
|
65
60
|
}
|
|
@@ -77,25 +72,23 @@ export const catchCommand = {
|
|
|
77
72
|
mention = name;
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
|
-
else if (argv[index] === '
|
|
81
|
-
|
|
75
|
+
else if (argv[index] === '--replace')
|
|
76
|
+
replace = true;
|
|
82
77
|
else if (argv[index] === '--now')
|
|
83
78
|
now = true;
|
|
84
|
-
else if (argv[index] === '--follow')
|
|
85
|
-
follow = true;
|
|
86
79
|
else
|
|
87
|
-
|
|
80
|
+
fail(`✕ catch does not know ${argv[index]}\n» square catch --help`);
|
|
88
81
|
}
|
|
89
|
-
if (now
|
|
90
|
-
fail('
|
|
82
|
+
if (now === (idleMs !== undefined))
|
|
83
|
+
fail('catch requires exactly one mode: --now or --idle <duration>.');
|
|
84
|
+
if (replace && now)
|
|
85
|
+
fail('--replace can only be used with --idle.');
|
|
91
86
|
return {
|
|
92
|
-
activityCount,
|
|
93
87
|
...(participants.length > 0 ? { participants } : {}),
|
|
94
88
|
...(mention === undefined ? {} : { mention }),
|
|
95
89
|
...(idleMs === undefined ? {} : { idleMs }),
|
|
96
|
-
...(
|
|
90
|
+
...(replace ? { replace } : {}),
|
|
97
91
|
...(now ? { now } : {}),
|
|
98
|
-
...(follow ? { follow } : {}),
|
|
99
92
|
};
|
|
100
93
|
},
|
|
101
94
|
async execute(intent, context) {
|
|
@@ -106,7 +99,7 @@ export const catchCommand = {
|
|
|
106
99
|
function parseActRef(value, flag) {
|
|
107
100
|
const match = value.trim().match(/^(?:act_)?(\d+)$/i);
|
|
108
101
|
if (!match)
|
|
109
|
-
fail(`Invalid ${flag}: expected
|
|
102
|
+
fail(`Invalid ${flag}: expected an activity id like act_12 or 12.`);
|
|
110
103
|
return Number(match[1]);
|
|
111
104
|
}
|
|
112
105
|
function parseTimestamp(value, flag) {
|
|
@@ -115,7 +108,8 @@ function parseTimestamp(value, flag) {
|
|
|
115
108
|
fail(`Invalid ${flag} timestamp: ${value}`);
|
|
116
109
|
return timestamp;
|
|
117
110
|
}
|
|
118
|
-
function
|
|
111
|
+
function parseHistory(argv, context) {
|
|
112
|
+
const viewer = context.name;
|
|
119
113
|
let lastN = 10;
|
|
120
114
|
let lastNExplicit = false;
|
|
121
115
|
let before;
|
|
@@ -125,7 +119,6 @@ function parseEcho(argv, viewer) {
|
|
|
125
119
|
let beforeContext;
|
|
126
120
|
let afterContext;
|
|
127
121
|
let mention;
|
|
128
|
-
let mentionsViewer = false;
|
|
129
122
|
let pending = false;
|
|
130
123
|
let full = false;
|
|
131
124
|
let grep;
|
|
@@ -138,8 +131,15 @@ function parseEcho(argv, viewer) {
|
|
|
138
131
|
const participants = [];
|
|
139
132
|
for (let index = 0; index < argv.length; index++) {
|
|
140
133
|
const flag = argv[index];
|
|
141
|
-
if (flag === '--
|
|
142
|
-
|
|
134
|
+
if (flag === '--limit') {
|
|
135
|
+
const value = argv[index + 1];
|
|
136
|
+
const retry = `${commandPrefix(context.squarePath)} history --limit 30`;
|
|
137
|
+
if (value === undefined || value.startsWith('--'))
|
|
138
|
+
fail(`✕ --limit needs a positive number\n» ${retry}`);
|
|
139
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) {
|
|
140
|
+
fail(`✕ --limit needs a positive number\n» ${retry}`);
|
|
141
|
+
}
|
|
142
|
+
lastN = Number(value);
|
|
143
143
|
lastNExplicit = true;
|
|
144
144
|
index += 1;
|
|
145
145
|
}
|
|
@@ -147,11 +147,11 @@ function parseEcho(argv, viewer) {
|
|
|
147
147
|
lastN = null;
|
|
148
148
|
lastNExplicit = true;
|
|
149
149
|
}
|
|
150
|
-
else if (flag === '--
|
|
150
|
+
else if (flag === '--from') {
|
|
151
151
|
participants.push(...parseNameList(requireValue(argv, index, flag), flag));
|
|
152
152
|
index += 1;
|
|
153
153
|
}
|
|
154
|
-
else if (flag === '--
|
|
154
|
+
else if (flag === '--until') {
|
|
155
155
|
before = parseTimestamp(requireValue(argv, index, flag), flag);
|
|
156
156
|
index += 1;
|
|
157
157
|
}
|
|
@@ -187,13 +187,6 @@ function parseEcho(argv, viewer) {
|
|
|
187
187
|
mention = requireValue(argv, index, flag);
|
|
188
188
|
index += 1;
|
|
189
189
|
}
|
|
190
|
-
else if (flag === '--mentions') {
|
|
191
|
-
const value = requireValue(argv, index, flag);
|
|
192
|
-
if (value !== 'me')
|
|
193
|
-
fail(`Invalid --mentions: only 'me' is supported (got ${value}).`);
|
|
194
|
-
mentionsViewer = true;
|
|
195
|
-
index += 1;
|
|
196
|
-
}
|
|
197
190
|
else if (flag === '--pending')
|
|
198
191
|
pending = true;
|
|
199
192
|
else if (flag === '--grep') {
|
|
@@ -228,10 +221,10 @@ function parseEcho(argv, viewer) {
|
|
|
228
221
|
else if (flag === '--json')
|
|
229
222
|
json = true;
|
|
230
223
|
else
|
|
231
|
-
fail(
|
|
224
|
+
fail(`✕ history does not know ${flag}\n» square history --help`);
|
|
232
225
|
}
|
|
233
|
-
if (
|
|
234
|
-
fail('--
|
|
226
|
+
if (pending && !viewer)
|
|
227
|
+
fail('--pending requires --as <name>.');
|
|
235
228
|
if (grep !== undefined && fixed !== undefined)
|
|
236
229
|
fail('--grep and --fixed cannot be combined.');
|
|
237
230
|
if (grep === '' || fixed === '')
|
|
@@ -248,7 +241,6 @@ function parseEcho(argv, viewer) {
|
|
|
248
241
|
beforeContext,
|
|
249
242
|
afterContext,
|
|
250
243
|
mention,
|
|
251
|
-
mentionsViewer,
|
|
252
244
|
pending,
|
|
253
245
|
viewer,
|
|
254
246
|
full,
|
|
@@ -266,18 +258,19 @@ function renderFields(doc, item, fields) {
|
|
|
266
258
|
switch (field) {
|
|
267
259
|
case 'id': return actId(item.index);
|
|
268
260
|
case 'author':
|
|
269
|
-
case 'actor': return item.
|
|
261
|
+
case 'actor': return item.actor ?? '';
|
|
270
262
|
case 'ts':
|
|
271
|
-
case 'at': return formatTimestamp(item.
|
|
272
|
-
case 'kind': return item.
|
|
273
|
-
case 'body': return 'body' in item
|
|
274
|
-
case 'number': return item.
|
|
263
|
+
case 'at': return formatTimestamp(item.at);
|
|
264
|
+
case 'kind': return item.kind;
|
|
265
|
+
case 'body': return 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
|
|
266
|
+
case 'number': return item.kind === 'say' ? String(sayNumberFor(doc.acts, item)) : '';
|
|
267
|
+
case 'reply': return item.kind === 'say' && item.reply !== undefined ? actId(item.reply) : '';
|
|
275
268
|
default: return '';
|
|
276
269
|
}
|
|
277
270
|
}).join('\t');
|
|
278
271
|
}
|
|
279
272
|
function jsonLine(doc, item) {
|
|
280
|
-
const act = item
|
|
273
|
+
const act = item;
|
|
281
274
|
return JSON.stringify({
|
|
282
275
|
id: actId(item.index),
|
|
283
276
|
index: item.index,
|
|
@@ -288,10 +281,11 @@ function jsonLine(doc, item) {
|
|
|
288
281
|
body: 'body' in act && typeof act.body === 'string' ? act.body : '',
|
|
289
282
|
number: act.kind === 'say' ? sayNumberFor(doc.acts, act) : null,
|
|
290
283
|
reach: act.kind === 'say' ? act.reach ?? null : null,
|
|
284
|
+
reply: act.kind === 'say' && act.reply !== undefined ? actId(act.reply) : null,
|
|
291
285
|
});
|
|
292
286
|
}
|
|
293
|
-
export const
|
|
294
|
-
parse(argv, context) { return
|
|
287
|
+
export const historyCommand = {
|
|
288
|
+
parse(argv, context) { return parseHistory(argv, context); },
|
|
295
289
|
execute(options, context) {
|
|
296
290
|
const doc = loadSquare(context.squarePath);
|
|
297
291
|
let events = coreActivities(doc, options);
|
|
@@ -333,14 +327,15 @@ export const participantsCommand = {
|
|
|
333
327
|
usage(context.command); return undefined; },
|
|
334
328
|
execute(_intent, context) {
|
|
335
329
|
const doc = loadSquare(context.squarePath);
|
|
336
|
-
const
|
|
337
|
-
const
|
|
330
|
+
const now = nowMs();
|
|
331
|
+
const participants = coreParticipants(doc, now);
|
|
332
|
+
const lines = participants.map((participant) => {
|
|
338
333
|
const glyph = participant.state === 'done' ? '×' : participant.presence === 'watching' ? '◎' : participant.activityCount > 0 ? '●' : '○';
|
|
339
334
|
const state = participant.state === 'done' ? 'done' : participant.presence === 'watching' ? 'catching' : participant.state;
|
|
340
|
-
const last = participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt,
|
|
341
|
-
return ` ${glyph} ${participant.name} · ${state} · ${participant.activityCount}
|
|
335
|
+
const last = participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, now);
|
|
336
|
+
return ` ${glyph} ${participant.name} · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`;
|
|
342
337
|
});
|
|
343
|
-
const participantCount =
|
|
338
|
+
const participantCount = participants.filter((participant) => participant.state === 'active').length;
|
|
344
339
|
return withPathOutput(context.squarePath, ['participants', ...lines].join('\n'), {
|
|
345
340
|
participantCount,
|
|
346
341
|
});
|
|
@@ -365,7 +360,7 @@ export const statusCommand = {
|
|
|
365
360
|
? '◎'
|
|
366
361
|
: participant.activityCount > 0 ? '●' : '○';
|
|
367
362
|
const summary = participant.activityCount > 0
|
|
368
|
-
? `${participant.activityCount}
|
|
363
|
+
? `${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${participant.lastActiveAt === undefined
|
|
369
364
|
? 'just now'
|
|
370
365
|
: formatRelativeTime(participant.lastActiveAt, result.now)}`
|
|
371
366
|
: `quiet · ${participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, result.now)}`;
|
|
@@ -401,7 +396,7 @@ export const statusCommand = {
|
|
|
401
396
|
: [` ${visible.replace(/\n/g, '\n ')}`];
|
|
402
397
|
if (visible.includes('more chars') && result.latestAct !== undefined) {
|
|
403
398
|
const prefix = context.name === undefined ? commandPrefix(context.squarePath) : participantCommandPrefix(context.squarePath, context.name);
|
|
404
|
-
latest.push(`» ${prefix}
|
|
399
|
+
latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --full`);
|
|
405
400
|
}
|
|
406
401
|
const output = [
|
|
407
402
|
`${result.activeCount} active · ${result.doneCount} done · cap ${cap} · throttle ${result.throttlePerMinute === undefined ? 'none' : `${result.throttlePerMinute}/min`}`,
|
package/dist/cli/program.js
CHANGED
|
@@ -4,9 +4,9 @@ import { defaultContext, parseGlobalArgs } from './context.js';
|
|
|
4
4
|
import { refreshLocalRegistration } from './observation-commands.js';
|
|
5
5
|
import { executeRegisteredCommand, findCommand } from './registry.js';
|
|
6
6
|
function isMutatingCommand(command, argv) {
|
|
7
|
-
if (['build', 'join', 'catch', '
|
|
7
|
+
if (['build', 'join', 'catch', 'express', 'done', 'hold', 'resume', 'compact'].includes(command))
|
|
8
8
|
return true;
|
|
9
|
-
return command === 'doctor' && argv.
|
|
9
|
+
return command === 'doctor' && argv.includes('--fix');
|
|
10
10
|
}
|
|
11
11
|
function handleSquareError(error) {
|
|
12
12
|
if (error instanceof SquareError) {
|
|
@@ -34,10 +34,10 @@ export async function runCli(rawArgs = process.argv.slice(2)) {
|
|
|
34
34
|
process.exit(2);
|
|
35
35
|
}
|
|
36
36
|
if (!parsed.explicitSquarePath && parsed.multipleSquares && isMutatingCommand(command, parsed.args.slice(1))) {
|
|
37
|
-
process.stderr.write('✕ more than one square is active here; choose the path before changing or consuming activity.\n» square
|
|
37
|
+
process.stderr.write('✕ more than one square is active here; choose the path before changing or consuming activity.\n» square list\n');
|
|
38
38
|
process.exit(2);
|
|
39
39
|
}
|
|
40
|
-
if (['
|
|
40
|
+
if (['express', 'catch', 'done', 'hold', 'resume'].includes(command)) {
|
|
41
41
|
refreshLocalRegistration(parsed.squarePath, parsed.name);
|
|
42
42
|
}
|
|
43
43
|
await executeRegisteredCommand(command, parsed.args.slice(1), defaultContext(command, parsed.squarePath, parsed.name));
|
package/dist/cli/registry.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildCommand, compactCommand, doneCommand, expressCommand, holdCommand, joinCommand, resumeCommand } from './square-commands.js';
|
|
2
2
|
import { doctorCommand } from './maintenance-commands.js';
|
|
3
3
|
import { harnessCommand } from './harness-command.js';
|
|
4
4
|
import { helpCommand, versionCommand } from './meta-commands.js';
|
|
5
|
-
import { catchCommand, claudeHookCommand, codexHookCommand,
|
|
5
|
+
import { catchCommand, claudeHookCommand, codexHookCommand, historyCommand, inboxCommand, listCommand, participantsCommand, statusCommand, streamCommand, warmupCommand, } 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 },
|
|
9
|
-
{ names: ['
|
|
9
|
+
{ names: ['list', 'ls'], spec: listCommand },
|
|
10
10
|
{ names: ['join'], spec: joinCommand },
|
|
11
11
|
{ names: ['stream'], spec: streamCommand },
|
|
12
12
|
{ names: ['inbox'], spec: inboxCommand },
|
|
13
13
|
{ names: ['claude-hook'], spec: claudeHookCommand },
|
|
14
14
|
{ names: ['codex-hook'], spec: codexHookCommand },
|
|
15
15
|
{ names: ['catch'], spec: catchCommand },
|
|
16
|
-
{ names: ['
|
|
16
|
+
{ names: ['express'], spec: expressCommand },
|
|
17
17
|
{ names: ['done'], spec: doneCommand },
|
|
18
18
|
{ names: ['hold'], spec: holdCommand },
|
|
19
19
|
{ names: ['resume'], spec: resumeCommand },
|
|
20
20
|
{ names: ['harness'], spec: harnessCommand },
|
|
21
21
|
{ names: ['compact'], spec: compactCommand },
|
|
22
22
|
{ names: ['doctor'], spec: doctorCommand },
|
|
23
|
-
{ names: ['
|
|
23
|
+
{ names: ['history'], spec: historyCommand },
|
|
24
24
|
{ names: ['warmup'], spec: warmupCommand },
|
|
25
25
|
{ names: ['status'], spec: statusCommand },
|
|
26
26
|
{ names: ['participants'], spec: participantsCommand },
|