@astrosheep/square 0.3.9 → 0.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/codex-plugin/.codex-plugin/plugin.json +1 -1
  2. package/dist/activity.js +4 -0
  3. package/dist/artifact.js +33 -2
  4. package/dist/cli/context.js +1 -1
  5. package/dist/cli/maintenance-commands.js +12 -1
  6. package/dist/cli/observation-commands.js +3 -16
  7. package/dist/cli/program.js +0 -4
  8. package/dist/cli/square-commands.js +23 -3
  9. package/dist/cmd/notify-once.js +5 -15
  10. package/dist/decisions.js +10 -2
  11. package/dist/delivery-health.js +55 -136
  12. package/dist/doctor.js +1 -0
  13. package/dist/file-lock.js +112 -0
  14. package/dist/harness-codex.js +35 -29
  15. package/dist/harness-links.js +0 -3
  16. package/dist/harness-pi.js +57 -0
  17. package/dist/harness.js +10 -15
  18. package/dist/help.js +8 -8
  19. package/dist/index.js +5 -1
  20. package/dist/model.js +4 -0
  21. package/dist/notifications.js +205 -28
  22. package/dist/paseo-connection.js +135 -0
  23. package/dist/paseo-delivery.js +73 -144
  24. package/dist/paseo-state.js +1 -1
  25. package/dist/paseo-timeline.js +32 -42
  26. package/dist/presentation.js +2 -2
  27. package/dist/presented.js +10 -72
  28. package/dist/registry.js +23 -24
  29. package/dist/routes.js +153 -0
  30. package/dist/square-application.js +47 -49
  31. package/dist/stream.js +1 -1
  32. package/dist/wake-attempts.js +171 -0
  33. package/dist/wake-evidence.js +35 -0
  34. package/dist/wake-port.js +22 -0
  35. package/dist/wake-sink.js +45 -6
  36. package/dist/watch.js +1 -2
  37. package/guides/participant.md +1 -1
  38. package/package.json +6 -1
  39. package/skills/brainstorm/SKILL.md +24 -24
  40. package/skills/square/.claude-plugin/plugin.json +1 -1
  41. package/skills/square/SKILL.md +4 -3
  42. package/skills/square-feedback/SKILL.md +2 -2
  43. package/dist/notification-failures.js +0 -54
@@ -0,0 +1,112 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { setTimeout as sleep } from 'node:timers/promises';
5
+ const lockWait = new Int32Array(new SharedArrayBuffer(4));
6
+ const heldSyncLocks = new Set();
7
+ function ownerState(lockPath) {
8
+ let pid;
9
+ try {
10
+ pid = Number.parseInt(fs.readFileSync(lockPath, 'utf8').split('\n')[0], 10);
11
+ }
12
+ catch {
13
+ return 'unknown';
14
+ }
15
+ if (!Number.isSafeInteger(pid) || pid <= 0)
16
+ return 'unknown';
17
+ try {
18
+ process.kill(pid, 0);
19
+ return 'alive';
20
+ }
21
+ catch (error) {
22
+ return error.code === 'ESRCH' ? 'dead' : 'alive';
23
+ }
24
+ }
25
+ function createLock(lockPath) {
26
+ let fd;
27
+ try {
28
+ fd = fs.openSync(lockPath, 'wx', 0o600);
29
+ }
30
+ catch (error) {
31
+ if (error.code === 'EEXIST')
32
+ return undefined;
33
+ throw error;
34
+ }
35
+ const token = `${process.pid}\n${Date.now()}\n${randomUUID()}\n`;
36
+ try {
37
+ fs.writeFileSync(fd, token, 'utf8');
38
+ return token;
39
+ }
40
+ catch (error) {
41
+ try {
42
+ fs.unlinkSync(lockPath);
43
+ }
44
+ catch { }
45
+ throw error;
46
+ }
47
+ finally {
48
+ fs.closeSync(fd);
49
+ }
50
+ }
51
+ function reclaimLock(lockPath, staleMs) {
52
+ try {
53
+ const stale = Date.now() - fs.statSync(lockPath).mtimeMs > staleMs;
54
+ if (ownerState(lockPath) !== 'dead' && !stale)
55
+ return false;
56
+ fs.unlinkSync(lockPath);
57
+ return true;
58
+ }
59
+ catch (error) {
60
+ return error.code === 'ENOENT';
61
+ }
62
+ }
63
+ function releaseLock(lockPath, token) {
64
+ try {
65
+ if (fs.readFileSync(lockPath, 'utf8') === token)
66
+ fs.unlinkSync(lockPath);
67
+ }
68
+ catch { }
69
+ }
70
+ function prepare(lockPath) {
71
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true });
72
+ }
73
+ export function withFileLockSync(lockPath, options, fn) {
74
+ if (heldSyncLocks.has(lockPath))
75
+ throw new Error(`Reentrant file lock: ${lockPath}`);
76
+ prepare(lockPath);
77
+ let token;
78
+ while (token === undefined) {
79
+ token = createLock(lockPath);
80
+ if (token !== undefined)
81
+ break;
82
+ if (reclaimLock(lockPath, options.staleMs))
83
+ continue;
84
+ Atomics.wait(lockWait, 0, 0, options.retryMs);
85
+ }
86
+ heldSyncLocks.add(lockPath);
87
+ try {
88
+ return fn();
89
+ }
90
+ finally {
91
+ heldSyncLocks.delete(lockPath);
92
+ releaseLock(lockPath, token);
93
+ }
94
+ }
95
+ export async function withFileLock(lockPath, options, fn) {
96
+ prepare(lockPath);
97
+ let token;
98
+ while (token === undefined) {
99
+ token = createLock(lockPath);
100
+ if (token !== undefined)
101
+ break;
102
+ if (reclaimLock(lockPath, options.staleMs))
103
+ continue;
104
+ await sleep(options.retryMs);
105
+ }
106
+ try {
107
+ return await fn();
108
+ }
109
+ finally {
110
+ releaseLock(lockPath, token);
111
+ }
112
+ }
@@ -12,7 +12,7 @@ const LEGACY_MARKETPLACES = ['astrosheep-square'];
12
12
  export function codexMarketplaceRoot(homeDir) {
13
13
  return path.join(homeDir, '.square', 'codex', 'marketplaces', CODEX_MARKETPLACE_NAME);
14
14
  }
15
- function configuredMarketplaceRoot(homeDir, configText) {
15
+ function configuredMarketplaceRoot(homeDir, configText, codexHomeDir) {
16
16
  const lines = configText.split('\n');
17
17
  const header = `[marketplaces.${CODEX_MARKETPLACE_NAME}]`;
18
18
  const start = lines.findIndex((line) => line.trim() === header);
@@ -28,10 +28,10 @@ function configuredMarketplaceRoot(homeDir, configText) {
28
28
  if (!match)
29
29
  return undefined;
30
30
  const value = match[1].startsWith('"') ? JSON.parse(match[1]) : match[1].slice(1, -1);
31
- return path.isAbsolute(value) ? value : path.resolve(codexHome(homeDir), value);
31
+ return path.isAbsolute(value) ? value : path.resolve(codexHome(homeDir, codexHomeDir), value);
32
32
  }
33
- function activeMarketplaceRoot(homeDir, configText) {
34
- return configuredMarketplaceRoot(homeDir, configText) ?? codexMarketplaceRoot(homeDir);
33
+ function activeMarketplaceRoot(homeDir, configText, codexHomeDir) {
34
+ return configuredMarketplaceRoot(homeDir, configText, codexHomeDir) ?? codexMarketplaceRoot(homeDir);
35
35
  }
36
36
  export function codexPluginRoot(homeDir, marketplaceRoot = codexMarketplaceRoot(homeDir)) {
37
37
  return path.join(marketplaceRoot, 'plugins', SQUARE_IDENTITY.pluginName);
@@ -39,12 +39,15 @@ export function codexPluginRoot(homeDir, marketplaceRoot = codexMarketplaceRoot(
39
39
  export function codexPluginHooksPath(homeDir, marketplaceRoot = codexMarketplaceRoot(homeDir)) {
40
40
  return path.join(codexPluginRoot(homeDir, marketplaceRoot), 'hooks', 'hooks.json');
41
41
  }
42
- function codexHome(homeDir) { return path.join(homeDir, '.codex'); }
43
- export function codexHomeHooksPath(homeDir) { return path.join(codexHome(homeDir), 'hooks.json'); }
44
- export function codexConfigPath(homeDir) { return path.join(codexHome(homeDir), 'config.toml'); }
45
- function runCodex(homeDir, args) {
42
+ function codexHome(homeDir, codexHomeDir) {
43
+ const explicit = codexHomeDir?.trim();
44
+ return explicit ? path.resolve(explicit) : path.join(homeDir, '.codex');
45
+ }
46
+ export function codexHomeHooksPath(homeDir, codexHomeDir) { return path.join(codexHome(homeDir, codexHomeDir), 'hooks.json'); }
47
+ export function codexConfigPath(homeDir, codexHomeDir) { return path.join(codexHome(homeDir, codexHomeDir), 'config.toml'); }
48
+ function runCodex(homeDir, args, codexHomeDir) {
46
49
  const result = spawnSync(process.env.SQUARE_CODEX_BIN || 'codex', args, {
47
- encoding: 'utf8', env: { ...process.env, HOME: homeDir, CODEX_HOME: codexHome(homeDir) }, timeout: 30_000,
50
+ encoding: 'utf8', env: { ...process.env, HOME: homeDir, CODEX_HOME: codexHome(homeDir, codexHomeDir) }, timeout: 30_000,
48
51
  });
49
52
  if (result.error)
50
53
  throw result.error;
@@ -77,10 +80,11 @@ function writeAtomic(file, text) {
77
80
  fs.writeFileSync(temp, text, { mode: 0o600 });
78
81
  fs.renameSync(temp, file);
79
82
  }
80
- export async function installCodexPlugin(homeDir, run = runCodex) {
81
- const config = codexConfigPath(homeDir);
83
+ export async function installCodexPlugin(homeDir, run = runCodex, codexHomeDir) {
84
+ const resolvedCodexHome = codexHome(homeDir, codexHomeDir);
85
+ const config = codexConfigPath(homeDir, resolvedCodexHome);
82
86
  const current = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
83
- const root = activeMarketplaceRoot(homeDir, current);
87
+ const root = activeMarketplaceRoot(homeDir, current, resolvedCodexHome);
84
88
  const staged = stageReplacement(root, (stage) => {
85
89
  const plugin = path.join(stage, 'plugins', SQUARE_IDENTITY.pluginName);
86
90
  fs.cpSync(fileURLToPath(new URL('../codex-plugin/', import.meta.url)), plugin, { recursive: true });
@@ -92,8 +96,8 @@ export async function installCodexPlugin(homeDir, run = runCodex) {
92
96
  const notes = [];
93
97
  try {
94
98
  writeAtomic(config, upsertTomlSectionKey(current, 'features', 'hooks', 'true'));
95
- requireSuccess(run(homeDir, ['plugin', 'marketplace', 'add', root, '--json']), 'marketplace install', true);
96
- const installed = run(homeDir, ['plugin', 'add', CODEX_PLUGIN_ID, '--json']);
99
+ requireSuccess(run(homeDir, ['plugin', 'marketplace', 'add', root, '--json'], resolvedCodexHome), 'marketplace install', true);
100
+ const installed = run(homeDir, ['plugin', 'add', CODEX_PLUGIN_ID, '--json'], resolvedCodexHome);
97
101
  requireSuccess(installed, 'plugin install');
98
102
  let installedPath;
99
103
  try {
@@ -108,8 +112,8 @@ export async function installCodexPlugin(homeDir, run = runCodex) {
108
112
  const pluginId = `${SQUARE_IDENTITY.pluginName}@${marketplace}`;
109
113
  if (!current.includes(`[marketplaces.${marketplace}]`) && !current.includes(`[plugins."${pluginId}"]`))
110
114
  continue;
111
- requireSuccess(run(homeDir, ['plugin', 'remove', pluginId, '--json']), 'legacy plugin removal', true);
112
- requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json']), 'legacy marketplace removal', true);
115
+ requireSuccess(run(homeDir, ['plugin', 'remove', pluginId, '--json'], resolvedCodexHome), 'legacy plugin removal', true);
116
+ requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json'], resolvedCodexHome), 'legacy marketplace removal', true);
113
117
  notes.push(`retired ${pluginId}`);
114
118
  }
115
119
  staged.finalize();
@@ -120,25 +124,27 @@ export async function installCodexPlugin(homeDir, run = runCodex) {
120
124
  throw error;
121
125
  }
122
126
  }
123
- export async function uninstallCodexPlugin(homeDir, run = runCodex) {
124
- const config = codexConfigPath(homeDir);
127
+ export async function uninstallCodexPlugin(homeDir, run = runCodex, codexHomeDir) {
128
+ const resolvedCodexHome = codexHome(homeDir, codexHomeDir);
129
+ const config = codexConfigPath(homeDir, resolvedCodexHome);
125
130
  const current = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
126
- const root = activeMarketplaceRoot(homeDir, current);
127
- requireSuccess(run(homeDir, ['plugin', 'remove', CODEX_PLUGIN_ID, '--json']), 'plugin removal', true);
128
- requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', CODEX_MARKETPLACE_NAME, '--json']), 'marketplace removal', true);
131
+ const root = activeMarketplaceRoot(homeDir, current, resolvedCodexHome);
132
+ requireSuccess(run(homeDir, ['plugin', 'remove', CODEX_PLUGIN_ID, '--json'], resolvedCodexHome), 'plugin removal', true);
133
+ requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', CODEX_MARKETPLACE_NAME, '--json'], resolvedCodexHome), 'marketplace removal', true);
129
134
  for (const marketplace of LEGACY_MARKETPLACES) {
130
- requireSuccess(run(homeDir, ['plugin', 'remove', `${SQUARE_IDENTITY.pluginName}@${marketplace}`, '--json']), 'legacy plugin removal', true);
131
- requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json']), 'legacy marketplace removal', true);
135
+ requireSuccess(run(homeDir, ['plugin', 'remove', `${SQUARE_IDENTITY.pluginName}@${marketplace}`, '--json'], resolvedCodexHome), 'legacy plugin removal', true);
136
+ requireSuccess(run(homeDir, ['plugin', 'marketplace', 'remove', marketplace, '--json'], resolvedCodexHome), 'legacy marketplace removal', true);
132
137
  }
133
138
  fs.rmSync(root, { recursive: true, force: true });
134
- fs.rmSync(codexHomeHooksPath(homeDir), { force: true });
135
- return { paths: [root, codexConfigPath(homeDir), codexHomeHooksPath(homeDir)], notes: [] };
139
+ fs.rmSync(codexHomeHooksPath(homeDir, resolvedCodexHome), { force: true });
140
+ return { paths: [root, config, codexHomeHooksPath(homeDir, resolvedCodexHome)], notes: [] };
136
141
  }
137
- export async function doctorCodexPlugin(homeDir, run = runCodex) {
138
- const config = codexConfigPath(homeDir);
142
+ export async function doctorCodexPlugin(homeDir, run = runCodex, codexHomeDir) {
143
+ const resolvedCodexHome = codexHome(homeDir, codexHomeDir);
144
+ const config = codexConfigPath(homeDir, resolvedCodexHome);
139
145
  const current = fs.existsSync(config) ? fs.readFileSync(config, 'utf8') : '';
140
- const root = activeMarketplaceRoot(homeDir, current);
141
- const listed = run(homeDir, ['plugin', 'list', '--json']);
146
+ const root = activeMarketplaceRoot(homeDir, current, resolvedCodexHome);
147
+ const listed = run(homeDir, ['plugin', 'list', '--json'], resolvedCodexHome);
142
148
  return [
143
149
  /^hooks\s*=\s*true$/m.test(current) ? `✓ features.hooks=true in ${config}` : `○ features.hooks missing in ${config}`,
144
150
  fs.existsSync(codexPluginHooksPath(homeDir, root)) ? `✓ Square plugin hooks ${codexPluginHooksPath(homeDir, root)}` : `○ Square plugin bundle missing ${root}`,
@@ -122,6 +122,3 @@ export function opencodeExtensionLink(homeDir = os.homedir()) {
122
122
  kind: 'extension',
123
123
  };
124
124
  }
125
- export function piExtensionLink(homeDir = os.homedir()) {
126
- return { source: path.join(packageRoot(), 'extensions', 'square-pi.js'), target: path.join(homeDir, '.pi', 'agent', 'extensions', 'square.js'), kind: 'extension' };
127
- }
@@ -0,0 +1,57 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { SQUARE_IDENTITY } from './identity.js';
5
+ export function piPackageSource() {
6
+ return `npm:${SQUARE_IDENTITY.packageName}@${SQUARE_IDENTITY.packageVersion}`;
7
+ }
8
+ export function piPackageRoot(homeDir) {
9
+ return path.join(homeDir, '.pi', 'agent', 'npm', 'node_modules', ...SQUARE_IDENTITY.packageName.split('/'));
10
+ }
11
+ function runPi(homeDir, args) {
12
+ const result = spawnSync(process.env.SQUARE_PI_BIN || 'pi', args, {
13
+ encoding: 'utf8',
14
+ env: { ...process.env, HOME: homeDir },
15
+ timeout: 30_000,
16
+ });
17
+ if (result.error)
18
+ throw result.error;
19
+ return { status: result.status ?? 1, stdout: result.stdout || '', stderr: result.stderr || '' };
20
+ }
21
+ function requireSuccess(result, action) {
22
+ if (result.status === 0)
23
+ return;
24
+ throw new Error(`Pi ${action} failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.status}`}`);
25
+ }
26
+ export function installPiPackage(homeDir, run = runPi) {
27
+ requireSuccess(run(homeDir, ['install', piPackageSource()]), 'package install');
28
+ return [piPackageRoot(homeDir)];
29
+ }
30
+ export function uninstallPiPackage(homeDir, run = runPi) {
31
+ requireSuccess(run(homeDir, ['remove', piPackageSource()]), 'package removal');
32
+ return [piPackageRoot(homeDir)];
33
+ }
34
+ export function doctorPiPackage(homeDir, run = runPi) {
35
+ const listed = run(homeDir, ['list']);
36
+ const root = piPackageRoot(homeDir);
37
+ const manifestPath = path.join(root, 'package.json');
38
+ let manifest;
39
+ try {
40
+ manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
41
+ }
42
+ catch {
43
+ // The diagnostics below name the missing or invalid package state.
44
+ }
45
+ const extensions = manifest?.pi?.extensions;
46
+ return [
47
+ listed.status === 0 && listed.stdout.includes(SQUARE_IDENTITY.packageName)
48
+ ? `✓ Pi package ${SQUARE_IDENTITY.packageName} configured`
49
+ : `○ Pi package ${SQUARE_IDENTITY.packageName} not configured`,
50
+ manifest?.version === SQUARE_IDENTITY.packageVersion
51
+ ? `✓ Pi package ${SQUARE_IDENTITY.packageVersion} installed at ${root}`
52
+ : `○ Pi package ${SQUARE_IDENTITY.packageVersion} missing at ${root}`,
53
+ Array.isArray(extensions) && extensions.includes('./extensions/square-pi.js')
54
+ ? '✓ Pi Square extension declared'
55
+ : '○ Pi Square extension not declared',
56
+ ];
57
+ }
package/dist/harness.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import { doctorDeliveryHealth } from './delivery-health.js';
3
+ import { wakeGraceMs } from './notifications.js';
3
4
  import { doctorClaudePlugin, installClaudePlugin, uninstallClaudePlugin, } from './harness-claude.js';
4
5
  import { doctorCodexPlugin, installCodexPlugin, uninstallCodexPlugin, } from './harness-codex.js';
5
- import { doctorHarnessLinks, installHarnessLinks, opencodeExtensionLink, piExtensionLink, skillLinks, uninstallHarnessLinks, verifyOpenCodeRuntime, } from './harness-links.js';
6
+ import { doctorPiPackage, installPiPackage, uninstallPiPackage, } from './harness-pi.js';
7
+ import { doctorHarnessLinks, installHarnessLinks, opencodeExtensionLink, skillLinks, uninstallHarnessLinks, verifyOpenCodeRuntime, } from './harness-links.js';
6
8
  function result(lines, notes = []) {
7
9
  return { lines, notes };
8
10
  }
@@ -28,13 +30,6 @@ function readableSquarePath(squarePath) {
28
30
  }
29
31
  }
30
32
  const TARGETS = [
31
- {
32
- name: 'skills',
33
- capabilities: ['install', 'uninstall', 'doctor'],
34
- install: ({ homeDir, force }) => result(installHarnessLinks(skillLinks(homeDir), force)),
35
- uninstall: ({ homeDir }) => result(uninstallHarnessLinks(skillLinks(homeDir))),
36
- doctor: ({ homeDir }) => result(doctorHarnessLinks(skillLinks(homeDir))),
37
- },
38
33
  {
39
34
  name: 'claude',
40
35
  capabilities: ['install', 'uninstall', 'doctor'],
@@ -52,7 +47,7 @@ const TARGETS = [
52
47
  name: 'codex',
53
48
  capabilities: ['install', 'uninstall', 'doctor'],
54
49
  async install({ homeDir }) {
55
- const installed = await installCodexPlugin(homeDir);
50
+ const installed = await installCodexPlugin(homeDir, undefined, process.env.CODEX_HOME);
56
51
  const lines = [
57
52
  installed.configPath,
58
53
  installed.marketplaceRoot,
@@ -62,10 +57,10 @@ const TARGETS = [
62
57
  return result(lines, installed.notes);
63
58
  },
64
59
  async uninstall({ homeDir }) {
65
- const removed = await uninstallCodexPlugin(homeDir);
60
+ const removed = await uninstallCodexPlugin(homeDir, undefined, process.env.CODEX_HOME);
66
61
  return result(removed.paths, removed.notes);
67
62
  },
68
- async doctor({ homeDir }) { return doctorHost('Codex', () => doctorCodexPlugin(homeDir)); },
63
+ async doctor({ homeDir }) { return doctorHost('Codex', () => doctorCodexPlugin(homeDir, undefined, process.env.CODEX_HOME)); },
69
64
  },
70
65
  {
71
66
  name: 'opencode',
@@ -77,15 +72,15 @@ const TARGETS = [
77
72
  {
78
73
  name: 'pi',
79
74
  capabilities: ['install', 'uninstall', 'doctor'],
80
- install: ({ homeDir, force }) => result(installHarnessLinks([piExtensionLink(homeDir)], force)),
81
- uninstall: ({ homeDir }) => result(uninstallHarnessLinks([piExtensionLink(homeDir)])),
82
- doctor: ({ homeDir }) => result(doctorHarnessLinks([piExtensionLink(homeDir)])),
75
+ install: ({ homeDir }) => result(installPiPackage(homeDir)),
76
+ uninstall: ({ homeDir }) => result(uninstallPiPackage(homeDir)),
77
+ doctor: ({ homeDir }) => result(doctorPiPackage(homeDir)),
83
78
  },
84
79
  {
85
80
  name: 'delivery',
86
81
  capabilities: ['doctor'],
87
82
  doctor: ({ squarePath }) => result(readableSquarePath(squarePath)
88
- ? doctorDeliveryHealth(squarePath)
83
+ ? doctorDeliveryHealth(squarePath, wakeGraceMs())
89
84
  : ['○ delivery health skipped (no readable square path)']),
90
85
  },
91
86
  ];
package/dist/help.js CHANGED
@@ -11,7 +11,7 @@ const COMMANDS = [
11
11
  details: ['Options:', ' --depth <N> Descend through at most N directory levels (default 4; 0 scans only the current directory).'],
12
12
  },
13
13
  {
14
- names: ['join'], usage: '--as <name> join [--last N | --all]', usesSquare: true, group: 'participant',
14
+ names: ['join'], usage: '--as <name> join [--last N | --all] [--kick]', usesSquare: true, group: 'participant',
15
15
  summary: 'Step into the square and read its current context.',
16
16
  details: ['Options:', ' --last <N> Show the last N public activities (default 10).', ' --all Show the complete history.'],
17
17
  },
@@ -50,17 +50,17 @@ const COMMANDS = [
50
50
  {
51
51
  names: ['install'], usage: 'install (--all | <target>...) [-f]', group: 'maintenance',
52
52
  summary: 'Install Square support for one or more agent hosts.',
53
- details: ['Targets:', ' skills, claude, codex, opencode, pi', '', 'Options:', ' --all Install every supported target.', ' -f, --force Replace existing managed links.'],
53
+ details: ['Targets:', ' claude, codex, opencode, pi', '', 'Options:', ' --all Install every supported target.', ' -f, --force Replace existing managed links.'],
54
54
  },
55
55
  {
56
56
  names: ['uninstall'], usage: 'uninstall (--all | <target>...)', group: 'maintenance',
57
57
  summary: 'Remove Square support from one or more agent hosts.',
58
- details: ['Targets:', ' skills, claude, codex, opencode, pi', '', 'Options:', ' --all Remove every supported target.'],
58
+ details: ['Targets:', ' claude, codex, opencode, pi', '', 'Options:', ' --all Remove every supported target.'],
59
59
  },
60
60
  {
61
- names: ['harness'], usage: 'harness doctor [skills|claude|codex|opencode|pi|delivery]', usesSquare: true, group: 'maintenance', hiddenFromIndex: true,
61
+ names: ['harness'], usage: 'harness doctor [claude|codex|opencode|pi|delivery]', usesSquare: true, group: 'maintenance', hiddenFromIndex: true,
62
62
  summary: 'Diagnose installed agent-host support.',
63
- details: ['Targets:', ' skills, claude, codex, opencode, pi Diagnose one installed adapter.', ' delivery Diagnose delivery for the selected square.'],
63
+ details: ['Targets:', ' claude, codex, opencode, pi Diagnose one installed adapter.', ' delivery Diagnose delivery for the selected square.'],
64
64
  },
65
65
  { names: ['compact'], usage: 'compact [--keep N]', usesSquare: true, group: 'host', summary: 'Move older activity out of the working artifact while keeping the latest N.' },
66
66
  {
@@ -90,7 +90,7 @@ export function renderGlobalHelp() {
90
90
  '',
91
91
  ]);
92
92
  return [
93
- 'Usage: square [--square-path <path>] [--as <name>] <command> [args...]',
93
+ 'Usage: square [--location <path>] [--as <name>] <command> [args...]',
94
94
  '',
95
95
  ...commandLines,
96
96
  "Run 'square <command> --help' for command options.",
@@ -104,7 +104,7 @@ export function renderSubcommandHelp(command) {
104
104
  const aliases = definition.names.filter((name) => name !== command);
105
105
  const usage = definition.usage.replace('{command}', command);
106
106
  return [
107
- `Usage: square ${definition.usesSquare ? '[--square-path <path>] ' : ''}${usage}`,
107
+ `Usage: square ${definition.usesSquare ? '[--location <path>] ' : ''}${usage}`,
108
108
  ...(aliases.length > 0 ? [`Aliases: ${aliases.join(', ')}`] : []),
109
109
  '',
110
110
  definition.summary,
@@ -121,7 +121,7 @@ export function helpRequest(rawArgs) {
121
121
  const args = [];
122
122
  for (let index = 0; index < rawArgs.length; index++) {
123
123
  const arg = rawArgs[index];
124
- if (arg === '--square-path' || arg === '--as') {
124
+ if (arg === '--location' || arg === '--as') {
125
125
  const value = rawArgs[index + 1];
126
126
  if (value === undefined || value.startsWith('--'))
127
127
  return undefined;
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ export { SquareError } from './model.js';
2
2
  export { loadSquare } from './artifact.js';
3
3
  export { extractMentions, countSays, joinedNames, doneNames, isCurrentlyJoined, publicActs, readCursor, } from './runtime.js';
4
4
  import { loadSquare } from './artifact.js';
5
- import { hasDeliveredNotification as hasDeliveredNotificationImpl, waitForDeliveredNotification as waitForDeliveredNotificationImpl, } from './notifications.js';
5
+ import { dispatchActNotifications, hasDeliveredNotification as hasDeliveredNotificationImpl, sweepPendingNotifications, waitForDeliveredNotification as waitForDeliveredNotificationImpl, } from './notifications.js';
6
6
  import { WATCH_STALE_MS, freshWatchLease, getReadState as getDocReadState, } from './runtime.js';
7
7
  import { resolveKnownName } from './decisions.js';
8
8
  import { execute } from './square-application.js';
@@ -52,6 +52,7 @@ export async function resume(squarePath, actor) {
52
52
  await execute(squarePath, { type: 'resume', actor, now: Date.now() });
53
53
  }
54
54
  export async function express(squarePath, name, body, opts = {}) {
55
+ await sweepPendingNotifications(squarePath);
55
56
  const committed = await execute(squarePath, {
56
57
  type: 'say',
57
58
  name,
@@ -60,6 +61,9 @@ export async function express(squarePath, name, body, opts = {}) {
60
61
  now: Date.now(),
61
62
  ...(opts.reply === undefined ? {} : { reply: actRefIndex(opts.reply) }),
62
63
  });
64
+ const sayAct = committed.acts.find((act) => act.kind === 'say');
65
+ if (sayAct !== undefined)
66
+ await dispatchActNotifications(squarePath, sayAct);
63
67
  if (committed.result.type !== 'sent')
64
68
  throw new Error(`Activity rejected: ${committed.result.type}`);
65
69
  }
package/dist/model.js CHANGED
@@ -1,4 +1,8 @@
1
1
  // Shared model and constants for Square.
2
+ export const WAKE_ROUTE_KINDS = ['opencode-server', 'codex-app-server', 'claude-native', 'pi-extension', 'paseo'];
3
+ export function isWakeRouteKind(value) {
4
+ return typeof value === 'string' && WAKE_ROUTE_KINDS.includes(value);
5
+ }
2
6
  export class SquareError extends Error {
3
7
  code;
4
8
  constructor(code, message) {