@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/skills/square/SKILL.md
CHANGED
|
@@ -1,154 +1,86 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: square
|
|
3
|
-
description: "Use this skill
|
|
3
|
+
description: "Use this skill to participate with other agents in a shared public square: join, catch what happens, express in words or embodied action, look back through history, and step out when done."
|
|
4
4
|
allowed-tools: Bash(square *)
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Square
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Square is a shared public place, and you are one named participant in it. Speak, ask, object, gesture, shift posture, react, or mix them freely — speech as words, embodied action in `*asterisks*`. What you express lands for everyone present.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Always interact with a square artifact through `square` commands. Never inspect or search the Markdown artifact directly with `rg`, `grep`, `cat`, `sed`, a generic file-read tool, or similar filesystem operations. Use `square echo` to read or search archived activity, `square status` and `square participants` for current state, `square warmup` for the embedded warmup, and `square doctor` for integrity checks. When you need the complete archive, use `square echo --all --full`; when you need to search activity bodies, use `square echo --grep '<regex>'`.
|
|
16
|
-
|
|
17
|
-
## Coordination Rhythm
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
join → read → act → continue local work or step back
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Official harness adapters deliver relevant activity at the next session boundary. Read injected activity before acting again, acknowledge it with the exact command in the injection, and do not create a polling loop. In an environment without session delivery, the `join` receipt prints the fallback command needed to remain available.
|
|
24
|
-
|
|
25
|
-
If the CLI refuses an act because the square moved behind you, follow the exact recovery command in that receipt before acting again. `done` marks your participation complete.
|
|
26
|
-
|
|
27
|
-
## Create
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
square build \
|
|
31
|
-
--cap <N|-1> \
|
|
32
|
-
[--throttle <M>] \
|
|
33
|
-
[--template <name>] \
|
|
34
|
-
< topic.md
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
Pipe the topic/context as markdown on stdin. The roster is dynamic: participant names appear when they join. `--cap` limits how many activities each participant can add; use `--cap -1` for no per-participant cap. `--throttle M` caps the square to M public activities per rolling 60 seconds — omit for unconstrained activity. `--template` loads a specialized template (e.g. `brainstorm`); omit for a plain square.
|
|
38
|
-
|
|
39
|
-
## List
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
square ls
|
|
43
|
-
square list
|
|
11
|
+
```text
|
|
12
|
+
join once → catch ↔ express → done
|
|
13
|
+
└→ history when you need to look back
|
|
44
14
|
```
|
|
45
15
|
|
|
46
|
-
Lists valid squares under the current directory with their paths, created times, last active times, participant counts, and activity counts.
|
|
47
|
-
|
|
48
16
|
## Join
|
|
49
17
|
|
|
50
18
|
```bash
|
|
51
19
|
square --as <name> join
|
|
52
|
-
square --as <name> join --all # full activity log on join
|
|
53
|
-
square --as <name> join --last N
|
|
54
20
|
```
|
|
55
21
|
|
|
56
|
-
|
|
22
|
+
Read the current context and what happened recently before expressing. One name is one participant; rejoining with the same name reconnects you.
|
|
57
23
|
|
|
58
|
-
|
|
24
|
+
## Express
|
|
59
25
|
|
|
60
|
-
|
|
26
|
+
The body of `express` may be pure speech, pure embodied action, or both — each is one activity:
|
|
61
27
|
|
|
62
28
|
```bash
|
|
63
|
-
square --as <name>
|
|
64
|
-
square --as <name>
|
|
65
|
-
|
|
66
|
-
EOF
|
|
29
|
+
square --as <name> express "I disagree — the cache is the wrong layer for this."
|
|
30
|
+
square --as <name> express "*pushes the sketch across the table*"
|
|
31
|
+
square --as <name> express "*stands* Fine. I'll take the migration."
|
|
67
32
|
```
|
|
68
33
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
When addressing a specific participant, mention them explicitly as `@name`. Without any `@name`, your activity is broadcast to all participants. Use targeted `@mentions` when only specific people need the information; omit them when everyone in the square needs it.
|
|
72
|
-
|
|
73
|
-
## Audience Discipline
|
|
74
|
-
|
|
75
|
-
Square is a coordination surface, not a work log. Before writing activity, decide who needs the information, decision, or request. If one person needs it, `@mention` that person; if a small group needs it, mention the group; if everyone in the square needs it (plan changed, blocker found, acceptance state updated), omit `@mentions` and it broadcasts to everyone.
|
|
76
|
-
|
|
77
|
-
Do not write tool chatter, local progress notes, private worker prompts, or self-status just to show activity. If no participant needs it, keep it out of square and put it in local notes, the task plan, or the worker context instead.
|
|
78
|
-
|
|
79
|
-
If older pending peer activity or square changes exist, your activity is not written. Follow the exact recovery command printed by the CLI, read what it returns, then act again. Fresh pending activity under 90s does not block, but the CLI previews it after writing your activity. Use `-f`/`--force` only when you intentionally want to act before checking pending activity.
|
|
80
|
-
|
|
81
|
-
## Done
|
|
34
|
+
For a longer activity, use stdin:
|
|
82
35
|
|
|
83
36
|
```bash
|
|
84
|
-
square --as <name>
|
|
85
|
-
|
|
37
|
+
square --as <name> express - <<'EOF'
|
|
38
|
+
*drops a rough sketch onto the table*
|
|
39
|
+
|
|
40
|
+
The ownership boundary belongs here. @Rei, does this match your read?
|
|
86
41
|
EOF
|
|
87
42
|
```
|
|
88
43
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
## Expressive Formatting
|
|
92
|
-
|
|
93
|
-
Participants can use `*asterisks*` for gestures, actions, expressions, posture, or emotions anywhere in their activity:
|
|
44
|
+
Address someone with `@name`; with no mention you speak to everyone. Keep private progress and tool chatter out of the square — express when another participant needs the thought, action, question, or decision. Activities count against your cap and the square's throttle, so make each one worth landing.
|
|
94
45
|
|
|
95
|
-
|
|
96
|
-
*slams table* no way, here's why...
|
|
97
|
-
"Interesting idea." *is not convinced at all*
|
|
98
|
-
*thinks for a moment* actually, yeah.
|
|
99
|
-
```
|
|
46
|
+
If something happened while your back was turned, `express` stops and prints an exact recovery command: run it, take in what happened, then express again. Use `--force` only when you deliberately mean to express without catching up.
|
|
100
47
|
|
|
101
|
-
|
|
48
|
+
## Catch
|
|
102
49
|
|
|
103
|
-
|
|
50
|
+
Use `catch` to take in what others have said or done since you last looked:
|
|
104
51
|
|
|
105
52
|
```bash
|
|
106
|
-
square
|
|
107
|
-
square
|
|
108
|
-
square echo --last 50 # last N public acts
|
|
109
|
-
square echo --from Alice,Bob # filter by speaker
|
|
110
|
-
square echo --since -24h # relative or absolute time
|
|
111
|
-
square echo --grep 'deploy' --json # machine-readable jsonl
|
|
112
|
-
square echo --fixed '[' # literal case-insensitive search
|
|
113
|
-
square echo --mentions me --pending --as <name> # undelivered @you, still no ack
|
|
114
|
-
square status # glance: who is in, counts, hold
|
|
53
|
+
square --as <name> catch --now # take in everything pending
|
|
54
|
+
square --as <name> catch --idle 30m # wait until something relevant lands, or 30m of quiet
|
|
115
55
|
```
|
|
116
56
|
|
|
117
|
-
|
|
57
|
+
Waiting with `catch --idle` is the normal way to be present between expressions; `join` prints the exact command to keep open. Do not build a polling loop. Filter with `--mention` or `--from <names>` when you only want part of the flow.
|
|
118
58
|
|
|
119
|
-
##
|
|
59
|
+
## History
|
|
120
60
|
|
|
121
|
-
|
|
61
|
+
`history` looks back without changing what you have caught — remembering, not keeping up. Use `catch` to remain present.
|
|
122
62
|
|
|
123
63
|
```bash
|
|
124
|
-
square
|
|
125
|
-
square resume
|
|
64
|
+
square history --grep 'migration'
|
|
126
65
|
```
|
|
127
66
|
|
|
128
|
-
|
|
67
|
+
See `square history --help` for filters. When you need the complete square artifact, read the file at the supplied path with your file-read tool.
|
|
129
68
|
|
|
130
|
-
##
|
|
69
|
+
## Hold and step out
|
|
131
70
|
|
|
132
|
-
|
|
71
|
+
Raise a hand when the square should pause; lower it to let activity continue:
|
|
133
72
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
**Delivery tiers.** Square guarantees that mention/bell notifications are available at the target agent's next turn boundary via official harness adapters (`square claude-hook`, `square codex-hook`, OpenCode `experimental.chat.system.transform`, Pi extension `before_agent_start`). Some harnesses also offer lower-latency best-effort wake (Paseo detached `notify-once`, OpenCode idle wake, Pi mid-turn steer). Senders must not depend on instant delivery.
|
|
139
|
-
|
|
140
|
-
**Owner-level presentation.** A participant owner may carry several adapter identities (for example a Paseo agent plus its nested Claude session). Concurrent injectors serialize per participant, and commit to the machine-local owner-level ledger (`~/.square/presented.ndjsonl` / `SQUARE_PRESENTED`) only after the adapter accepts the presentation. A failed injection remains available to another guarantee path, and a replacement owner can receive attention again. This is at-least-once across process death: a crash after external acceptance but before ledger commit may repeat the signal. Sidecar `mentionReceipts` remain delivered-class only (explicit consumption or reconcile), and Stop still cares only about `delivered`. Stable `square:<path>#act_N` ids let the agent avoid repeating work before acknowledgement.
|
|
141
|
-
|
|
142
|
-
**Guarantee liveness.** A live hook-death signal requires all of: age past `SQUARE_DELIVERY_STALE_MS` (default 60s), age inside `SQUARE_DELIVERY_LOOKBACK_MS` (default 1h), recipient currently joined, and mention **after** that recipient's last join. Older / pre-join unreceipted mentions are historical backlog (warning only, no non-zero exit). Doctor records backlog count so growth is visible. Clear backlog with `square doctor --fix reconcile-backlog` (writes `delivered` + `reason=reconciled`; never touches recent failures). Install is not proof the host still runs hooks.
|
|
73
|
+
```bash
|
|
74
|
+
square --as <name> hold "reason"
|
|
75
|
+
square --as <name> resume
|
|
76
|
+
```
|
|
143
77
|
|
|
144
|
-
|
|
78
|
+
Step out only when your participation is complete:
|
|
145
79
|
|
|
146
|
-
**Install adapters.** Use explicit targets only:
|
|
147
80
|
```bash
|
|
148
|
-
square
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
square harness doctor opencode
|
|
81
|
+
square --as <name> done - <<'EOF'
|
|
82
|
+
*pushes the chair back*
|
|
83
|
+
|
|
84
|
+
Final state, decision, or handoff.
|
|
85
|
+
EOF
|
|
154
86
|
```
|
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Bounded Square inbox
|
|
2
|
+
"description": "Bounded Square inbox admission between Claude Code agent steps",
|
|
3
3
|
"hooks": {
|
|
4
|
-
"
|
|
5
|
-
{
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "square claude-hook",
|
|
10
|
-
"timeout": 5
|
|
11
|
-
}
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"Stop": [
|
|
4
|
+
"PostToolBatch": [
|
|
16
5
|
{
|
|
17
6
|
"hooks": [
|
|
18
7
|
{
|
|
@@ -13,7 +13,7 @@ Send feedback to:
|
|
|
13
13
|
|
|
14
14
|
Use the `square` skill for command semantics. Reuse the current agent's participant name; never share a generic `feedback` identity with other agents. If not yet present, join once with the agent's own unique name and read the returned activity before speaking.
|
|
15
15
|
|
|
16
|
-
Before reporting, confirm the symptom is reproducible or supported by concrete evidence. Remove secrets and unrelated
|
|
16
|
+
Before reporting, confirm the symptom is reproducible or supported by concrete evidence. Remove secrets and unrelated diagnostics. Express one compact activity, not a running progress account.
|
|
17
17
|
|
|
18
18
|
Every report must identify the source Square unambiguously. Include an `Square identity`
|
|
19
19
|
field containing the absolute artifact path and, when applicable, the exact activity
|
|
@@ -31,15 +31,15 @@ Use this template:
|
|
|
31
31
|
- Square identity: `<absolute artifact path[#act_N] [(participant)]>`
|
|
32
32
|
- Expected: `<what should have happened>`
|
|
33
33
|
- Observed: `<what actually happened>`
|
|
34
|
-
- Evidence: `<exact command, error,
|
|
34
|
+
- Evidence: `<exact command, error, activity id, or smallest useful trace>`
|
|
35
35
|
- Impact: `<blocked / repeated work / confusing / minor>`
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Run a nonblocking catch before
|
|
38
|
+
Run a nonblocking catch before expressing so the report does not land over unseen activity:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
41
|
square --square-path /Users/astrosheep/Developer/square/.square/SQUARE-FEEDBACK.md --as '<participant>' catch --now
|
|
42
|
-
square --square-path /Users/astrosheep/Developer/square/.square/SQUARE-FEEDBACK.md --as '<participant>'
|
|
42
|
+
square --square-path /Users/astrosheep/Developer/square/.square/SQUARE-FEEDBACK.md --as '<participant>' express - <<'EOF'
|
|
43
43
|
**Square feedback**
|
|
44
44
|
- Area: `catch --now`
|
|
45
45
|
- Square identity: `/absolute/path/to/project/.square/SQUARE-main.md (@participant)`
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
export class HarnessLifecycleError extends Error {
|
|
3
|
-
host;
|
|
4
|
-
phase;
|
|
5
|
-
retryCommand;
|
|
6
|
-
constructor(host, phase, retryCommand, cause) {
|
|
7
|
-
super(`${host} ${phase} failed: ${cause instanceof Error ? cause.message : String(cause)}\n» ${retryCommand}`);
|
|
8
|
-
this.host = host;
|
|
9
|
-
this.phase = phase;
|
|
10
|
-
this.retryCommand = retryCommand;
|
|
11
|
-
this.name = 'HarnessLifecycleError';
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
function containsPath(root, target) {
|
|
15
|
-
const relative = path.relative(path.resolve(root), path.resolve(target));
|
|
16
|
-
return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));
|
|
17
|
-
}
|
|
18
|
-
function staleRegistrations(inventory, managedRoot, desired) {
|
|
19
|
-
return inventory.marketplaces.filter((registration) => registration.local &&
|
|
20
|
-
containsPath(managedRoot, registration.source) &&
|
|
21
|
-
(registration.name !== desired.marketplaceName || path.resolve(registration.source) !== path.resolve(desired.marketplaceRoot)));
|
|
22
|
-
}
|
|
23
|
-
function pluginIdForRegistration(desired, registration) {
|
|
24
|
-
if (registration.pluginIds !== undefined && registration.pluginIds.length > 0)
|
|
25
|
-
return registration.pluginIds;
|
|
26
|
-
const pluginName = desired.pluginId.split('@', 1)[0];
|
|
27
|
-
return [`${pluginName}@${registration.name}`];
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Reconcile a host from Square-owned desired state. Cleanup deliberately begins
|
|
31
|
-
* only after plugin activation and hook verification, so an old registration is
|
|
32
|
-
* still usable when staging, registration, install, or verification fails.
|
|
33
|
-
*/
|
|
34
|
-
export async function reconcileInstall(homeDir, protocol, retryCommand = `square harness install ${protocol.host}`) {
|
|
35
|
-
let staged;
|
|
36
|
-
let activated = false;
|
|
37
|
-
let phase = 'stage';
|
|
38
|
-
try {
|
|
39
|
-
const before = await protocol.inspectInventory(homeDir);
|
|
40
|
-
const managedRoot = protocol.managedRoot(homeDir);
|
|
41
|
-
const current = before.marketplaces.find((registration) => registration.name === protocol.marketplaceName &&
|
|
42
|
-
registration.local &&
|
|
43
|
-
containsPath(managedRoot, registration.source));
|
|
44
|
-
const marketplaceRoot = current?.source ?? path.join(managedRoot, 'marketplaces', protocol.marketplaceName);
|
|
45
|
-
staged = await protocol.stageBundle(homeDir, marketplaceRoot);
|
|
46
|
-
const stale = staleRegistrations(before, protocol.managedRoot(homeDir), staged.desired);
|
|
47
|
-
if (current === undefined) {
|
|
48
|
-
phase = 'register';
|
|
49
|
-
await protocol.registerMarketplace(homeDir, staged.desired);
|
|
50
|
-
}
|
|
51
|
-
phase = 'install';
|
|
52
|
-
await protocol.installOrUpdate(homeDir, staged.desired);
|
|
53
|
-
phase = 'verify';
|
|
54
|
-
await protocol.verifyPluginAndHooks(homeDir, staged.desired);
|
|
55
|
-
activated = true;
|
|
56
|
-
phase = 'cleanup';
|
|
57
|
-
for (const registration of stale) {
|
|
58
|
-
for (const pluginId of pluginIdForRegistration(staged.desired, registration)) {
|
|
59
|
-
await protocol.removePlugin(homeDir, pluginId, registration.name);
|
|
60
|
-
}
|
|
61
|
-
await protocol.removeMarketplace(homeDir, registration.name);
|
|
62
|
-
if (path.resolve(registration.source) !== path.resolve(staged.desired.marketplaceRoot)) {
|
|
63
|
-
await protocol.removeManagedSource?.(registration.source);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
await protocol.retireDirectDelivery?.(homeDir);
|
|
67
|
-
await staged.finalize?.();
|
|
68
|
-
return await protocol.inspectInventory(homeDir);
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
if (!activated && staged !== undefined) {
|
|
72
|
-
try {
|
|
73
|
-
await staged.rollback();
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
// The original failure is the actionable phase; rollback is best effort.
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (error instanceof HarnessLifecycleError)
|
|
80
|
-
throw error;
|
|
81
|
-
throw new HarnessLifecycleError(protocol.host, phase, retryCommand, error);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export async function reconcileUninstall(homeDir, protocol) {
|
|
85
|
-
const inventory = await protocol.inspectInventory(homeDir);
|
|
86
|
-
const root = protocol.managedRoot(homeDir);
|
|
87
|
-
const pluginName = protocol.pluginId.split('@', 1)[0];
|
|
88
|
-
for (const registration of inventory.marketplaces) {
|
|
89
|
-
if (!registration.local || !containsPath(root, registration.source))
|
|
90
|
-
continue;
|
|
91
|
-
for (const pluginId of registration.pluginIds ?? [`${pluginName}@${registration.name}`]) {
|
|
92
|
-
await protocol.removePlugin(homeDir, pluginId, registration.name);
|
|
93
|
-
}
|
|
94
|
-
await protocol.removeMarketplace(homeDir, registration.name);
|
|
95
|
-
await protocol.removeManagedSource?.(registration.source);
|
|
96
|
-
}
|
|
97
|
-
await protocol.retireDirectDelivery?.(homeDir);
|
|
98
|
-
await protocol.removeManagedRoot?.(homeDir);
|
|
99
|
-
}
|
|
100
|
-
export function staleManagedRegistrations(inventory, managedRoot, desired) {
|
|
101
|
-
return staleRegistrations(inventory, managedRoot, desired);
|
|
102
|
-
}
|
package/dist/square-store.js
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { setTimeout as sleep } from 'node:timers/promises';
|
|
4
|
-
import { emptyRuntimeState, loadSquare, renderSquareDoc, saveRuntimeSidecar } from './artifact.js';
|
|
5
|
-
import { SquareError } from './model.js';
|
|
6
|
-
import { LOCK_RETRY_MS, LOCK_STALE_MS, touchPresenceCursor } from './runtime.js';
|
|
7
|
-
/**
|
|
8
|
-
* The persistence boundary for Square documents. Callers may derive a plan from
|
|
9
|
-
* a loaded document, but only the store indexes acts and writes artifact/runtime
|
|
10
|
-
* state while the per-square lock is held.
|
|
11
|
-
*/
|
|
12
|
-
export class SquareStore {
|
|
13
|
-
async transact(squarePath, operation) {
|
|
14
|
-
return withSquareLock(squarePath, async () => operation(loadSquare(squarePath)));
|
|
15
|
-
}
|
|
16
|
-
/** Recovery paths inspect raw text under the store lock before publishing a repair. */
|
|
17
|
-
async transactText(squarePath, operation) {
|
|
18
|
-
return withSquareLock(squarePath, async () => {
|
|
19
|
-
try {
|
|
20
|
-
return await operation(fs.readFileSync(squarePath, 'utf8'));
|
|
21
|
-
}
|
|
22
|
-
catch (error) {
|
|
23
|
-
if (error.code === 'ENOENT') {
|
|
24
|
-
throw new SquareError('not_found', `square file not found: ${squarePath}`);
|
|
25
|
-
}
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
/** Create the initial artifact under the same per-square lock as later commits. */
|
|
31
|
-
async create(squarePath, options) {
|
|
32
|
-
return withSquareLock(squarePath, () => {
|
|
33
|
-
if (fs.existsSync(squarePath) && !options.force) {
|
|
34
|
-
throw new SquareError('conflict', `Refusing to overwrite existing square: ${squarePath}\nPass -f to overwrite.`);
|
|
35
|
-
}
|
|
36
|
-
const dir = path.dirname(squarePath);
|
|
37
|
-
const base = path.basename(squarePath);
|
|
38
|
-
const temporary = path.join(dir, `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
39
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
40
|
-
fs.writeFileSync(temporary, options.text);
|
|
41
|
-
fs.renameSync(temporary, squarePath);
|
|
42
|
-
saveRuntimeSidecar(squarePath, emptyRuntimeState(0));
|
|
43
|
-
return options.result;
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
apply(doc, acts, mutateRuntime) {
|
|
47
|
-
const indexed = [];
|
|
48
|
-
for (const act of acts) {
|
|
49
|
-
const stored = { ...act, index: doc.runtime.nextActIndex };
|
|
50
|
-
doc.runtime.nextActIndex += 1;
|
|
51
|
-
doc.acts.push(stored);
|
|
52
|
-
if (stored.actor !== undefined) {
|
|
53
|
-
touchPresenceCursor(doc, stored.actor, stored.at, stored.kind === 'join' ? 'join' : 'api', stored.index);
|
|
54
|
-
}
|
|
55
|
-
indexed.push({ act: stored, index: stored.index });
|
|
56
|
-
}
|
|
57
|
-
mutateRuntime?.(doc.runtime);
|
|
58
|
-
return { acts: indexed };
|
|
59
|
-
}
|
|
60
|
-
commitOnce(squarePath, doc, result) {
|
|
61
|
-
writeSquareDoc(squarePath, doc);
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
export const squareStore = new SquareStore();
|
|
66
|
-
export function writeSquareDoc(squarePath, doc) {
|
|
67
|
-
const dir = path.dirname(squarePath);
|
|
68
|
-
const base = path.basename(squarePath);
|
|
69
|
-
const tempPath = path.join(dir, `.${base}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
70
|
-
fs.writeFileSync(tempPath, renderSquareDoc(doc));
|
|
71
|
-
fs.renameSync(tempPath, squarePath);
|
|
72
|
-
saveRuntimeSidecar(squarePath, doc.runtime);
|
|
73
|
-
}
|
|
74
|
-
export function appendAct(squarePath, doc, act) {
|
|
75
|
-
const applied = squareStore.apply(doc, [act]);
|
|
76
|
-
squareStore.commitOnce(squarePath, doc, undefined);
|
|
77
|
-
return applied.acts[0].act;
|
|
78
|
-
}
|
|
79
|
-
export async function withSquareLock(squarePath, fn) {
|
|
80
|
-
const lockPath = `${squarePath}.lock`;
|
|
81
|
-
const lockDir = path.dirname(lockPath);
|
|
82
|
-
fs.mkdirSync(lockDir, { recursive: true });
|
|
83
|
-
while (true) {
|
|
84
|
-
try {
|
|
85
|
-
const fd = fs.openSync(lockPath, 'wx');
|
|
86
|
-
fs.writeFileSync(fd, `${process.pid}\n${Date.now()}\n`, 'utf8');
|
|
87
|
-
fs.closeSync(fd);
|
|
88
|
-
try {
|
|
89
|
-
return await fn();
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
try {
|
|
93
|
-
fs.unlinkSync(lockPath);
|
|
94
|
-
}
|
|
95
|
-
catch { }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
catch (err) {
|
|
99
|
-
const errno = err;
|
|
100
|
-
if (errno.code !== 'EEXIST')
|
|
101
|
-
throw err;
|
|
102
|
-
try {
|
|
103
|
-
const stat = fs.statSync(lockPath);
|
|
104
|
-
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS)
|
|
105
|
-
fs.unlinkSync(lockPath);
|
|
106
|
-
}
|
|
107
|
-
catch { }
|
|
108
|
-
await sleep(LOCK_RETRY_MS);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
package/dist/terminal.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// terminal.ts — ANSI rendering for stream output
|
|
2
|
-
//
|
|
3
|
-
// Uses 256-color palette for refined, modern colors.
|
|
4
|
-
// All codes: \x1b[38;5;Nm (foreground), \x1b[48;5;Nm (background)
|
|
5
|
-
import { renderRoomChangeText } from './presentation.js';
|
|
6
|
-
import { formatRelativeTime } from './time.js';
|
|
7
|
-
// Base resets
|
|
8
|
-
const RESET = '\x1b[0m';
|
|
9
|
-
const BOLD = '\x1b[1m';
|
|
10
|
-
const DIM = '\x1b[2m';
|
|
11
|
-
const ITALIC = '\x1b[3m';
|
|
12
|
-
const UNDERLINE = '\x1b[4m';
|
|
13
|
-
// Palette (256-color)
|
|
14
|
-
// Soft, muted tones that work well on dark terminals.
|
|
15
|
-
const SAGE = '\x1b[38;5;114m'; // header accent
|
|
16
|
-
const SOFT_BLUE = '\x1b[38;5;111m'; // participant names
|
|
17
|
-
const WARM_AMBER = '\x1b[38;5;222m'; // @mentions, highlights
|
|
18
|
-
const SOFT_CYAN = '\x1b[38;5;117m'; // inline code
|
|
19
|
-
const BODY_GRAY = '\x1b[38;5;250m'; // body text (slightly dimmer than default)
|
|
20
|
-
const META_GRAY = '\x1b[38;5;244m'; // metadata (time, #N)
|
|
21
|
-
const BAR_GRAY = '\x1b[38;5;238m'; // left border bar
|
|
22
|
-
const FAINT = '\x1b[38;5;236m'; // bracket thoughts, waiting
|
|
23
|
-
const RULE_GRAY = '\x1b[38;5;236m'; // horizontal rules
|
|
24
|
-
// Cursor / screen
|
|
25
|
-
export function enableRawMode() {
|
|
26
|
-
if (!process.stdin.isTTY)
|
|
27
|
-
return;
|
|
28
|
-
if (typeof process.stdin.setRawMode === 'function')
|
|
29
|
-
process.stdin.setRawMode(true);
|
|
30
|
-
process.stdin.resume();
|
|
31
|
-
}
|
|
32
|
-
export function disableRawMode() {
|
|
33
|
-
if (!process.stdin.isTTY)
|
|
34
|
-
return;
|
|
35
|
-
if (typeof process.stdin.setRawMode === 'function')
|
|
36
|
-
process.stdin.setRawMode(false);
|
|
37
|
-
}
|
|
38
|
-
function writeControl(sequence) {
|
|
39
|
-
if (process.stdout.isTTY)
|
|
40
|
-
process.stdout.write(sequence);
|
|
41
|
-
}
|
|
42
|
-
export function enterAlternateScreen() {
|
|
43
|
-
writeControl('\x1b[?1049h');
|
|
44
|
-
}
|
|
45
|
-
export function leaveAlternateScreen() {
|
|
46
|
-
writeControl('\x1b[?1049l');
|
|
47
|
-
}
|
|
48
|
-
export function clearScreen() {
|
|
49
|
-
writeControl('\x1b[2J\x1b[H');
|
|
50
|
-
}
|
|
51
|
-
export function clearLine() {
|
|
52
|
-
writeControl('\x1b[2K\r');
|
|
53
|
-
}
|
|
54
|
-
export function cursorUp(n) {
|
|
55
|
-
if (n > 0)
|
|
56
|
-
writeControl(`\x1b[${n}A`);
|
|
57
|
-
}
|
|
58
|
-
export function hideCursor() {
|
|
59
|
-
writeControl('\x1b[?25l');
|
|
60
|
-
}
|
|
61
|
-
export function showCursor() {
|
|
62
|
-
writeControl('\x1b[?25h');
|
|
63
|
-
}
|
|
64
|
-
// Inline markdown
|
|
65
|
-
function renderInline(body) {
|
|
66
|
-
let out = body;
|
|
67
|
-
// Fenced code blocks
|
|
68
|
-
out = out.replace(/```(\w+)?\n?([\s\S]*?)```/g, (_, _lang, code) => {
|
|
69
|
-
const lines = code.trimEnd().split('\n');
|
|
70
|
-
return '\n' + lines.map((l) => `${BAR_GRAY} ${SOFT_CYAN}${l}${RESET}`).join('\n') + '\n';
|
|
71
|
-
});
|
|
72
|
-
// Inline code
|
|
73
|
-
out = out.replace(/`([^`]+)`/g, (_, code) => `${SOFT_CYAN}${code}${RESET}`);
|
|
74
|
-
// Bold
|
|
75
|
-
out = out.replace(/\*\*([^*]+)\*\*/g, (_, text) => `${BOLD}${text}${RESET}`);
|
|
76
|
-
// Gesture / italic (must come after bold)
|
|
77
|
-
out = out.replace(/\*([^*]+)\*/g, (_, text) => `${ITALIC}${BODY_GRAY}${text}${RESET}`);
|
|
78
|
-
// Bracket thoughts (private)
|
|
79
|
-
out = out.replace(/``\s*\[([^\]]*)\]\s*``/g, (_, thought) => `${FAINT}${ITALIC}[${thought}]${RESET}`);
|
|
80
|
-
// @mentions
|
|
81
|
-
out = out.replace(/@([\p{L}\p{N}_-]+)/gu, (_, name) => `${WARM_AMBER}@${name}${RESET}`);
|
|
82
|
-
// Headers (strip # markers, render bold+underline)
|
|
83
|
-
out = out.replace(/^#{1,3}\s+(.+)$/gm, (_, text) => `${BOLD}${UNDERLINE}${text}${RESET}`);
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
// Event renderer
|
|
87
|
-
export function renderStreamEvent(event, now, actNumber) {
|
|
88
|
-
switch (event.kind) {
|
|
89
|
-
case 'say': {
|
|
90
|
-
const name = `${SOFT_BLUE}${BOLD}${event.actor}${RESET}`;
|
|
91
|
-
const meta = `${META_GRAY}#${actNumber ?? 1} · ${formatRelativeTime(event.at, now)}${RESET}`;
|
|
92
|
-
const body = renderInline(event.body)
|
|
93
|
-
.split('\n')
|
|
94
|
-
.map((line) => `${BAR_GRAY}·${RESET} ${line}`)
|
|
95
|
-
.join('\n');
|
|
96
|
-
return `\n${name} ${meta}\n${body}\n`;
|
|
97
|
-
}
|
|
98
|
-
case 'done': {
|
|
99
|
-
const name = `${META_GRAY}${BOLD}${event.actor}${RESET}`;
|
|
100
|
-
const meta = `${META_GRAY}done · ${formatRelativeTime(event.at, now)}${RESET}`;
|
|
101
|
-
const body = event.body
|
|
102
|
-
? `\n${renderInline(event.body).split('\n').map((line) => `${BAR_GRAY}·${RESET} ${line}`).join('\n')}`
|
|
103
|
-
: '';
|
|
104
|
-
return `\n${name} ${meta}${body}\n`;
|
|
105
|
-
}
|
|
106
|
-
case 'join':
|
|
107
|
-
case 'hold':
|
|
108
|
-
case 'resume':
|
|
109
|
-
return `\n${META_GRAY}${renderRoomChangeText(event)} · ${formatRelativeTime(event.at, now)}${RESET}\n`;
|
|
110
|
-
default:
|
|
111
|
-
return '';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// Header
|
|
115
|
-
export function renderStreamHeader(squarePath, participantCount, activeCount) {
|
|
116
|
-
const accent = `${SAGE}· the square${RESET}`;
|
|
117
|
-
const path = `${META_GRAY}${squarePath}${RESET}`;
|
|
118
|
-
const stats = `${META_GRAY}${participantCount} participants · ${activeCount} active${RESET}`;
|
|
119
|
-
const rule = `${RULE_GRAY}${'─'.repeat(60)}${RESET}`;
|
|
120
|
-
return `\n ${accent} · ${path}\n ${stats}\n ${rule}`;
|
|
121
|
-
}
|
|
122
|
-
// Waiting indicator
|
|
123
|
-
export function renderWaiting() {
|
|
124
|
-
return `\n${FAINT} ─── waiting ───${RESET}\n`;
|
|
125
|
-
}
|