@astrosheep/square 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-plugin/.codex-plugin/plugin.json +3 -2
- package/dist/activity-feed.js +26 -18
- package/dist/activity.js +9 -10
- package/dist/artifact.js +126 -202
- package/dist/claude-hook.js +45 -21
- 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 +44 -52
- package/dist/cli/program.js +4 -4
- package/dist/cli/registry.js +5 -5
- package/dist/cli/square-commands.js +16 -18
- package/dist/cmd/notify-once.js +23 -21
- package/dist/compact.js +1 -1
- package/dist/decisions.js +53 -86
- package/dist/delivery-health.js +104 -210
- package/dist/delivery.js +68 -18
- package/dist/doctor.js +9 -8
- package/dist/harness-claude.js +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 +9 -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-timeline.js +58 -188
- package/dist/presentation.js +55 -63
- package/dist/presented.js +9 -8
- package/dist/registry.js +55 -45
- package/dist/runtime.js +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 +134 -188
- package/dist/watch.js +65 -122
- package/extensions/square-opencode.js +1 -1
- package/extensions/square-pi.js +8 -130
- package/guides/architect.md +3 -3
- package/guides/participant.md +25 -16
- package/package.json +2 -2
- package/skills/brainstorm/SKILL.md +25 -32
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +39 -107
- package/skills/square-feedback/SKILL.md +4 -4
- package/dist/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,12 +1,13 @@
|
|
|
1
1
|
import { leaseOwnsNotification } from './delivery.js';
|
|
2
|
-
import { notificationMessageId } from './delivery
|
|
2
|
+
import { notificationMessageId } from './delivery.js';
|
|
3
3
|
import { sessionInbox } from './inbox.js';
|
|
4
4
|
import { participantCommandPrefix } from './presentation.js';
|
|
5
5
|
import { presentOnce } from './presented.js';
|
|
6
6
|
function pendingCount(inbox) {
|
|
7
7
|
return inbox.reduce((total, membership) => total + membership.notifications.length, 0);
|
|
8
8
|
}
|
|
9
|
-
const INJECT_BODY_MAX =
|
|
9
|
+
const INJECT_BODY_MAX = 200;
|
|
10
|
+
const INJECT_TOTAL_MAX = 1200;
|
|
10
11
|
/** Let a fresh blocking catch own notifications it can deliver; hook injection remains the fallback. */
|
|
11
12
|
export function deferToActiveCatch(inbox) {
|
|
12
13
|
return inbox
|
|
@@ -16,38 +17,61 @@ export function deferToActiveCatch(inbox) {
|
|
|
16
17
|
return membership;
|
|
17
18
|
return {
|
|
18
19
|
...membership,
|
|
19
|
-
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, notification)),
|
|
20
|
+
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, { ...notification, recipient: membership.name })),
|
|
20
21
|
};
|
|
21
22
|
})
|
|
22
23
|
.filter((membership) => membership.notifications.length > 0);
|
|
23
24
|
}
|
|
24
|
-
function injectBodyPreview(body
|
|
25
|
+
function injectBodyPreview(body) {
|
|
25
26
|
const compact = body.replace(/\r\n/g, '\n');
|
|
26
27
|
if (compact.length <= INJECT_BODY_MAX)
|
|
27
28
|
return compact;
|
|
28
|
-
|
|
29
|
-
return `${compact.slice(0, INJECT_BODY_MAX).trimEnd()}\n… [truncated] full echo: ${pointer}`;
|
|
29
|
+
return `${compact.slice(0, INJECT_BODY_MAX).trimEnd()}\n… [truncated; run catch --now]`;
|
|
30
30
|
}
|
|
31
31
|
export function renderClaudeInboxContext(inbox) {
|
|
32
32
|
const count = pendingCount(inbox);
|
|
33
33
|
const noun = count === 1 ? 'notification' : 'notifications';
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
}),
|
|
34
|
+
const header = `<system-reminder source="square">You have ${count} unread Square ${noun}.`;
|
|
35
|
+
const footer = [
|
|
48
36
|
// Body here is a cache only. Delivered is written solely by catch.
|
|
49
37
|
'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
38
|
'Read and respond in the square before finishing the current turn.</system-reminder>',
|
|
39
|
+
];
|
|
40
|
+
const queued = inbox.flatMap((membership) => membership.notifications.map((notification) => ({ membership, notification })));
|
|
41
|
+
const blocks = [];
|
|
42
|
+
let omitted = 0;
|
|
43
|
+
for (const [index, entry] of queued.entries()) {
|
|
44
|
+
const { membership, notification } = entry;
|
|
45
|
+
const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
|
|
46
|
+
const id = notificationMessageId(membership.squarePath, notification.actIndex);
|
|
47
|
+
const block = [
|
|
48
|
+
`${id} · ${membership.squarePath}: @${membership.name} from @${notification.actor} (${notification.route})`,
|
|
49
|
+
injectBodyPreview(notification.body),
|
|
50
|
+
`Ack with: ${command}`,
|
|
51
|
+
].join('\n');
|
|
52
|
+
const omittedAfter = omitted + queued.length - index - 1;
|
|
53
|
+
const prospective = [
|
|
54
|
+
header,
|
|
55
|
+
...blocks,
|
|
56
|
+
block,
|
|
57
|
+
...(omittedAfter > 0
|
|
58
|
+
? [`… ${omittedAfter} unread ${omittedAfter === 1 ? 'notification' : 'notifications'} omitted. Run catch --now to receive them.`]
|
|
59
|
+
: []),
|
|
60
|
+
...footer,
|
|
61
|
+
].join('\n');
|
|
62
|
+
if (prospective.length > INJECT_TOTAL_MAX) {
|
|
63
|
+
omitted += 1;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
blocks.push(block);
|
|
67
|
+
}
|
|
68
|
+
return [
|
|
69
|
+
header,
|
|
70
|
+
...blocks,
|
|
71
|
+
...(omitted > 0
|
|
72
|
+
? [`… ${omitted} unread ${omitted === 1 ? 'notification' : 'notifications'} omitted. Run catch --now to receive them.`]
|
|
73
|
+
: []),
|
|
74
|
+
...footer,
|
|
51
75
|
].join('\n');
|
|
52
76
|
}
|
|
53
77
|
function nativeHookResponse(input, lookup, env) {
|
|
@@ -58,7 +82,7 @@ function nativeHookResponse(input, lookup, env) {
|
|
|
58
82
|
}
|
|
59
83
|
if (input.hook_event_name === 'Stop' && input.stop_hook_active === true)
|
|
60
84
|
return undefined;
|
|
61
|
-
// Delivery membership is only claimed by explicit participant actions (join/
|
|
85
|
+
// Delivery membership is only claimed by explicit participant actions (join/express/catch/...).
|
|
62
86
|
// Inherited PASEO_AGENT_ID proves process ancestry, not conversational ownership.
|
|
63
87
|
// Stop is the guaranteed "don't leave while undelivered" nudge; it does not consume presentation.
|
|
64
88
|
if (input.hook_event_name === 'Stop') {
|
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
|
};
|
|
@@ -10,7 +10,7 @@ import { actId, inSquareCount, isCurrentlyJoined, nowMs, resolveRosterName, sayN
|
|
|
10
10
|
import { cmdStream, cmdStreamNdjson } from '../stream.js';
|
|
11
11
|
import { formatRelativeTime, formatTimestamp, parseTimeOrRelative } from '../time.js';
|
|
12
12
|
import { cmdWatch } from '../watch.js';
|
|
13
|
-
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger,
|
|
13
|
+
import { fail, parseDurationMs, parseNameList, parseNonNegativeInteger, readStdinSync, requireParticipant, requireValue, usage, } from './context.js';
|
|
14
14
|
export const listCommand = {
|
|
15
15
|
parse: (argv) => argv,
|
|
16
16
|
execute(argv, context) {
|
|
@@ -47,19 +47,13 @@ export const streamCommand = {
|
|
|
47
47
|
export const catchCommand = {
|
|
48
48
|
parse(argv, context) {
|
|
49
49
|
const name = requireParticipant(context.name);
|
|
50
|
-
let activityCount = 1;
|
|
51
50
|
let idleMs;
|
|
52
51
|
let mention;
|
|
53
|
-
let
|
|
52
|
+
let replace = false;
|
|
54
53
|
let now = false;
|
|
55
|
-
let follow = false;
|
|
56
54
|
const participants = [];
|
|
57
55
|
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') {
|
|
56
|
+
if (argv[index] === '--from') {
|
|
63
57
|
participants.push(...parseNameList(requireValue(argv, index, argv[index]), argv[index]));
|
|
64
58
|
index += 1;
|
|
65
59
|
}
|
|
@@ -77,25 +71,23 @@ export const catchCommand = {
|
|
|
77
71
|
mention = name;
|
|
78
72
|
}
|
|
79
73
|
}
|
|
80
|
-
else if (argv[index] === '
|
|
81
|
-
|
|
74
|
+
else if (argv[index] === '--replace')
|
|
75
|
+
replace = true;
|
|
82
76
|
else if (argv[index] === '--now')
|
|
83
77
|
now = true;
|
|
84
|
-
else if (argv[index] === '--follow')
|
|
85
|
-
follow = true;
|
|
86
78
|
else
|
|
87
|
-
|
|
79
|
+
fail(`✕ catch does not know ${argv[index]}\n» square catch --help`);
|
|
88
80
|
}
|
|
89
|
-
if (now
|
|
90
|
-
fail('
|
|
81
|
+
if (now === (idleMs !== undefined))
|
|
82
|
+
fail('catch requires exactly one mode: --now or --idle <duration>.');
|
|
83
|
+
if (replace && now)
|
|
84
|
+
fail('--replace can only be used with --idle.');
|
|
91
85
|
return {
|
|
92
|
-
activityCount,
|
|
93
86
|
...(participants.length > 0 ? { participants } : {}),
|
|
94
87
|
...(mention === undefined ? {} : { mention }),
|
|
95
88
|
...(idleMs === undefined ? {} : { idleMs }),
|
|
96
|
-
...(
|
|
89
|
+
...(replace ? { replace } : {}),
|
|
97
90
|
...(now ? { now } : {}),
|
|
98
|
-
...(follow ? { follow } : {}),
|
|
99
91
|
};
|
|
100
92
|
},
|
|
101
93
|
async execute(intent, context) {
|
|
@@ -106,7 +98,7 @@ export const catchCommand = {
|
|
|
106
98
|
function parseActRef(value, flag) {
|
|
107
99
|
const match = value.trim().match(/^(?:act_)?(\d+)$/i);
|
|
108
100
|
if (!match)
|
|
109
|
-
fail(`Invalid ${flag}: expected
|
|
101
|
+
fail(`Invalid ${flag}: expected an activity id like act_12 or 12.`);
|
|
110
102
|
return Number(match[1]);
|
|
111
103
|
}
|
|
112
104
|
function parseTimestamp(value, flag) {
|
|
@@ -115,7 +107,8 @@ function parseTimestamp(value, flag) {
|
|
|
115
107
|
fail(`Invalid ${flag} timestamp: ${value}`);
|
|
116
108
|
return timestamp;
|
|
117
109
|
}
|
|
118
|
-
function
|
|
110
|
+
function parseHistory(argv, context) {
|
|
111
|
+
const viewer = context.name;
|
|
119
112
|
let lastN = 10;
|
|
120
113
|
let lastNExplicit = false;
|
|
121
114
|
let before;
|
|
@@ -125,7 +118,6 @@ function parseEcho(argv, viewer) {
|
|
|
125
118
|
let beforeContext;
|
|
126
119
|
let afterContext;
|
|
127
120
|
let mention;
|
|
128
|
-
let mentionsViewer = false;
|
|
129
121
|
let pending = false;
|
|
130
122
|
let full = false;
|
|
131
123
|
let grep;
|
|
@@ -138,8 +130,15 @@ function parseEcho(argv, viewer) {
|
|
|
138
130
|
const participants = [];
|
|
139
131
|
for (let index = 0; index < argv.length; index++) {
|
|
140
132
|
const flag = argv[index];
|
|
141
|
-
if (flag === '--
|
|
142
|
-
|
|
133
|
+
if (flag === '--limit') {
|
|
134
|
+
const value = argv[index + 1];
|
|
135
|
+
const retry = `${commandPrefix(context.squarePath)} history --limit 30`;
|
|
136
|
+
if (value === undefined || value.startsWith('--'))
|
|
137
|
+
fail(`✕ --limit needs a positive number\n» ${retry}`);
|
|
138
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(Number(value))) {
|
|
139
|
+
fail(`✕ --limit needs a positive number\n» ${retry}`);
|
|
140
|
+
}
|
|
141
|
+
lastN = Number(value);
|
|
143
142
|
lastNExplicit = true;
|
|
144
143
|
index += 1;
|
|
145
144
|
}
|
|
@@ -147,11 +146,11 @@ function parseEcho(argv, viewer) {
|
|
|
147
146
|
lastN = null;
|
|
148
147
|
lastNExplicit = true;
|
|
149
148
|
}
|
|
150
|
-
else if (flag === '--
|
|
149
|
+
else if (flag === '--from') {
|
|
151
150
|
participants.push(...parseNameList(requireValue(argv, index, flag), flag));
|
|
152
151
|
index += 1;
|
|
153
152
|
}
|
|
154
|
-
else if (flag === '--
|
|
153
|
+
else if (flag === '--until') {
|
|
155
154
|
before = parseTimestamp(requireValue(argv, index, flag), flag);
|
|
156
155
|
index += 1;
|
|
157
156
|
}
|
|
@@ -187,13 +186,6 @@ function parseEcho(argv, viewer) {
|
|
|
187
186
|
mention = requireValue(argv, index, flag);
|
|
188
187
|
index += 1;
|
|
189
188
|
}
|
|
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
189
|
else if (flag === '--pending')
|
|
198
190
|
pending = true;
|
|
199
191
|
else if (flag === '--grep') {
|
|
@@ -228,10 +220,10 @@ function parseEcho(argv, viewer) {
|
|
|
228
220
|
else if (flag === '--json')
|
|
229
221
|
json = true;
|
|
230
222
|
else
|
|
231
|
-
fail(
|
|
223
|
+
fail(`✕ history does not know ${flag}\n» square history --help`);
|
|
232
224
|
}
|
|
233
|
-
if (
|
|
234
|
-
fail('--
|
|
225
|
+
if (pending && !viewer)
|
|
226
|
+
fail('--pending requires --as <name>.');
|
|
235
227
|
if (grep !== undefined && fixed !== undefined)
|
|
236
228
|
fail('--grep and --fixed cannot be combined.');
|
|
237
229
|
if (grep === '' || fixed === '')
|
|
@@ -248,7 +240,6 @@ function parseEcho(argv, viewer) {
|
|
|
248
240
|
beforeContext,
|
|
249
241
|
afterContext,
|
|
250
242
|
mention,
|
|
251
|
-
mentionsViewer,
|
|
252
243
|
pending,
|
|
253
244
|
viewer,
|
|
254
245
|
full,
|
|
@@ -266,18 +257,18 @@ function renderFields(doc, item, fields) {
|
|
|
266
257
|
switch (field) {
|
|
267
258
|
case 'id': return actId(item.index);
|
|
268
259
|
case 'author':
|
|
269
|
-
case 'actor': return item.
|
|
260
|
+
case 'actor': return item.actor ?? '';
|
|
270
261
|
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.
|
|
262
|
+
case 'at': return formatTimestamp(item.at);
|
|
263
|
+
case 'kind': return item.kind;
|
|
264
|
+
case 'body': return 'body' in item && typeof item.body === 'string' ? item.body.replace(/\s+/g, ' ').trim() : '';
|
|
265
|
+
case 'number': return item.kind === 'say' ? String(sayNumberFor(doc.acts, item)) : '';
|
|
275
266
|
default: return '';
|
|
276
267
|
}
|
|
277
268
|
}).join('\t');
|
|
278
269
|
}
|
|
279
270
|
function jsonLine(doc, item) {
|
|
280
|
-
const act = item
|
|
271
|
+
const act = item;
|
|
281
272
|
return JSON.stringify({
|
|
282
273
|
id: actId(item.index),
|
|
283
274
|
index: item.index,
|
|
@@ -290,8 +281,8 @@ function jsonLine(doc, item) {
|
|
|
290
281
|
reach: act.kind === 'say' ? act.reach ?? null : null,
|
|
291
282
|
});
|
|
292
283
|
}
|
|
293
|
-
export const
|
|
294
|
-
parse(argv, context) { return
|
|
284
|
+
export const historyCommand = {
|
|
285
|
+
parse(argv, context) { return parseHistory(argv, context); },
|
|
295
286
|
execute(options, context) {
|
|
296
287
|
const doc = loadSquare(context.squarePath);
|
|
297
288
|
let events = coreActivities(doc, options);
|
|
@@ -333,14 +324,15 @@ export const participantsCommand = {
|
|
|
333
324
|
usage(context.command); return undefined; },
|
|
334
325
|
execute(_intent, context) {
|
|
335
326
|
const doc = loadSquare(context.squarePath);
|
|
336
|
-
const
|
|
337
|
-
const
|
|
327
|
+
const now = nowMs();
|
|
328
|
+
const participants = coreParticipants(doc, now);
|
|
329
|
+
const lines = participants.map((participant) => {
|
|
338
330
|
const glyph = participant.state === 'done' ? '×' : participant.presence === 'watching' ? '◎' : participant.activityCount > 0 ? '●' : '○';
|
|
339
331
|
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}
|
|
332
|
+
const last = participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, now);
|
|
333
|
+
return ` ${glyph} ${participant.name} · ${state} · ${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${last}`;
|
|
342
334
|
});
|
|
343
|
-
const participantCount =
|
|
335
|
+
const participantCount = participants.filter((participant) => participant.state === 'active').length;
|
|
344
336
|
return withPathOutput(context.squarePath, ['participants', ...lines].join('\n'), {
|
|
345
337
|
participantCount,
|
|
346
338
|
});
|
|
@@ -365,7 +357,7 @@ export const statusCommand = {
|
|
|
365
357
|
? '◎'
|
|
366
358
|
: participant.activityCount > 0 ? '●' : '○';
|
|
367
359
|
const summary = participant.activityCount > 0
|
|
368
|
-
? `${participant.activityCount}
|
|
360
|
+
? `${participant.activityCount} ${participant.activityCount === 1 ? 'activity' : 'activities'} · ${participant.lastActiveAt === undefined
|
|
369
361
|
? 'just now'
|
|
370
362
|
: formatRelativeTime(participant.lastActiveAt, result.now)}`
|
|
371
363
|
: `quiet · ${participant.lastActiveAt === undefined ? '—' : formatRelativeTime(participant.lastActiveAt, result.now)}`;
|
|
@@ -401,7 +393,7 @@ export const statusCommand = {
|
|
|
401
393
|
: [` ${visible.replace(/\n/g, '\n ')}`];
|
|
402
394
|
if (visible.includes('more chars') && result.latestAct !== undefined) {
|
|
403
395
|
const prefix = context.name === undefined ? commandPrefix(context.squarePath) : participantCommandPrefix(context.squarePath, context.name);
|
|
404
|
-
latest.push(`» ${prefix}
|
|
396
|
+
latest.push(`» ${prefix} history --at ${actId(result.latestAct)} -C 2 --full`);
|
|
405
397
|
}
|
|
406
398
|
const output = [
|
|
407
399
|
`${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 },
|