amorphous-cli 0.3.8 → 0.3.9
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 +2 -2
- package/hooks/session-start.mjs +1 -1
- package/lib/agent-contract.test.mjs +2 -0
- package/lib/agent-guide-commands.test.mjs +1 -1
- package/lib/api.mjs +18 -17
- package/lib/commands.mjs +16 -15
- package/lib/help.mjs +2 -2
- package/lib/login.mjs +3 -1
- package/lib/session.mjs +6 -5
- package/lib/tool-result.test.mjs +2 -2
- package/lib/update-check.mjs +1 -1
- package/lib/update-check.test.mjs +15 -3
- package/mcp.mjs +1 -1
- package/package.json +1 -1
- package/skill/amorphous/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Make an abstract with your AI agent. Describe what you want, and your agent can build and edit it with you.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx amorphous-cli@latest setup
|
|
6
|
+
npx --yes amorphous-cli@latest setup
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
After setup, tell your agent what to make. Ask it to preview, publish, or invite people when you're ready. Run `amorphous-cli --help` to see available commands.
|
|
9
|
+
After setup, tell your agent what to make. Ask it to preview, publish, or invite people when you're ready. Run `npx --yes amorphous-cli@latest --help` to see available commands.
|
package/hooks/session-start.mjs
CHANGED
|
@@ -48,7 +48,7 @@ if (target === 'cursor') {
|
|
|
48
48
|
process.stdout.write(
|
|
49
49
|
`${JSON.stringify({
|
|
50
50
|
additional_context:
|
|
51
|
-
'The Amorphous skill on this machine was just updated. Read ~/.agents/skills/amorphous/SKILL.md and follow it.
|
|
51
|
+
'The Amorphous skill on this machine was just updated. Read ~/.agents/skills/amorphous/SKILL.md and follow it. Run commands with npx --yes amorphous-cli@latest; no global install is needed.',
|
|
52
52
|
})}\n`,
|
|
53
53
|
)
|
|
54
54
|
} else {
|
|
@@ -41,6 +41,8 @@ test('setup copies the skill, and an edited contract is left alone', () => {
|
|
|
41
41
|
assert.equal(readFileSync(join(dirs[0], '.amorphous-version'), 'utf8'), `${CLI_VERSION}\n`)
|
|
42
42
|
assert.equal(readFileSync(join(dirs[1], '.amorphous-version'), 'utf8'), `${CLI_VERSION}\n`)
|
|
43
43
|
const skill = readFileSync(join(dirs[0], 'SKILL.md'), 'utf8')
|
|
44
|
+
assert.match(skill, /npx --yes amorphous-cli@latest <command>/)
|
|
45
|
+
assert.doesNotMatch(skill, /npm install -g amorphous-cli@latest/)
|
|
44
46
|
assert.match(skill, /amorphous-cli list --view all/)
|
|
45
47
|
assert.match(skill, /run `amorphous-cli pull <id-or-name>` first/)
|
|
46
48
|
assert.match(skill, /Then run `amorphous-cli context` once/)
|
|
@@ -237,7 +237,7 @@ test('missing credentials quarantine prior private bytes under the original iden
|
|
|
237
237
|
const sentinel = 'private bytes with missing auth.'
|
|
238
238
|
writeFileSync(join(h.project, 'private-agent.md'), sentinel)
|
|
239
239
|
unlinkSync(h.credentials)
|
|
240
|
-
await assert.rejects(runCommand(['context'], h.project), /Not signed in/i)
|
|
240
|
+
await assert.rejects(runCommand(['context'], h.project), /Not signed in\. Run: npx --yes amorphous-cli@latest login/i)
|
|
241
241
|
assert.equal(existsSync(join(h.project, 'private-agent.md')), false)
|
|
242
242
|
assert.equal(readFileSync(privateRecoveryFile(h.project), 'utf8'), sentinel)
|
|
243
243
|
})
|
package/lib/api.mjs
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import { cliAppFetchOrigin, cliAppHostHeader } from './config.mjs'
|
|
2
2
|
import { ensureFreshSession } from './session.mjs'
|
|
3
3
|
|
|
4
|
+
const CLI = 'npx --yes amorphous-cli@latest'
|
|
4
5
|
const FRIENDLY = {
|
|
5
6
|
'User not found': 'No account with that email yet.',
|
|
6
|
-
'Grant failed':
|
|
7
|
-
'Cannot revoke owner':
|
|
8
|
-
'Cannot remove yourself':
|
|
9
|
-
'Collection not found':
|
|
10
|
-
Forbidden:
|
|
11
|
-
'Not Found':
|
|
12
|
-
'Owner required':
|
|
13
|
-
'id required':
|
|
14
|
-
'userId required':
|
|
15
|
-
'Invalid body':
|
|
16
|
-
'Name required':
|
|
17
|
-
'Create failed':
|
|
18
|
-
'Publish failed':
|
|
19
|
-
'File too large':
|
|
20
|
-
'Not logged in. Run: amorphous login <email>':
|
|
7
|
+
'Grant failed': `Could not invite. Run: ${CLI} people`,
|
|
8
|
+
'Cannot revoke owner': `You can't remove the owner. Run: ${CLI} people`,
|
|
9
|
+
'Cannot remove yourself': `You can't remove yourself. Run: ${CLI} people`,
|
|
10
|
+
'Collection not found': `Folder not found. Run: ${CLI} folder list`,
|
|
11
|
+
Forbidden: `You don't have access. Run: ${CLI} list`,
|
|
12
|
+
'Not Found': `Not found. Run: ${CLI} list`,
|
|
13
|
+
'Owner required': `Only the owner can do that. Run: ${CLI} people`,
|
|
14
|
+
'id required': `Missing id. Run: ${CLI} list`,
|
|
15
|
+
'userId required': `Pass an email or id. Run: ${CLI} people`,
|
|
16
|
+
'Invalid body': `Invalid request. Run: ${CLI} pull`,
|
|
17
|
+
'Name required': `Name required. Run: ${CLI} rename --name <name>`,
|
|
18
|
+
'Create failed': `Could not create the abstract. Run: ${CLI} list`,
|
|
19
|
+
'Publish failed': `Could not publish. Run: ${CLI} push`,
|
|
20
|
+
'File too large': `That file is too large. Run: ${CLI} pull`,
|
|
21
|
+
'Not logged in. Run: amorphous login <email>': `Not signed in. Run: ${CLI} login`,
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function friendlyMessage(raw) {
|
|
@@ -26,10 +27,10 @@ export function friendlyMessage(raw) {
|
|
|
26
27
|
const stripped = text.replace(/^\d{3}\s+/, '')
|
|
27
28
|
if (FRIENDLY[stripped]) return FRIENDLY[stripped]
|
|
28
29
|
if (/^You already own \d+ abstracts$/.test(stripped)) {
|
|
29
|
-
return `${stripped}. Run:
|
|
30
|
+
return `${stripped}. Run: ${CLI} list`
|
|
30
31
|
}
|
|
31
32
|
if (/^This abstract already has \d+ people$/.test(stripped)) {
|
|
32
|
-
return `${stripped}. Run:
|
|
33
|
+
return `${stripped}. Run: ${CLI} people`
|
|
33
34
|
}
|
|
34
35
|
return stripped || text || 'Request failed.'
|
|
35
36
|
}
|
package/lib/commands.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
|
|
26
26
|
const COMBINED_GUIDE_MAX_BYTES = 48 * 1024
|
|
27
27
|
const GUIDE_MAX_BYTES = 32 * 1024
|
|
28
|
+
const CLI = 'npx --yes amorphous-cli@latest'
|
|
28
29
|
const QUARANTINE_USER_IDS = [
|
|
29
30
|
'ffffffff-ffff-4fff-8fff-ffffffffffff',
|
|
30
31
|
'eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee',
|
|
@@ -114,7 +115,7 @@ export function writeRecipeFiles(cwd, source) {
|
|
|
114
115
|
function requireMeta(cwd) {
|
|
115
116
|
const meta = readMeta(cwd)
|
|
116
117
|
if (!meta?.abstractId) {
|
|
117
|
-
throw new Error(
|
|
118
|
+
throw new Error(`No .amorphous.json here. Run ${CLI} create first.`)
|
|
118
119
|
}
|
|
119
120
|
return meta.abstractId
|
|
120
121
|
}
|
|
@@ -171,7 +172,7 @@ function sessionUserId(accessToken) {
|
|
|
171
172
|
if (typeof decoded.sub !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(decoded.sub)) throw new Error()
|
|
172
173
|
return decoded.sub.toLowerCase()
|
|
173
174
|
} catch {
|
|
174
|
-
throw new Error(
|
|
175
|
+
throw new Error(`The signed-in Amorphous session has no valid user identity. Run ${CLI} login.`)
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
|
|
@@ -264,11 +265,11 @@ async function cmdContext(cwd, rest) {
|
|
|
264
265
|
authorizationComplete = true
|
|
265
266
|
const combinedBytes = Buffer.byteLength(abstractGuide.body, 'utf8') + Buffer.byteLength(privateGuide.body, 'utf8')
|
|
266
267
|
if (combinedBytes > COMBINED_GUIDE_MAX_BYTES) {
|
|
267
|
-
throw new Error(
|
|
268
|
+
throw new Error(`Agent guides exceed the combined 48 KiB UTF-8 context limit. Neither guide was loaded; reduce their size and run ${CLI} context again.`)
|
|
268
269
|
}
|
|
269
270
|
const identity = activateGuideIdentity(cwd, id, userId)
|
|
270
271
|
if (!identity.privateSafeToLoad) {
|
|
271
|
-
throw new Error(
|
|
272
|
+
throw new Error(`Guide identity is uncertain. Existing local files were preserved in recovery and neither guide was loaded; confirm the account and run ${CLI} context again.`)
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
const documents = [
|
|
@@ -279,7 +280,7 @@ async function cmdContext(cwd, rest) {
|
|
|
279
280
|
for (const { kind, guide } of documents) {
|
|
280
281
|
const outcome = materializeGuide({ projectRoot: cwd, abstractId: id, userId, kind, body: guide.body, revision: guide.revision })
|
|
281
282
|
if (outcome.status === 'identity-uncertain') {
|
|
282
|
-
throw new Error(
|
|
283
|
+
throw new Error(`Guide identity changed during context loading. Do not use either local guide; confirm the account and run ${CLI} context again.`)
|
|
283
284
|
}
|
|
284
285
|
outcomes.push(outcome)
|
|
285
286
|
}
|
|
@@ -313,7 +314,7 @@ async function cmdGuide(cwd, rest) {
|
|
|
313
314
|
const { flags, positional } = parseFlags(rest)
|
|
314
315
|
if (flags.help || positional[0] !== 'save') return printHelp('guide')
|
|
315
316
|
const kind = positional[1]
|
|
316
|
-
if (!['abstract', 'private'].includes(kind)) throw new Error(
|
|
317
|
+
if (!['abstract', 'private'].includes(kind)) throw new Error(`Use: ${CLI} guide save <abstract|private> [abstract].`)
|
|
317
318
|
const abstractToken = positional[2]
|
|
318
319
|
const { accessToken, userId } = await guideSession()
|
|
319
320
|
const request = (path, options) => apiFetch(path, { ...options, accessToken })
|
|
@@ -322,11 +323,11 @@ async function cmdGuide(cwd, rest) {
|
|
|
322
323
|
const current = validateGuideResponse(await request(`/api/abstracts/${id}/${endpoint}`), `${kind} guide`)
|
|
323
324
|
const paths = guidePaths(cwd, id, userId)
|
|
324
325
|
const identity = activateGuideIdentity(cwd, id, userId)
|
|
325
|
-
if (!identity.privateSafeToLoad) throw new Error(
|
|
326
|
+
if (!identity.privateSafeToLoad) throw new Error(`Guide identity is uncertain; preserved local files are not eligible for saving. Run ${CLI} context after confirming the signed-in account.`)
|
|
326
327
|
const state = readGuideSyncState(cwd, id, userId)
|
|
327
328
|
const base = state?.guides?.[kind]
|
|
328
|
-
if (!base) throw new Error(
|
|
329
|
-
if (base.revision !== current.revision) throw new Error(
|
|
329
|
+
if (!base) throw new Error(`No saved guide revision is available. Run ${CLI} context first.`)
|
|
330
|
+
if (base.revision !== current.revision) throw new Error(`The server guide changed since context was loaded. Run ${CLI} context, resolve any conflict, then save again.`)
|
|
330
331
|
const path = kind === 'abstract' ? paths.abstractGuide : paths.privateGuide
|
|
331
332
|
const file = lstatSync(path)
|
|
332
333
|
if (file.isSymbolicLink() || !file.isFile()) throw new Error('Guide file must be a regular non-symlink file.')
|
|
@@ -447,7 +448,7 @@ async function cmdSetup(rest) {
|
|
|
447
448
|
console.log(`
|
|
448
449
|
Tell the person:
|
|
449
450
|
They describe what they want made. You make it.
|
|
450
|
-
Prefer
|
|
451
|
+
Prefer ${CLI} serve while you edit. Open the URL and leave it running. It reloads when the files change.
|
|
451
452
|
They tell you when to push, preview, publish, or invite.`)
|
|
452
453
|
}
|
|
453
454
|
|
|
@@ -481,7 +482,7 @@ async function cmdCreate(cwd, rest) {
|
|
|
481
482
|
}
|
|
482
483
|
syncProjectContract(cwd)
|
|
483
484
|
if (jsonMode(flags)) printJson({ id: data.id, name: data.name })
|
|
484
|
-
else { console.log(data.id); console.log(
|
|
485
|
+
else { console.log(data.id); console.log(`Prefer ${CLI} serve while you edit.`) }
|
|
485
486
|
return
|
|
486
487
|
}
|
|
487
488
|
const template = typeof flags.template === 'string' ? flags.template : 'blank'
|
|
@@ -509,7 +510,7 @@ async function cmdCreate(cwd, rest) {
|
|
|
509
510
|
if (jsonMode(flags)) printJson({ id, name: name || 'Untitled' })
|
|
510
511
|
else {
|
|
511
512
|
console.log(id)
|
|
512
|
-
console.log(
|
|
513
|
+
console.log(`Prefer ${CLI} serve while you edit.`)
|
|
513
514
|
}
|
|
514
515
|
}
|
|
515
516
|
|
|
@@ -644,7 +645,7 @@ async function cmdPull(cwd, rest) {
|
|
|
644
645
|
const records = await apiFetch(`/api/abstracts/${id}/records`)
|
|
645
646
|
writePulledProject(cwd, id, detail, records, { fresh: positional.length > 0 || Boolean(flags.abstract) })
|
|
646
647
|
console.log('Pulled.')
|
|
647
|
-
console.log(
|
|
648
|
+
console.log(`Prefer ${CLI} serve while you edit.`)
|
|
648
649
|
}
|
|
649
650
|
|
|
650
651
|
async function cmdPush(cwd, rest) {
|
|
@@ -660,7 +661,7 @@ async function cmdPush(cwd, rest) {
|
|
|
660
661
|
body: JSON.stringify({ records: localRecords }),
|
|
661
662
|
})
|
|
662
663
|
if (refused?.length) {
|
|
663
|
-
console.error(`Skipped (already live): ${refused.join(', ')}. Run:
|
|
664
|
+
console.error(`Skipped (already live): ${refused.join(', ')}. Run: ${CLI} pull`)
|
|
664
665
|
}
|
|
665
666
|
}
|
|
666
667
|
const { html, css, js } = readProjectFiles(cwd)
|
|
@@ -1098,6 +1099,6 @@ export async function runCommand(argv, cwd = process.cwd()) {
|
|
|
1098
1099
|
case 'rename':
|
|
1099
1100
|
return cmdRename(cwd, rest)
|
|
1100
1101
|
default:
|
|
1101
|
-
throw new Error(`Unknown command “${raw}”. Run
|
|
1102
|
+
throw new Error(`Unknown command “${raw}”. Run ${CLI} --help.`)
|
|
1102
1103
|
}
|
|
1103
1104
|
}
|
package/lib/help.mjs
CHANGED
|
@@ -31,8 +31,8 @@ export const ROOT_HELP = `Amorphous
|
|
|
31
31
|
folder remove Remove empty folder
|
|
32
32
|
rename --name Rename
|
|
33
33
|
|
|
34
|
-
Run amorphous-cli <command> --help for flags. Pass --json where you need machine output.
|
|
35
|
-
Run amorphous-cli --version to print the
|
|
34
|
+
Run npx --yes amorphous-cli@latest <command> --help for flags. Pass --json where you need machine output.
|
|
35
|
+
Run npx --yes amorphous-cli@latest --version to print the current version.
|
|
36
36
|
`
|
|
37
37
|
|
|
38
38
|
export const COMMAND_HELP = {
|
package/lib/login.mjs
CHANGED
|
@@ -8,6 +8,8 @@ import { createPkce, statesMatch } from './connect-crypto.mjs'
|
|
|
8
8
|
import { mailpitBase, sessionFromVerifyUrl, waitForMagicLinkVerifyUrl } from './mailpit.mjs'
|
|
9
9
|
import { saveSession, sessionRecord } from './session.mjs'
|
|
10
10
|
|
|
11
|
+
const CLI = 'npx --yes amorphous-cli@latest'
|
|
12
|
+
|
|
11
13
|
function isLocalSupabase() {
|
|
12
14
|
try {
|
|
13
15
|
const host = new URL(supabaseUrl()).hostname
|
|
@@ -70,7 +72,7 @@ async function loginViaMailpit(supabase, email, redirectTo, sinceMs) {
|
|
|
70
72
|
async function readCode(email) {
|
|
71
73
|
if (!process.stdin.isTTY) {
|
|
72
74
|
throw new Error(
|
|
73
|
-
`Code sent to ${email}. Read it from that inbox, then run:
|
|
75
|
+
`Code sent to ${email}. Read it from that inbox, then run: ${CLI} login ${email} --code <code>`,
|
|
74
76
|
)
|
|
75
77
|
}
|
|
76
78
|
const rl = createInterface({ input: process.stdin, output: process.stderr })
|
package/lib/session.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { createClient } from '@supabase/supabase-js'
|
|
|
6
6
|
import { credentialsPath, loadDotEnv, supabaseAnonKey, supabaseUrl } from './config.mjs'
|
|
7
7
|
|
|
8
8
|
const REFRESH_WINDOW_MS = 60_000
|
|
9
|
+
const LOGIN_COMMAND = 'npx --yes amorphous-cli@latest login'
|
|
9
10
|
const LOCK_STALE_MS = 15_000
|
|
10
11
|
const LOCK_WAIT_MS = 20_000
|
|
11
12
|
|
|
@@ -58,7 +59,7 @@ async function withCredentialLock(dir, fn) {
|
|
|
58
59
|
/* the other process released it */
|
|
59
60
|
}
|
|
60
61
|
if (Date.now() - started > LOCK_WAIT_MS) {
|
|
61
|
-
throw new Error(
|
|
62
|
+
throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
|
|
62
63
|
}
|
|
63
64
|
await sleep(25)
|
|
64
65
|
}
|
|
@@ -115,11 +116,11 @@ export async function ensureFreshSession(options = {}) {
|
|
|
115
116
|
const now = options.now ?? Date.now()
|
|
116
117
|
const session = loadSession()
|
|
117
118
|
if (!session?.access_token) {
|
|
118
|
-
throw new Error(
|
|
119
|
+
throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
|
|
119
120
|
}
|
|
120
121
|
if (!options.force && !sessionNeedsRefresh(session, now)) return session
|
|
121
122
|
if (!session.refresh_token) {
|
|
122
|
-
throw new Error(
|
|
123
|
+
throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
|
|
123
124
|
}
|
|
124
125
|
const refresh = options.refresh ?? refreshWithSupabase
|
|
125
126
|
try {
|
|
@@ -135,7 +136,7 @@ export async function ensureFreshSession(options = {}) {
|
|
|
135
136
|
) {
|
|
136
137
|
return again
|
|
137
138
|
}
|
|
138
|
-
throw new Error(
|
|
139
|
+
throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
|
|
139
140
|
}
|
|
140
141
|
})
|
|
141
142
|
}
|
|
@@ -148,7 +149,7 @@ export function accessToken() {
|
|
|
148
149
|
export function authHeaders(extra = {}) {
|
|
149
150
|
const token = accessToken()
|
|
150
151
|
if (!token) {
|
|
151
|
-
throw new Error(
|
|
152
|
+
throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
|
|
152
153
|
}
|
|
153
154
|
return {
|
|
154
155
|
Authorization: `Bearer ${token}`,
|
package/lib/tool-result.test.mjs
CHANGED
|
@@ -2,14 +2,14 @@ import assert from 'node:assert/strict'
|
|
|
2
2
|
import test from 'node:test'
|
|
3
3
|
import { toolText } from './tool-result.mjs'
|
|
4
4
|
|
|
5
|
-
const NUDGE = 'Run:
|
|
5
|
+
const NUDGE = 'Run: npx --yes amorphous-cli@latest setup'
|
|
6
6
|
|
|
7
7
|
test('a successful command keeps stderr the model must see', () => {
|
|
8
8
|
assert.equal(toolText('[{"id":"a"}]', `${NUDGE}\n`, 0), `[{"id":"a"}]\n${NUDGE}`)
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
test('a failed command returns the error text', () => {
|
|
12
|
-
assert.equal(toolText('', 'Not signed in. Run: amorphous-cli login\n', 1), 'Not signed in. Run: amorphous-cli login')
|
|
12
|
+
assert.equal(toolText('', 'Not signed in. Run: npx --yes amorphous-cli@latest login\n', 1), 'Not signed in. Run: npx --yes amorphous-cli@latest login')
|
|
13
13
|
})
|
|
14
14
|
|
|
15
15
|
test('empty success is ok', () => {
|
package/lib/update-check.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { userSkillDirs } from './agent-contract.mjs'
|
|
|
8
8
|
import { CLI_VERSION } from './version.mjs'
|
|
9
9
|
|
|
10
10
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000
|
|
11
|
-
const NUDGE = 'Run:
|
|
11
|
+
const NUDGE = 'Run: npx --yes amorphous-cli@latest setup\n'
|
|
12
12
|
|
|
13
13
|
function parseVersion(v) {
|
|
14
14
|
if (typeof v !== 'string') return null
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
startUpdateCheck,
|
|
15
15
|
} from './update-check.mjs'
|
|
16
16
|
|
|
17
|
-
const NUDGE = 'Run:
|
|
17
|
+
const NUDGE = 'Run: npx --yes amorphous-cli@latest setup\n'
|
|
18
18
|
|
|
19
19
|
test('compareVersions', () => {
|
|
20
20
|
assert.equal(compareVersions('1.0.0', '1.0.0'), 0)
|
|
@@ -252,7 +252,8 @@ test('non-setup refreshes a stale skill when registry matches', async () => {
|
|
|
252
252
|
await update.finish()
|
|
253
253
|
assert.equal(err, '')
|
|
254
254
|
assert.equal(readFileSync(join(d0, '.amorphous-version'), 'utf8').trim(), CLI_VERSION)
|
|
255
|
-
assert.match(readFileSync(join(d0, 'SKILL.md'), 'utf8'), /
|
|
255
|
+
assert.match(readFileSync(join(d0, 'SKILL.md'), 'utf8'), /npx --yes amorphous-cli@latest <command>/)
|
|
256
|
+
assert.doesNotMatch(readFileSync(join(d0, 'SKILL.md'), 'utf8'), /npm install -g amorphous-cli@latest/)
|
|
256
257
|
})
|
|
257
258
|
|
|
258
259
|
test('a newer registry is not copied over the installed skill', async () => {
|
|
@@ -291,6 +292,17 @@ test('integration help skips update check file', () => {
|
|
|
291
292
|
encoding: 'utf8',
|
|
292
293
|
})
|
|
293
294
|
assert.equal(r.status, 0)
|
|
294
|
-
assert.ok(!r.stderr.includes('Run:
|
|
295
|
+
assert.ok(!r.stderr.includes('Run: npx --yes amorphous-cli@latest setup'))
|
|
296
|
+
assert.match(r.stdout, /Run npx --yes amorphous-cli@latest <command> --help for flags/)
|
|
295
297
|
assert.throws(() => readFileSync(cacheFile(home)), { code: 'ENOENT' })
|
|
296
298
|
})
|
|
299
|
+
|
|
300
|
+
test('skill and session hook use the same no-global-install command', () => {
|
|
301
|
+
const repoRoot = join(import.meta.dirname, '../..')
|
|
302
|
+
const skill = readFileSync(join(repoRoot, 'cli/skill/amorphous/SKILL.md'), 'utf8')
|
|
303
|
+
const hook = readFileSync(join(repoRoot, 'cli/hooks/session-start.mjs'), 'utf8')
|
|
304
|
+
assert.match(skill, /npx --yes amorphous-cli@latest <command>/)
|
|
305
|
+
assert.doesNotMatch(skill, /npm install -g amorphous-cli@latest/)
|
|
306
|
+
assert.match(hook, /Run commands with npx --yes amorphous-cli@latest/)
|
|
307
|
+
assert.doesNotMatch(hook, /npm install -g amorphous-cli@latest/)
|
|
308
|
+
})
|
package/mcp.mjs
CHANGED
|
@@ -72,7 +72,7 @@ await serveStdio(() => {
|
|
|
72
72
|
{
|
|
73
73
|
title: 'Pull the abstract',
|
|
74
74
|
description:
|
|
75
|
-
'Download the abstract files and records into this folder. Overwrites the local copies of those files. Prefer amorphous-cli serve while editing; it reloads as the files change.',
|
|
75
|
+
'Download the abstract files and records into this folder. Overwrites the local copies of those files. Prefer npx --yes amorphous-cli@latest serve while editing; it reloads as the files change.',
|
|
76
76
|
inputSchema: z.object({}),
|
|
77
77
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true },
|
|
78
78
|
},
|
package/package.json
CHANGED
package/skill/amorphous/SKILL.md
CHANGED
|
@@ -40,7 +40,7 @@ They tell you when to push, preview, publish, or invite. Do those when they ask.
|
|
|
40
40
|
|
|
41
41
|
## Start
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
Run CLI commands as `npx --yes amorphous-cli@latest <command>`; this uses the latest CLI without a global install. The command examples below abbreviate that prefix as `amorphous-cli`. If a command prints `Run: npx --yes amorphous-cli@latest setup`, run that line, then repeat the command. If you are not signed in, run `npx --yes amorphous-cli@latest login`. Show the person the link it prints and ask them to finish in the browser. Do not ask for an email or a code.
|
|
44
44
|
|
|
45
45
|
Then choose the path that matches the request:
|
|
46
46
|
|