@astrosheep/square 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-plugin/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/hooks/hooks.json +9 -0
- package/dist/activity-feed.d.ts +14 -0
- package/dist/activity-feed.js +5 -5
- package/dist/activity.d.ts +9 -0
- package/dist/activity.js +112 -96
- package/dist/artifact.d.ts +20 -0
- package/dist/artifact.js +317 -648
- package/dist/automatic-session.d.ts +5 -0
- package/dist/automatic-session.js +86 -0
- package/dist/boundary-presentation.d.ts +5 -0
- package/dist/boundary-presentation.js +6 -5
- package/dist/claude-hook.d.ts +9 -0
- package/dist/claude-hook.js +35 -3
- package/dist/cli/context.d.ts +35 -0
- package/dist/cli/context.js +26 -31
- package/dist/cli/harness-command.d.ts +28 -0
- package/dist/cli/harness-command.js +30 -15
- package/dist/cli/maintenance-commands.d.ts +7 -0
- package/dist/cli/maintenance-commands.js +12 -69
- package/dist/cli/meta-commands.d.ts +5 -0
- package/dist/cli/observation-commands.d.ts +20 -0
- package/dist/cli/observation-commands.js +159 -139
- package/dist/cli/program.d.ts +2 -0
- package/dist/cli/program.js +3 -12
- package/dist/cli/registry.d.ts +9 -0
- package/dist/cli/registry.js +2 -4
- package/dist/cli/square-commands.d.ts +34 -0
- package/dist/cli/square-commands.js +90 -84
- package/dist/cmd/notify-once.d.ts +2 -0
- package/dist/codex-hook.d.ts +10 -0
- package/dist/codex-hook.js +35 -3
- package/dist/decisions.d.ts +92 -0
- package/dist/decisions.js +93 -81
- package/dist/delivery-health.d.ts +21 -0
- package/dist/delivery-health.js +41 -34
- package/dist/delivery.d.ts +81 -0
- package/dist/delivery.js +71 -69
- package/dist/file-lock.d.ts +6 -0
- package/dist/harness-claude.d.ts +19 -0
- package/dist/harness-claude.js +8 -3
- package/dist/harness-codex.d.ts +29 -0
- package/dist/harness-codex.js +2 -2
- package/dist/harness-links.d.ts +17 -0
- package/dist/harness-links.js +2 -2
- package/dist/harness-pi.d.ts +11 -0
- package/dist/harness-pi.js +2 -2
- package/dist/harness-stage.d.ts +6 -0
- package/dist/harness.d.ts +20 -0
- package/dist/harness.js +5 -3
- package/dist/help.d.ts +6 -0
- package/dist/help.js +12 -13
- package/dist/identity.d.ts +13 -0
- package/dist/inbox.d.ts +2 -0
- package/dist/inbox.js +15 -19
- package/dist/index.d.ts +13 -0
- package/dist/index.js +31 -67
- package/dist/landing.d.ts +10 -0
- package/dist/landing.js +95 -0
- package/dist/list.d.ts +1 -0
- package/dist/list.js +39 -61
- package/dist/model.d.ts +147 -0
- package/dist/model.js +12 -8
- package/dist/notifications.d.ts +30 -0
- package/dist/notifications.js +83 -91
- package/dist/open-square.d.ts +11 -0
- package/dist/open-square.js +4 -0
- package/dist/participant-identity.d.ts +1 -0
- package/dist/participant-identity.js +3 -0
- package/dist/paseo-connection.d.ts +15 -0
- package/dist/paseo-delivery.d.ts +14 -0
- package/dist/paseo-state.d.ts +11 -0
- package/dist/paseo-timeline.d.ts +16 -0
- package/dist/presence.d.ts +3 -0
- package/dist/presence.js +42 -0
- package/dist/presentation.d.ts +91 -0
- package/dist/presentation.js +52 -63
- package/dist/presented.d.ts +11 -0
- package/dist/registry.d.ts +58 -0
- package/dist/registry.js +36 -3
- package/dist/routes.d.ts +42 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.js +48 -55
- package/dist/search.d.ts +11 -0
- package/dist/square-core.d.ts +111 -0
- package/dist/square-core.js +63 -9
- package/dist/square-facade.d.ts +86 -0
- package/dist/square-facade.js +1 -0
- package/dist/square-file-adapter.d.ts +18 -0
- package/dist/square-file-adapter.js +70 -0
- package/dist/square-storage.d.ts +20 -0
- package/dist/square-storage.js +171 -0
- package/dist/square-wiring.d.ts +19 -0
- package/dist/square-wiring.js +70 -0
- package/dist/square.d.ts +2 -0
- package/dist/state-cell.d.ts +14 -0
- package/dist/state-cell.js +1 -0
- package/dist/stream.d.ts +3 -0
- package/dist/stream.js +19 -23
- package/dist/time.d.ts +6 -0
- package/dist/views.d.ts +95 -0
- package/dist/views.js +81 -0
- package/dist/wake-attempts.d.ts +39 -0
- package/dist/wake-attempts.js +8 -4
- package/dist/wake-evidence.d.ts +12 -0
- package/dist/wake-evidence.js +7 -10
- package/dist/wake-port.d.ts +20 -0
- package/dist/wake-sink.d.ts +12 -0
- package/dist/wakes.d.ts +40 -0
- package/dist/wakes.js +62 -0
- package/dist/watch.d.ts +2 -0
- package/dist/watch.js +89 -210
- package/extensions/square-opencode.js +27 -1
- package/extensions/square-pi.js +13 -0
- package/guides/participant.md +7 -174
- package/package.json +5 -4
- package/skills/brainstorm/SKILL.md +30 -30
- package/skills/square/.claude-plugin/plugin.json +1 -1
- package/skills/square/SKILL.md +56 -29
- package/skills/square/hooks/hooks.json +9 -0
- package/skills/square-feedback/SKILL.md +9 -9
- package/dist/compact.js +0 -26
- package/dist/doctor.js +0 -36
- package/dist/square-application.js +0 -262
- package/template.md +0 -4
- package/templates/architect.md +0 -4
- package/templates/brainstorm.md +0 -4
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type AutomaticProvider = 'codex' | 'claude' | 'opencode' | 'pi';
|
|
2
|
+
export declare function publicSquarePath(cwd: string): string;
|
|
3
|
+
export declare function automaticParticipant(provider: AutomaticProvider, sessionId: string, env: NodeJS.ProcessEnv): string;
|
|
4
|
+
export declare function automaticSessionStart(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<string | undefined>;
|
|
5
|
+
export declare function automaticSessionEnd(provider: AutomaticProvider, sessionId: string, cwd: string, env?: NodeJS.ProcessEnv): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { openSquare } from './square-file-adapter.js';
|
|
5
|
+
import { closeOpenSquare } from './open-square.js';
|
|
6
|
+
import { Square } from './square-wiring.js';
|
|
7
|
+
import { entryPresentation } from './views.js';
|
|
8
|
+
import { canonicalSquarePath, lookupSession, lookupSessionBindings, recordSessionDone, recordSessionJoin } from './registry.js';
|
|
9
|
+
import { participantIdentity, renderAmbientEvent } from './presentation.js';
|
|
10
|
+
import { validateName } from './model.js';
|
|
11
|
+
const providerEnv = {
|
|
12
|
+
codex: 'CODEX_THREAD_ID',
|
|
13
|
+
claude: 'CLAUDE_CODE_SESSION_ID',
|
|
14
|
+
opencode: 'OPENCODE_SESSION_ID',
|
|
15
|
+
pi: 'SQUARE_PI_SESSION_ID',
|
|
16
|
+
};
|
|
17
|
+
export function publicSquarePath(cwd) {
|
|
18
|
+
return path.join(cwd, '.square', 'PUBLIC.square');
|
|
19
|
+
}
|
|
20
|
+
export function automaticParticipant(provider, sessionId, env) {
|
|
21
|
+
const configured = env.SQUARE_PARTICIPANT_NAME?.trim();
|
|
22
|
+
if (configured) {
|
|
23
|
+
validateName(configured);
|
|
24
|
+
return configured;
|
|
25
|
+
}
|
|
26
|
+
const digest = createHash('sha256').update(sessionId, 'utf8').digest('hex').slice(0, 12);
|
|
27
|
+
return `${provider}-${digest}`;
|
|
28
|
+
}
|
|
29
|
+
export async function automaticSessionStart(provider, sessionId, cwd, env = process.env) {
|
|
30
|
+
const squarePath = publicSquarePath(cwd);
|
|
31
|
+
if (!fs.existsSync(squarePath))
|
|
32
|
+
return undefined;
|
|
33
|
+
let reader;
|
|
34
|
+
try {
|
|
35
|
+
reader = await openSquare(squarePath);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
process.stderr.write(`! PUBLIC.square unreadable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const name = automaticParticipant(provider, sessionId, env);
|
|
42
|
+
const bindings = lookupSession(sessionId);
|
|
43
|
+
const before = await entryPresentation(reader, name);
|
|
44
|
+
if (bindings.some((binding) => canonicalSquarePath(binding.squarePath) === canonicalSquarePath(squarePath) && binding.name === name) && before.joined) {
|
|
45
|
+
await closeOpenSquare(reader);
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
await closeOpenSquare(reader);
|
|
49
|
+
const square = await Square.at({ path: squarePath });
|
|
50
|
+
try {
|
|
51
|
+
await square.join(name);
|
|
52
|
+
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
53
|
+
recordSessionJoin(sessionId, name, squarePath, channel, { ...env, [providerEnv[provider]]: sessionId });
|
|
54
|
+
const afterSquare = await openSquare(squarePath);
|
|
55
|
+
const after = await entryPresentation(afterSquare, name, 10).finally(() => closeOpenSquare(afterSquare));
|
|
56
|
+
const activity = after.recentActivities.map((event) => renderAmbientEvent(event, name, {
|
|
57
|
+
now: Date.now(),
|
|
58
|
+
preview: 200,
|
|
59
|
+
actNumber: event.kind === 'say' ? after.sayNumbers[event.index] : undefined,
|
|
60
|
+
})).filter(Boolean).join('\n\n');
|
|
61
|
+
return [`You joined the public square as ${participantIdentity(name)}.`, after.scene, after.context ? `context\n${after.context}` : '', activity ? `recent activity\n${activity}` : ''].filter(Boolean).join('\n\n');
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
await square.close();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export async function automaticSessionEnd(provider, sessionId, cwd, env = process.env) {
|
|
68
|
+
const squarePath = publicSquarePath(cwd);
|
|
69
|
+
const channel = provider === 'claude' ? 'claude-code' : provider;
|
|
70
|
+
const binding = lookupSessionBindings(sessionId).find((item) => canonicalSquarePath(item.squarePath) === canonicalSquarePath(squarePath) && item.channel === channel);
|
|
71
|
+
if (binding === undefined || !fs.existsSync(squarePath))
|
|
72
|
+
return;
|
|
73
|
+
const reader = await openSquare(squarePath);
|
|
74
|
+
const joined = await entryPresentation(reader, binding.name).finally(() => closeOpenSquare(reader));
|
|
75
|
+
if (!joined.joined)
|
|
76
|
+
return;
|
|
77
|
+
const square = await Square.at({ path: squarePath });
|
|
78
|
+
try {
|
|
79
|
+
const participant = await square.join(binding.name);
|
|
80
|
+
await participant.done();
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
await square.close();
|
|
84
|
+
}
|
|
85
|
+
recordSessionDone(sessionId, binding.name, squarePath, channel, env);
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { InboxMembership } from './model.js';
|
|
2
|
+
/** A fresh blocking catch owns only the notifications admitted by its filter. */
|
|
3
|
+
export declare function pendingAtBoundary(inbox: InboxMembership[]): InboxMembership[];
|
|
4
|
+
export declare function renderPendingAtBoundary(inbox: InboxMembership[]): string;
|
|
5
|
+
export declare function presentPendingAtBoundary<T>(sessionId: string, present: (context: string) => T, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<T | undefined>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { leaseOwnsNotification, notificationMessageId } from './delivery.js';
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
|
-
import { participantCommandPrefix } from './presentation.js';
|
|
3
|
+
import { participantCommandPrefix, participantIdentity } from './presentation.js';
|
|
4
4
|
import { presentOnce } from './presented.js';
|
|
5
5
|
const BODY_MAX = 200;
|
|
6
6
|
const CONTEXT_MAX = 1200;
|
|
@@ -16,7 +16,7 @@ export function pendingAtBoundary(inbox) {
|
|
|
16
16
|
return membership;
|
|
17
17
|
return {
|
|
18
18
|
...membership,
|
|
19
|
-
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease,
|
|
19
|
+
notifications: membership.notifications.filter((notification) => !leaseOwnsNotification(lease, notification)),
|
|
20
20
|
};
|
|
21
21
|
})
|
|
22
22
|
.filter((membership) => membership.notifications.length > 0);
|
|
@@ -43,7 +43,7 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
43
43
|
const command = `${participantCommandPrefix(membership.squarePath, membership.name)} catch --now`;
|
|
44
44
|
const id = notificationMessageId(membership.squarePath, notification.actIndex);
|
|
45
45
|
const block = [
|
|
46
|
-
`${id} · ${membership.squarePath}:
|
|
46
|
+
`${id} · ${membership.squarePath}: ${participantIdentity(membership.name)} from ${participantIdentity(notification.actor)} (${notification.route})`,
|
|
47
47
|
bodyPreview(notification.body),
|
|
48
48
|
`Ack with: ${command}`,
|
|
49
49
|
].join('\n');
|
|
@@ -72,6 +72,7 @@ export function renderPendingAtBoundary(inbox) {
|
|
|
72
72
|
...footer,
|
|
73
73
|
].join('\n');
|
|
74
74
|
}
|
|
75
|
-
export function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
76
|
-
|
|
75
|
+
export async function presentPendingAtBoundary(sessionId, present, lookup = sessionInbox, env = process.env) {
|
|
76
|
+
const inbox = await lookup(sessionId);
|
|
77
|
+
return presentOnce(sessionId, () => pendingAtBoundary(inbox), (inbox) => present(renderPendingAtBoundary(inbox)), env);
|
|
77
78
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { InboxMembership } from './model.js';
|
|
2
|
+
export interface NativeHookInput {
|
|
3
|
+
session_id?: unknown;
|
|
4
|
+
hook_event_name?: unknown;
|
|
5
|
+
cwd?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare function runClaudeHookAsync(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
|
8
|
+
export declare function claudeHookResponse(input: NativeHookInput, lookup?: (sessionId: string) => Promise<InboxMembership[]> | InboxMembership[], env?: NodeJS.ProcessEnv): Promise<object | undefined>;
|
|
9
|
+
export declare function runClaudeHook(inputText: string, env?: NodeJS.ProcessEnv): Promise<string>;
|
package/dist/claude-hook.js
CHANGED
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
import { presentPendingAtBoundary } from './boundary-presentation.js';
|
|
2
2
|
import { sessionInbox } from './inbox.js';
|
|
3
|
-
|
|
3
|
+
import { automaticSessionEnd, automaticSessionStart } from './automatic-session.js';
|
|
4
|
+
export async function runClaudeHookAsync(inputText, env = process.env) {
|
|
5
|
+
let input;
|
|
6
|
+
try {
|
|
7
|
+
input = JSON.parse(inputText);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
if (input === null || typeof input !== 'object')
|
|
13
|
+
return '';
|
|
14
|
+
const value = input;
|
|
15
|
+
if (typeof value.session_id !== 'string' || typeof value.cwd !== 'string')
|
|
16
|
+
return runClaudeHook(inputText, env);
|
|
17
|
+
if (value.hook_event_name === 'SessionStart' || value.hook_event_name === 'SessionResume') {
|
|
18
|
+
try {
|
|
19
|
+
const context = await automaticSessionStart('claude', value.session_id, value.cwd, env);
|
|
20
|
+
return context === undefined ? '' : `${JSON.stringify({ hookSpecificOutput: { hookEventName: value.hook_event_name, additionalContext: context } })}\n`;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (value.hook_event_name === 'SessionEnd') {
|
|
27
|
+
try {
|
|
28
|
+
await automaticSessionEnd('claude', value.session_id, value.cwd, env);
|
|
29
|
+
}
|
|
30
|
+
catch { /* end remains bounded */ }
|
|
31
|
+
return '';
|
|
32
|
+
}
|
|
33
|
+
return runClaudeHook(inputText, env);
|
|
34
|
+
}
|
|
35
|
+
export async function claudeHookResponse(input, lookup = sessionInbox, env = process.env) {
|
|
4
36
|
if (typeof input.session_id !== 'string' || input.session_id === '')
|
|
5
37
|
return undefined;
|
|
6
38
|
if (input.hook_event_name !== 'PostToolBatch')
|
|
7
39
|
return undefined;
|
|
8
40
|
return presentPendingAtBoundary(input.session_id, (context) => ({ hookSpecificOutput: { hookEventName: 'PostToolBatch', additionalContext: context } }), lookup, env);
|
|
9
41
|
}
|
|
10
|
-
export function runClaudeHook(inputText, env = process.env) {
|
|
42
|
+
export async function runClaudeHook(inputText, env = process.env) {
|
|
11
43
|
let input;
|
|
12
44
|
try {
|
|
13
45
|
input = JSON.parse(inputText);
|
|
@@ -17,6 +49,6 @@ export function runClaudeHook(inputText, env = process.env) {
|
|
|
17
49
|
}
|
|
18
50
|
if (input === null || typeof input !== 'object')
|
|
19
51
|
return '';
|
|
20
|
-
const response = claudeHookResponse(input, sessionInbox, env);
|
|
52
|
+
const response = await claudeHookResponse(input, sessionInbox, env);
|
|
21
53
|
return response === undefined ? '' : `${JSON.stringify(response)}\n`;
|
|
22
54
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type HardCap } from '../model.js';
|
|
2
|
+
export declare const DEFAULT_SQUARE_PATH = ".square/SQUARE.square";
|
|
3
|
+
export interface CommandContext {
|
|
4
|
+
squarePath: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
homeDir: string;
|
|
7
|
+
command: string;
|
|
8
|
+
}
|
|
9
|
+
export interface CommandSpec<Intent = unknown, Result = void> {
|
|
10
|
+
parse(argv: string[], context: CommandContext): Intent;
|
|
11
|
+
execute(intent: Intent, context: CommandContext): Promise<Result> | Result;
|
|
12
|
+
present(result: Result, context: CommandContext): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function readStdinSync(): string;
|
|
15
|
+
export declare function resolveBody(arg: string): string;
|
|
16
|
+
export declare function readPipedBodyFallback(): string | undefined;
|
|
17
|
+
export declare function fail(message: string, exitCode?: number): never;
|
|
18
|
+
export declare function usage(command: string): never;
|
|
19
|
+
export declare function requireValue(args: string[], index: number, flag: string): string;
|
|
20
|
+
export declare function parseDurationMs(value: string, flag: string): number;
|
|
21
|
+
export declare function parsePositiveInteger(value: string, flag: string): number;
|
|
22
|
+
export declare function parseNonNegativeInteger(value: string, flag: string): number;
|
|
23
|
+
export declare function parseHardCap(value: string): HardCap;
|
|
24
|
+
export declare function parseNameList(value: string, flag: string): string[];
|
|
25
|
+
export declare function requireParticipant(name: string | undefined): string;
|
|
26
|
+
export interface ParsedGlobalArgs {
|
|
27
|
+
squarePath: string;
|
|
28
|
+
explicitSquarePath: boolean;
|
|
29
|
+
multipleSquares: boolean;
|
|
30
|
+
name?: string;
|
|
31
|
+
args: string[];
|
|
32
|
+
}
|
|
33
|
+
export declare function parseGlobalArgs(rawArgs: string[]): ParsedGlobalArgs;
|
|
34
|
+
export declare function locationIsRequired(command: string): boolean;
|
|
35
|
+
export declare function defaultContext(command: string, squarePath: string, name?: string): CommandContext;
|
package/dist/cli/context.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { loadSquare } from '../artifact.js';
|
|
5
3
|
import { commandUsageHint } from '../help.js';
|
|
6
4
|
import { parseParticipantList, validateName } from '../model.js';
|
|
7
|
-
|
|
5
|
+
import { localParticipantName } from '../registry.js';
|
|
6
|
+
export const DEFAULT_SQUARE_PATH = '.square/SQUARE.square';
|
|
8
7
|
export function readStdinSync() {
|
|
9
8
|
try {
|
|
10
9
|
return fs.readFileSync(0, 'utf8');
|
|
@@ -88,30 +87,17 @@ export function requireParticipant(name) {
|
|
|
88
87
|
validateName(name);
|
|
89
88
|
return name;
|
|
90
89
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
103
|
-
continue;
|
|
104
|
-
const fullPath = path.join(directory, entry.name);
|
|
105
|
-
try {
|
|
106
|
-
const doc = loadSquare(fullPath);
|
|
107
|
-
candidates.push({ relPath: path.relative(process.cwd(), fullPath), at: doc.acts.at(-1)?.at ?? fs.statSync(fullPath).mtimeMs });
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
// Unreadable artifacts are not implicit command targets.
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
candidates.sort((a, b) => b.at - a.at);
|
|
114
|
-
return { path: candidates[0]?.relPath ?? DEFAULT_SQUARE_PATH, multiple: candidates.length > 1 };
|
|
90
|
+
const LOCATION_REQUIRED_COMMANDS = new Set(['join', 'catch', 'express', 'done', 'hold', 'resume']);
|
|
91
|
+
function configuredLocation() {
|
|
92
|
+
const value = process.env.SQUARE_LOCATION?.trim();
|
|
93
|
+
return value === '' ? undefined : value;
|
|
94
|
+
}
|
|
95
|
+
function configuredName() {
|
|
96
|
+
const value = process.env.SQUARE_PARTICIPANT_NAME?.trim();
|
|
97
|
+
if (!value)
|
|
98
|
+
return undefined;
|
|
99
|
+
validateName(value);
|
|
100
|
+
return value;
|
|
115
101
|
}
|
|
116
102
|
export function parseGlobalArgs(rawArgs) {
|
|
117
103
|
const args = [...rawArgs];
|
|
@@ -129,14 +115,23 @@ export function parseGlobalArgs(rawArgs) {
|
|
|
129
115
|
index -= 1;
|
|
130
116
|
}
|
|
131
117
|
}
|
|
118
|
+
if (name === undefined)
|
|
119
|
+
name = configuredName();
|
|
132
120
|
if (name !== undefined)
|
|
133
121
|
validateName(name);
|
|
134
122
|
const explicitSquarePath = requestedPath !== undefined;
|
|
135
123
|
const command = args[0];
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
124
|
+
const configured = configuredLocation();
|
|
125
|
+
if (command !== undefined && !['--help', '-h'].includes(command) && locationIsRequired(command) && requestedPath === undefined && configured === undefined) {
|
|
126
|
+
fail(`✕ ${command} needs a square location\n» square ls`);
|
|
127
|
+
}
|
|
128
|
+
const squarePath = requestedPath ?? configured ?? DEFAULT_SQUARE_PATH;
|
|
129
|
+
if (name === undefined && command !== undefined && locationIsRequired(command))
|
|
130
|
+
name = localParticipantName(squarePath);
|
|
131
|
+
return { squarePath, explicitSquarePath: explicitSquarePath || configured !== undefined, multipleSquares: false, name, args };
|
|
132
|
+
}
|
|
133
|
+
export function locationIsRequired(command) {
|
|
134
|
+
return LOCATION_REQUIRED_COMMANDS.has(command);
|
|
140
135
|
}
|
|
141
136
|
export function defaultContext(command, squarePath, name) {
|
|
142
137
|
return { command, squarePath, name, homeDir: os.homedir() };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { executeHarnessTarget, type HarnessAction } from '../harness.js';
|
|
2
|
+
import { type CommandSpec } from './context.js';
|
|
3
|
+
export type HarnessCommandAction = HarnessAction;
|
|
4
|
+
export interface ParsedHarnessCommand {
|
|
5
|
+
target?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ParsedInstallCommand {
|
|
8
|
+
targets: string[];
|
|
9
|
+
all: boolean;
|
|
10
|
+
force: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface HarnessCommandResult {
|
|
13
|
+
lines: string[];
|
|
14
|
+
notes: string[];
|
|
15
|
+
failures: string[];
|
|
16
|
+
}
|
|
17
|
+
type HarnessTargetExecutor = typeof executeHarnessTarget;
|
|
18
|
+
export declare function executeTargetBatch(targets: string[], action: 'install' | 'uninstall', context: {
|
|
19
|
+
homeDir: string;
|
|
20
|
+
squarePath?: string;
|
|
21
|
+
force: boolean;
|
|
22
|
+
}, executeTarget?: HarnessTargetExecutor): Promise<HarnessCommandResult>;
|
|
23
|
+
export declare const installCommand: CommandSpec<ParsedInstallCommand, HarnessCommandResult>;
|
|
24
|
+
export declare const uninstallCommand: CommandSpec<ParsedInstallCommand, HarnessCommandResult>;
|
|
25
|
+
export declare const harnessCommand: CommandSpec<ParsedHarnessCommand, HarnessCommandResult>;
|
|
26
|
+
export declare function formatHarnessResult(result: HarnessCommandResult): string;
|
|
27
|
+
export declare function runHarnessCommand(argv: string[], squarePath?: string): Promise<string>;
|
|
28
|
+
export {};
|
|
@@ -26,24 +26,38 @@ function parseInstall(argv, action) {
|
|
|
26
26
|
throw new Error(`Cannot combine ${action} --all with named targets`);
|
|
27
27
|
return { targets, all, force };
|
|
28
28
|
}
|
|
29
|
+
export async function executeTargetBatch(targets, action, context, executeTarget = executeHarnessTarget) {
|
|
30
|
+
const results = [];
|
|
31
|
+
const failures = [];
|
|
32
|
+
for (const target of targets) {
|
|
33
|
+
try {
|
|
34
|
+
results.push(await executeTarget(target, action, context));
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
failures.push(`✕ ${target} ${action} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
notes: results.flatMap((result) => result.notes),
|
|
42
|
+
lines: results.flatMap((result) => result.lines),
|
|
43
|
+
failures,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
29
46
|
function targetCommand(action) {
|
|
30
47
|
return {
|
|
31
48
|
parse(argv) { return parseInstall(argv, action); },
|
|
32
49
|
async execute(intent, context) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
lines: results.flatMap((result) => result.lines),
|
|
44
|
-
};
|
|
50
|
+
return executeTargetBatch(installTargets(intent, action), action, {
|
|
51
|
+
homeDir: context.homeDir,
|
|
52
|
+
squarePath: context.squarePath,
|
|
53
|
+
force: intent.force,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
present(result) {
|
|
57
|
+
process.stdout.write(formatHarnessResult(result));
|
|
58
|
+
if (result.failures.length > 0)
|
|
59
|
+
process.exitCode = 1;
|
|
45
60
|
},
|
|
46
|
-
present(result) { process.stdout.write(formatHarnessResult(result)); },
|
|
47
61
|
};
|
|
48
62
|
}
|
|
49
63
|
export const installCommand = targetCommand('install');
|
|
@@ -77,6 +91,7 @@ export const harnessCommand = {
|
|
|
77
91
|
return {
|
|
78
92
|
notes: results.flatMap((result) => result.notes),
|
|
79
93
|
lines: results.flatMap((result) => result.lines),
|
|
94
|
+
failures: [],
|
|
80
95
|
};
|
|
81
96
|
},
|
|
82
97
|
present(result) {
|
|
@@ -84,10 +99,10 @@ export const harnessCommand = {
|
|
|
84
99
|
},
|
|
85
100
|
};
|
|
86
101
|
export function formatHarnessResult(result) {
|
|
87
|
-
return `${[...result.notes, ...result.lines].join('\n')}\n`;
|
|
102
|
+
return `${[...result.notes, ...result.lines, ...result.failures].join('\n')}\n`;
|
|
88
103
|
}
|
|
89
104
|
export function runHarnessCommand(argv, squarePath) {
|
|
90
|
-
const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.
|
|
105
|
+
const context = { homeDir: os.homedir(), squarePath: squarePath ?? '.square/SQUARE.square', command: 'harness' };
|
|
91
106
|
const intent = harnessCommand.parse(argv, context);
|
|
92
107
|
return Promise.resolve(harnessCommand.execute(intent, context)).then(formatHarnessResult);
|
|
93
108
|
}
|
|
@@ -1,82 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { renderDoctorClean, renderDoctorProblems, renderDoctorRepaired, renderDoctorUnfixable, withPathOutput, } from '../presentation.js';
|
|
1
|
+
import { diagnoseSquareFile } from '../square-storage.js';
|
|
2
|
+
import { renderDoctorClean, renderDoctorUnfixable, withPathOutput } from '../presentation.js';
|
|
4
3
|
import { inSquareCount } from '../runtime.js';
|
|
5
|
-
import { repairSquare } from '../square-application.js';
|
|
6
|
-
import { SquareError } from '../model.js';
|
|
7
|
-
import { pruneRegistry } from '../registry.js';
|
|
8
4
|
import { usage } from './context.js';
|
|
9
|
-
function readSquareText(squarePath) {
|
|
10
|
-
try {
|
|
11
|
-
return fs.readFileSync(squarePath, 'utf8');
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
if (error.code === 'ENOENT')
|
|
15
|
-
throw new SquareError('not_found', `square file not found: ${squarePath}`);
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function quarantinePath(squarePath) {
|
|
20
|
-
return squarePath.replace(/\.md$/, '') + '.quarantine.md';
|
|
21
|
-
}
|
|
22
|
-
function registryActs(squarePath) {
|
|
23
|
-
if (!fs.existsSync(squarePath))
|
|
24
|
-
return [];
|
|
25
|
-
try {
|
|
26
|
-
return loadSquare(squarePath).acts;
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
// A temporarily unreadable artifact cannot disprove a cache binding.
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
5
|
export const doctorCommand = {
|
|
34
6
|
parse(argv, context) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (argument === '--fix')
|
|
39
|
-
fix = true;
|
|
40
|
-
else if (argument === '--before') {
|
|
41
|
-
index += 1;
|
|
42
|
-
if (argv[index] === undefined)
|
|
43
|
-
usage(context.command);
|
|
44
|
-
}
|
|
45
|
-
else
|
|
46
|
-
usage(context.command);
|
|
47
|
-
}
|
|
48
|
-
return { fix };
|
|
7
|
+
if (argv.length > 0)
|
|
8
|
+
usage(context.command);
|
|
9
|
+
return undefined;
|
|
49
10
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (diagnosis.unfixable) {
|
|
54
|
-
return {
|
|
55
|
-
output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
56
|
-
exitCode: 2,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const summary = diagnosis.problems.length === 0 ? renderDoctorClean() : renderDoctorProblems(diagnosis.problems);
|
|
11
|
+
execute(_intent, context) {
|
|
12
|
+
const diagnosis = diagnoseSquareFile(context.squarePath);
|
|
13
|
+
if (diagnosis.unfixable !== undefined || diagnosis.state === undefined) {
|
|
60
14
|
return {
|
|
61
|
-
output: withPathOutput(context.squarePath,
|
|
62
|
-
exitCode: diagnosis.problems.length === 0 ? 0 : 1,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
const repair = await repairSquare(context.squarePath);
|
|
66
|
-
if (repair.diagnosis.unfixable) {
|
|
67
|
-
return {
|
|
68
|
-
output: withPathOutput(context.squarePath, renderDoctorUnfixable(repair.diagnosis.unfixable), { participantCount: inSquareCount(loadSquare(context.squarePath)) }),
|
|
15
|
+
output: withPathOutput(context.squarePath, renderDoctorUnfixable(diagnosis.unfixable ?? 'the snapshot could not be decoded')),
|
|
69
16
|
exitCode: 2,
|
|
70
17
|
};
|
|
71
18
|
}
|
|
72
|
-
const repaired = repair.repaired;
|
|
73
|
-
const registry = pruneRegistry(registryActs);
|
|
74
|
-
if (registry.removed > 0) {
|
|
75
|
-
repaired.actions.push({ message: `pruned ${registry.removed} obsolete registry membership(s)` });
|
|
76
|
-
}
|
|
77
|
-
const sidecar = quarantinePath(context.squarePath);
|
|
78
19
|
return {
|
|
79
|
-
output: withPathOutput(context.squarePath,
|
|
20
|
+
output: withPathOutput(context.squarePath, renderDoctorClean(), {
|
|
21
|
+
participantCount: inSquareCount(diagnosis.state),
|
|
22
|
+
}),
|
|
80
23
|
};
|
|
81
24
|
},
|
|
82
25
|
present(result) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ActivitiesOptions, type WatchOptions } from '../model.js';
|
|
2
|
+
import { type CommandSpec } from './context.js';
|
|
3
|
+
export declare const listCommand: CommandSpec<string[]>;
|
|
4
|
+
interface StreamIntent {
|
|
5
|
+
ndjson: boolean;
|
|
6
|
+
forName?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const streamCommand: CommandSpec<StreamIntent>;
|
|
9
|
+
export declare const catchCommand: CommandSpec<WatchOptions>;
|
|
10
|
+
export declare const historyCommand: CommandSpec<ActivitiesOptions, string>;
|
|
11
|
+
export declare const participantsCommand: CommandSpec<undefined, string>;
|
|
12
|
+
export declare const statusCommand: CommandSpec<undefined, string>;
|
|
13
|
+
interface InboxIntent {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
json: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const inboxCommand: CommandSpec<InboxIntent, string>;
|
|
18
|
+
export declare const claudeHookCommand: CommandSpec<undefined, string>;
|
|
19
|
+
export declare const codexHookCommand: CommandSpec<undefined, string>;
|
|
20
|
+
export {};
|