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 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.
@@ -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. Install the CLI with npm install -g amorphous-cli@latest and invoke it as amorphous-cli.',
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': 'Could not invite. Run: amorphous-cli people',
7
- 'Cannot revoke owner': "You can't remove the owner. Run: amorphous-cli people",
8
- 'Cannot remove yourself': "You can't remove yourself. Run: amorphous-cli people",
9
- 'Collection not found': 'Folder not found. Run: amorphous-cli folder list',
10
- Forbidden: "You don't have access. Run: amorphous-cli list",
11
- 'Not Found': 'Not found. Run: amorphous-cli list',
12
- 'Owner required': 'Only the owner can do that. Run: amorphous-cli people',
13
- 'id required': 'Missing id. Run: amorphous-cli list',
14
- 'userId required': 'Pass an email or id. Run: amorphous-cli people',
15
- 'Invalid body': 'Invalid request. Run: amorphous-cli pull',
16
- 'Name required': 'Name required. Run: amorphous-cli rename --name <name>',
17
- 'Create failed': 'Could not create the abstract. Run: amorphous-cli list',
18
- 'Publish failed': 'Could not publish. Run: amorphous-cli push',
19
- 'File too large': 'That file is too large. Run: amorphous-cli pull',
20
- 'Not logged in. Run: amorphous login <email>': 'Not signed in. Run: amorphous-cli login',
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: amorphous-cli list`
30
+ return `${stripped}. Run: ${CLI} list`
30
31
  }
31
32
  if (/^This abstract already has \d+ people$/.test(stripped)) {
32
- return `${stripped}. Run: amorphous-cli people`
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('No .amorphous.json here. Run amorphous-cli create first.')
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('The signed-in Amorphous session has no valid user identity. Run amorphous-cli login.')
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('Agent guides exceed the combined 48 KiB UTF-8 context limit. Neither guide was loaded; reduce their size and run amorphous-cli context again.')
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('Guide identity is uncertain. Existing local files were preserved in recovery and neither guide was loaded; confirm the account and run amorphous-cli context again.')
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('Guide identity changed during context loading. Do not use either local guide; confirm the account and run amorphous-cli context again.')
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('Use: amorphous-cli guide save <abstract|private> [abstract].')
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('Guide identity is uncertain; preserved local files are not eligible for saving. Run context after confirming the signed-in account.')
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('No saved guide revision is available. Run amorphous-cli context first.')
329
- if (base.revision !== current.revision) throw new Error('The server guide changed since context was loaded. Run amorphous-cli context, resolve any conflict, then save again.')
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 amorphous-cli serve while you edit. Open the URL and leave it running. It reloads when the files change.
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('Prefer amorphous-cli serve while you edit.') }
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('Prefer amorphous-cli serve while you edit.')
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('Prefer amorphous-cli serve while you edit.')
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: amorphous-cli pull`)
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 amorphous-cli --help.`)
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 installed version.
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: amorphous-cli login ${email} --code <code>`,
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('Not signed in. Run: amorphous-cli login')
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('Not signed in. Run: amorphous-cli login')
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('Not signed in. Run: amorphous-cli login')
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('Not signed in. Run: amorphous-cli login')
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('Not signed in. Run: amorphous-cli login')
152
+ throw new Error(`Not signed in. Run: ${LOGIN_COMMAND}`)
152
153
  }
153
154
  return {
154
155
  Authorization: `Bearer ${token}`,
@@ -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: npm install -g amorphous-cli@latest && amorphous-cli setup'
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', () => {
@@ -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: npm install -g amorphous-cli@latest && amorphous-cli setup\n'
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: npm install -g amorphous-cli@latest && amorphous-cli setup\n'
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'), /npm install -g amorphous-cli@latest/)
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: npm install -g amorphous-cli@latest && amorphous-cli setup'))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amorphous-cli",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "mcpName": "io.github.amorphous-one/amorphous",
5
5
  "description": "Make whatever you want, yourself, with your own agent, or with others.",
6
6
  "type": "module",
@@ -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
- Install the CLI once with `npm install -g amorphous-cli@latest`, then call it as `amorphous-cli <command>`. If a command prints `Run: npm install -g amorphous-cli@latest && amorphous-cli setup`, run that line, then repeat the command. If you are not signed in, run `amorphous-cli 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.
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