amorphous-cli 0.1.11 → 0.1.12
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/lib/api.mjs +3 -1
- package/lib/commands.mjs +53 -3
- package/lib/help.mjs +7 -2
- package/mcp.mjs +14 -2
- package/package.json +1 -1
- package/skill/amorphous/SKILL.md +2 -1
package/lib/api.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import { cliAppFetchOrigin, cliAppHostHeader } from './config.mjs'
|
|
|
2
2
|
import { ensureFreshSession } from './session.mjs'
|
|
3
3
|
|
|
4
4
|
const FRIENDLY = {
|
|
5
|
-
'This abstract already has 8 people': 'This abstract already has 8 people. Run: amorphous-cli people',
|
|
6
5
|
'User not found': 'No account with that email yet.',
|
|
7
6
|
'Grant failed': 'Could not invite. Run: amorphous-cli people',
|
|
8
7
|
'Cannot revoke owner': "You can't remove the owner. Run: amorphous-cli people",
|
|
@@ -29,6 +28,9 @@ export function friendlyMessage(raw) {
|
|
|
29
28
|
if (/^You already own \d+ abstracts$/.test(stripped)) {
|
|
30
29
|
return `${stripped}. Run: amorphous-cli list`
|
|
31
30
|
}
|
|
31
|
+
if (/^This abstract already has \d+ people$/.test(stripped)) {
|
|
32
|
+
return `${stripped}. Run: amorphous-cli people`
|
|
33
|
+
}
|
|
32
34
|
return stripped || text || 'Request failed.'
|
|
33
35
|
}
|
|
34
36
|
|
package/lib/commands.mjs
CHANGED
|
@@ -64,6 +64,12 @@ function printJson(data) {
|
|
|
64
64
|
console.log(JSON.stringify(data, null, 2))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
function warnIfRemoteChanged(detail) {
|
|
68
|
+
if (detail?.remoteChangedSincePush) {
|
|
69
|
+
console.error('This abstract has changed since you last pushed.')
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
67
73
|
function readMeta(cwd) {
|
|
68
74
|
const path = projectMetaPath(cwd)
|
|
69
75
|
if (!existsSync(path)) return null
|
|
@@ -274,8 +280,26 @@ async function cmdInvite(cwd, rest) {
|
|
|
274
280
|
})
|
|
275
281
|
const url = String(result.url || '')
|
|
276
282
|
if (!url) throw new Error('Could not invite.')
|
|
277
|
-
|
|
278
|
-
|
|
283
|
+
const accessLabel = role === 'user' ? 'view' : role === 'editor' ? 'edit' : role
|
|
284
|
+
if (jsonMode(flags)) {
|
|
285
|
+
printJson({ email, access: accessLabel, url, mailed: Boolean(result.mailed) })
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
console.log(url)
|
|
289
|
+
if (result.notified === 'role') console.log('Updated their access.')
|
|
290
|
+
else if (result.mailed) console.log('Emailed the invite.')
|
|
291
|
+
else console.log('The invite is saved. The email did not send.')
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function cmdNotify(cwd, rest) {
|
|
295
|
+
const { flags, positional } = parseFlags(rest)
|
|
296
|
+
if (flags.help) return printHelp('notify')
|
|
297
|
+
const id = await resolveAbstractId(cwd, flags, positional)
|
|
298
|
+
const result = await apiFetch(`/api/abstracts/${id}/notify`, { method: 'POST' })
|
|
299
|
+
const sent = Number(result.sent || 0)
|
|
300
|
+
if (jsonMode(flags)) printJson({ sent })
|
|
301
|
+
else if (sent === 0) console.log('No one else to email.')
|
|
302
|
+
else console.log(sent === 1 ? 'Emailed 1 member.' : `Emailed ${sent} members.`)
|
|
279
303
|
}
|
|
280
304
|
|
|
281
305
|
async function cmdRemove(cwd, rest) {
|
|
@@ -321,6 +345,7 @@ async function cmdPull(cwd, rest) {
|
|
|
321
345
|
if (flags.help) return printHelp('pull')
|
|
322
346
|
const id = requireMeta(cwd)
|
|
323
347
|
const detail = await apiFetch(`/api/abstracts/${id}`)
|
|
348
|
+
warnIfRemoteChanged(detail)
|
|
324
349
|
const records = await apiFetch(`/api/abstracts/${id}/records`)
|
|
325
350
|
const rows = records.records || []
|
|
326
351
|
const shared = rows.filter((r) => r.visibility !== 'private')
|
|
@@ -341,6 +366,7 @@ async function cmdPush(cwd, rest) {
|
|
|
341
366
|
const { flags } = parseFlags(rest)
|
|
342
367
|
if (flags.help) return printHelp('push')
|
|
343
368
|
const id = requireMeta(cwd)
|
|
369
|
+
warnIfRemoteChanged(await apiFetch(`/api/abstracts/${id}`))
|
|
344
370
|
if (flags.data) {
|
|
345
371
|
const localRecords = recordsFromLocal(cwd)
|
|
346
372
|
const { refused } = await apiFetch(`/api/abstracts/${id}/records`, {
|
|
@@ -373,8 +399,27 @@ async function cmdServe(cwd, rest) {
|
|
|
373
399
|
port,
|
|
374
400
|
json: jsonMode(flags),
|
|
375
401
|
})
|
|
402
|
+
let warnedRemote = false
|
|
403
|
+
async function watchRemote() {
|
|
404
|
+
try {
|
|
405
|
+
const detail = await apiFetch(`/api/abstracts/${id}`)
|
|
406
|
+
if (detail.remoteChangedSincePush && !warnedRemote) {
|
|
407
|
+
warnedRemote = true
|
|
408
|
+
console.error('This abstract has changed since you last pushed.')
|
|
409
|
+
} else if (!detail.remoteChangedSincePush) {
|
|
410
|
+
warnedRemote = false
|
|
411
|
+
}
|
|
412
|
+
} catch {
|
|
413
|
+
/* leave the dev server up if the check cannot reach the abstract */
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
void watchRemote()
|
|
417
|
+
const remoteTimer = setInterval(() => {
|
|
418
|
+
void watchRemote()
|
|
419
|
+
}, 20000)
|
|
376
420
|
await new Promise((resolve) => {
|
|
377
421
|
const stop = () => {
|
|
422
|
+
clearInterval(remoteTimer)
|
|
378
423
|
running.close().then(resolve, resolve)
|
|
379
424
|
}
|
|
380
425
|
process.once('SIGINT', stop)
|
|
@@ -403,8 +448,10 @@ async function cmdPreview(cwd, rest) {
|
|
|
403
448
|
const { flags, positional } = parseFlags(rest)
|
|
404
449
|
if (flags.help) return printHelp('preview')
|
|
405
450
|
const id = await resolveAbstractId(cwd, flags, positional)
|
|
451
|
+
const detail = await apiFetch(`/api/abstracts/${id}`)
|
|
452
|
+
warnIfRemoteChanged(detail)
|
|
406
453
|
const urls = urlsFor(id)
|
|
407
|
-
if (jsonMode(flags)) printJson({ preview: urls.preview })
|
|
454
|
+
if (jsonMode(flags)) printJson({ preview: urls.preview, remoteChangedSincePush: Boolean(detail.remoteChangedSincePush) })
|
|
408
455
|
else console.log(urls.preview)
|
|
409
456
|
}
|
|
410
457
|
|
|
@@ -412,6 +459,7 @@ async function cmdPublish(cwd, rest) {
|
|
|
412
459
|
const { flags, positional } = parseFlags(rest)
|
|
413
460
|
if (flags.help) return printHelp('publish')
|
|
414
461
|
const id = await resolveAbstractId(cwd, flags, positional)
|
|
462
|
+
warnIfRemoteChanged(await apiFetch(`/api/abstracts/${id}`))
|
|
415
463
|
const live = Boolean(flags.live)
|
|
416
464
|
const version = await apiFetch(`/api/abstracts/${id}/publish`, {
|
|
417
465
|
method: 'POST',
|
|
@@ -761,6 +809,8 @@ export async function runCommand(argv, cwd = process.cwd()) {
|
|
|
761
809
|
return cmdCreate(cwd, rest)
|
|
762
810
|
case 'invite':
|
|
763
811
|
return cmdInvite(cwd, rest)
|
|
812
|
+
case 'notify':
|
|
813
|
+
return cmdNotify(cwd, rest)
|
|
764
814
|
case 'remove':
|
|
765
815
|
return cmdRemove(cwd, rest)
|
|
766
816
|
case 'people':
|
package/lib/help.mjs
CHANGED
|
@@ -13,7 +13,8 @@ export const ROOT_HELP = `Amorphous
|
|
|
13
13
|
publish [--live] Publish a version; --live is Go live
|
|
14
14
|
live [abstract] Make a version live
|
|
15
15
|
visibility anyone|members Anyone can view / Members only
|
|
16
|
-
invite <email>
|
|
16
|
+
invite <email> Email an invite and print the link
|
|
17
|
+
notify Email members about the published version
|
|
17
18
|
remove <email|id> Remove
|
|
18
19
|
people [abstract] People
|
|
19
20
|
tag / untag Tag
|
|
@@ -93,7 +94,11 @@ Anyone can view, or Members only. Uses the live version if you omit --version.`,
|
|
|
93
94
|
|
|
94
95
|
invite: `invite <email> [view|use|edit] [--abstract <id|name>] [--json]
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
Email an invite and print the link. Access: view, use, or edit. Default: view.`,
|
|
98
|
+
|
|
99
|
+
notify: `notify [abstract] [--json]
|
|
100
|
+
|
|
101
|
+
Email members that the published version changed. Does not publish. You are not emailed.`,
|
|
97
102
|
|
|
98
103
|
remove: `remove <email|id> [--abstract <id|name>]
|
|
99
104
|
|
package/mcp.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Local MCP.
|
|
3
|
+
* Local MCP. Tools for abstracts.
|
|
4
4
|
* Stdout is the protocol. The CLI runs as a child so its output stays off this stream.
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from 'node:child_process'
|
|
@@ -122,7 +122,7 @@ await serveStdio(() => {
|
|
|
122
122
|
{
|
|
123
123
|
title: 'Invite someone',
|
|
124
124
|
description:
|
|
125
|
-
'
|
|
125
|
+
'Email an invite and return the link. access is view (can open), use (can write shared records), or edit (can change the abstract).',
|
|
126
126
|
inputSchema: z.object({
|
|
127
127
|
email: z.string().describe('Email address'),
|
|
128
128
|
access: z.enum(['view', 'use', 'edit']).describe('view, use, or edit'),
|
|
@@ -132,5 +132,17 @@ await serveStdio(() => {
|
|
|
132
132
|
async ({ email, access }) => call(['invite', email, access, '--json']),
|
|
133
133
|
)
|
|
134
134
|
|
|
135
|
+
server.registerTool(
|
|
136
|
+
'notify',
|
|
137
|
+
{
|
|
138
|
+
title: 'Email members about the published version',
|
|
139
|
+
description:
|
|
140
|
+
'Email members that the published version changed. On demand. This does not publish. The person running it is not emailed.',
|
|
141
|
+
inputSchema: z.object({}),
|
|
142
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
143
|
+
},
|
|
144
|
+
async () => call(['notify', '--json']),
|
|
145
|
+
)
|
|
146
|
+
|
|
135
147
|
return server
|
|
136
148
|
})
|
package/package.json
CHANGED
package/skill/amorphous/SKILL.md
CHANGED
|
@@ -52,7 +52,8 @@ When they ask:
|
|
|
52
52
|
- `amorphous-cli push` sends the source. `amorphous-cli push --data` also sends the records in this folder.
|
|
53
53
|
- `amorphous-cli preview` and open that URL.
|
|
54
54
|
- `amorphous-cli publish --live`
|
|
55
|
-
- `amorphous-cli invite <email> use` or `edit` or `view`. It
|
|
55
|
+
- `amorphous-cli invite <email> use` or `edit` or `view`. It emails them a link and prints that link. They open it and sign in with that email.
|
|
56
|
+
- `amorphous-cli notify` emails members that the published version changed. It does not publish. Do that only when they ask.
|
|
56
57
|
|
|
57
58
|
`amorphous-cli visibility anyone` lets anyone view and write shared records. `amorphous-cli visibility members` keeps the abstract to invited people.
|
|
58
59
|
|