attnmd 0.4.0 → 0.4.1
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/README.md +3 -5
- package/bin/attn.js +57 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
19
19
|
<picture>
|
|
20
|
-
<source media="(prefers-color-scheme: dark)" srcset="site/static/screenshots/collab-dark.png">
|
|
21
|
-
<img src="site/static/screenshots/collab-light.png" alt="attn showing a shared markdown review with comments, suggestions, and a collaborator cursor" width="860">
|
|
20
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://media.githubusercontent.com/media/lightsofapollo/attn/main/site/static/screenshots/collab-dark.png">
|
|
21
|
+
<img src="https://media.githubusercontent.com/media/lightsofapollo/attn/main/site/static/screenshots/collab-light.png" alt="attn showing a shared markdown review with comments, suggestions, and a collaborator cursor" width="860">
|
|
22
22
|
</picture>
|
|
23
23
|
</p>
|
|
24
24
|
|
|
@@ -104,7 +104,6 @@ attn review join 'attn://review/<room-id>#key=<secret>'
|
|
|
104
104
|
For headless reviewers or agents:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
attn review register-agent reviewer
|
|
108
107
|
attn review join 'attn://review/<room-id>#key=<secret>' --as-agent reviewer
|
|
109
108
|
```
|
|
110
109
|
|
|
@@ -149,8 +148,7 @@ attn --json spec.md # dump document structure as JSON
|
|
|
149
148
|
|
|
150
149
|
```bash
|
|
151
150
|
attn review share docs/ # share a file or folder
|
|
152
|
-
attn review join 'attn://review/...' # join through the
|
|
153
|
-
attn review register-agent reviewer # create a headless reviewer identity
|
|
151
|
+
attn review join 'attn://review/...' # open/join through the app
|
|
154
152
|
attn review join 'attn://review/...' --as-agent reviewer
|
|
155
153
|
attn review list-agents
|
|
156
154
|
attn review whoami
|
package/bin/attn.js
CHANGED
|
@@ -62,14 +62,10 @@ async function main() {
|
|
|
62
62
|
const version = packageJson.version;
|
|
63
63
|
const headless = isHeadlessInvocation(args);
|
|
64
64
|
|
|
65
|
-
// Prefer a natively-installed `attn`
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
// shim rather than always pulling its own copy. The recursion guard in
|
|
70
|
-
// `findNativeBinary` excludes this package's own launcher so a global
|
|
71
|
-
// `npm i -g attn` symlink can't loop back here.
|
|
72
|
-
const nativeBinary = findNativeBinary();
|
|
65
|
+
// Prefer a natively-installed `attn` only when it matches this npm
|
|
66
|
+
// package. `npx attnmd@<version>` must not silently hand off to an older
|
|
67
|
+
// Homebrew/cargo/alias binary whose review protocol may be incompatible.
|
|
68
|
+
const nativeBinary = findNativeBinary(version);
|
|
73
69
|
if (nativeBinary) {
|
|
74
70
|
if (process.env.ATTN_DEBUG_LAUNCHER) {
|
|
75
71
|
console.error(`attn: using native binary ${nativeBinary}`);
|
|
@@ -88,7 +84,8 @@ async function main() {
|
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
if (!appPath) {
|
|
91
|
-
if (!
|
|
87
|
+
if (!isExpectedBinaryVersion(runtimeBinaryPath, version)) {
|
|
88
|
+
safeRemove(runtimeBinaryPath);
|
|
92
89
|
await ensureRuntimeBinary(version);
|
|
93
90
|
}
|
|
94
91
|
if (!existsSync(runtimeBinaryPath)) {
|
|
@@ -118,13 +115,16 @@ async function main() {
|
|
|
118
115
|
}
|
|
119
116
|
|
|
120
117
|
async function resolveAppPath(version) {
|
|
121
|
-
const globalApp = findGlobalAppInstall();
|
|
118
|
+
const globalApp = findGlobalAppInstall(version);
|
|
122
119
|
if (globalApp) {
|
|
123
120
|
return globalApp;
|
|
124
121
|
}
|
|
125
122
|
|
|
126
123
|
const managedVersionApp = join(managedAppsRoot, version, "attn.app");
|
|
127
|
-
if (
|
|
124
|
+
if (
|
|
125
|
+
existsSync(managedVersionApp) &&
|
|
126
|
+
isExpectedBinaryVersion(join(managedVersionApp, "Contents", "MacOS", "attn"), version)
|
|
127
|
+
) {
|
|
128
128
|
ensureCurrentAppLink(managedVersionApp);
|
|
129
129
|
return managedVersionApp;
|
|
130
130
|
}
|
|
@@ -156,13 +156,14 @@ async function ensureRuntimeBinary(version) {
|
|
|
156
156
|
console.error(`attn: installed runtime binary ${runtimeBinaryPath}`);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
function findGlobalAppInstall() {
|
|
159
|
+
function findGlobalAppInstall(version) {
|
|
160
160
|
const candidates = [
|
|
161
161
|
"/Applications/attn.app",
|
|
162
162
|
join(userHome, "Applications", "attn.app"),
|
|
163
163
|
];
|
|
164
164
|
for (const candidate of candidates) {
|
|
165
|
-
|
|
165
|
+
const binary = join(candidate, "Contents", "MacOS", "attn");
|
|
166
|
+
if (existsSync(candidate) && isExpectedBinaryVersion(binary, version)) {
|
|
166
167
|
return candidate;
|
|
167
168
|
}
|
|
168
169
|
}
|
|
@@ -182,7 +183,7 @@ function findGlobalAppInstall() {
|
|
|
182
183
|
* whose realpath points back at bin/attn.js, can't cause infinite
|
|
183
184
|
* recursion).
|
|
184
185
|
*/
|
|
185
|
-
function findNativeBinary() {
|
|
186
|
+
function findNativeBinary(version) {
|
|
186
187
|
const candidates = [];
|
|
187
188
|
|
|
188
189
|
if (process.env.ATTN_BIN) {
|
|
@@ -202,7 +203,7 @@ function findNativeBinary() {
|
|
|
202
203
|
);
|
|
203
204
|
|
|
204
205
|
for (const candidate of candidates) {
|
|
205
|
-
if (isUsableNativeBinary(candidate)) {
|
|
206
|
+
if (isUsableNativeBinary(candidate, version)) {
|
|
206
207
|
return candidate;
|
|
207
208
|
}
|
|
208
209
|
}
|
|
@@ -231,7 +232,7 @@ function whichAttn() {
|
|
|
231
232
|
* `<package>/bin/attn.js`; handing off to that would re-enter this script
|
|
232
233
|
* forever.
|
|
233
234
|
*/
|
|
234
|
-
function isUsableNativeBinary(candidate) {
|
|
235
|
+
function isUsableNativeBinary(candidate, version) {
|
|
235
236
|
if (!candidate) return false;
|
|
236
237
|
try {
|
|
237
238
|
accessSync(candidate, fsConstants.X_OK);
|
|
@@ -269,7 +270,42 @@ function isUsableNativeBinary(candidate) {
|
|
|
269
270
|
if (resolved.startsWith(binDirReal + "/") || resolved === binDirReal) {
|
|
270
271
|
return false;
|
|
271
272
|
}
|
|
272
|
-
return
|
|
273
|
+
return isExpectedBinaryVersion(resolved, version);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function isExpectedBinaryVersion(binary, version) {
|
|
277
|
+
if (!binary || !existsSync(binary)) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
if (process.env.ATTN_SKIP_NATIVE_VERSION_CHECK === "1") {
|
|
281
|
+
return true;
|
|
282
|
+
}
|
|
283
|
+
const actual = readBinaryVersion(binary);
|
|
284
|
+
const ok = actual === version;
|
|
285
|
+
if (!ok && process.env.ATTN_DEBUG_LAUNCHER) {
|
|
286
|
+
const label = actual ? `version ${actual}` : "unknown version";
|
|
287
|
+
console.error(`attn: skipping ${binary} (${label}; need ${version})`);
|
|
288
|
+
}
|
|
289
|
+
return ok;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function readBinaryVersion(binary) {
|
|
293
|
+
const result = spawnSync(binary, ["--version"], {
|
|
294
|
+
encoding: "utf8",
|
|
295
|
+
timeout: 2000,
|
|
296
|
+
});
|
|
297
|
+
if (result.error || result.status !== 0) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
301
|
+
const match = output.match(/\b\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\b/);
|
|
302
|
+
return match ? match[0] : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function safeRemove(path) {
|
|
306
|
+
if (existsSync(path)) {
|
|
307
|
+
rmSync(path, { recursive: true, force: true });
|
|
308
|
+
}
|
|
273
309
|
}
|
|
274
310
|
|
|
275
311
|
function ensureCurrentAppLink(appPath) {
|
|
@@ -389,6 +425,9 @@ if [ ! -e "$APP_LINK" ]; then
|
|
|
389
425
|
fi
|
|
390
426
|
BINARY="$APP_LINK/Contents/MacOS/attn"
|
|
391
427
|
HEADLESS=0
|
|
428
|
+
if [ "\${1:-}" = "review" ]; then
|
|
429
|
+
HEADLESS=1
|
|
430
|
+
fi
|
|
392
431
|
for arg in "$@"; do
|
|
393
432
|
case "$arg" in
|
|
394
433
|
--status|--json|--check|--info|--eval|--click|--wait-for|--query|--fill)
|
|
@@ -487,7 +526,7 @@ function resolvePathArgs(args) {
|
|
|
487
526
|
}
|
|
488
527
|
|
|
489
528
|
function isHeadlessInvocation(args) {
|
|
490
|
-
return args.some((arg) => HEADLESS_FLAGS.has(arg));
|
|
529
|
+
return args[0] === "review" || args.some((arg) => HEADLESS_FLAGS.has(arg));
|
|
491
530
|
}
|
|
492
531
|
|
|
493
532
|
function run(cmd, args) {
|