@useguano/create 0.1.2 → 0.1.4
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 +15 -6
- package/index.js +111 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,15 +6,24 @@ Scaffold a new [Guano](https://www.npmjs.com/package/@useguano/guano) site proje
|
|
|
6
6
|
npm create @useguano
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Asks for a folder name (default `my-site`),
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Asks for a folder name (default `my-site`), whether to connect **Claude
|
|
10
|
+
Desktop** over MCP (default yes — quit Claude first), and the two agent
|
|
11
|
+
permissions (may it edit the live project, may it publish — both default no).
|
|
12
|
+
Then it creates the folder with `@useguano/guano` as a dependency plus
|
|
13
|
+
`dev`/`start`/`build`/`connect` scripts, a `.env.example` and a `.gitignore`,
|
|
14
|
+
runs `npm install`, and connects Claude. Then:
|
|
12
15
|
|
|
13
16
|
```sh
|
|
14
17
|
cd my-site
|
|
15
18
|
npm run dev
|
|
16
19
|
```
|
|
17
20
|
|
|
18
|
-
Open `http://localhost:4174/admin` and create the first account
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
Open `http://localhost:4174/admin` and create the first account — the Claude
|
|
22
|
+
token is bound to it, and the form shows the permissions you chose. Change
|
|
23
|
+
them any time in Settings → MCP → Agent permissions.
|
|
24
|
+
|
|
25
|
+
Flags: pass the folder to skip that prompt (`npm create @useguano my-site`);
|
|
26
|
+
`--no-install` skips the install; `--no-connect` skips Claude, `--connect`
|
|
27
|
+
connects without asking, `--main` / `--publish` grant the permissions
|
|
28
|
+
non-interactively. Without a terminal (CI) nothing is asked and Claude is not
|
|
29
|
+
connected.
|
package/index.js
CHANGED
|
@@ -1,30 +1,81 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @useguano/create: scaffold a project dir that runs Guano via npm scripts,
|
|
3
|
-
// install it
|
|
4
|
-
//
|
|
2
|
+
// @useguano/create: scaffold a project dir that runs Guano via npm scripts,
|
|
3
|
+
// install it, and connect Claude Desktop — all in this one command, so what
|
|
4
|
+
// is left afterwards is `cd my-site && npm run dev`. Zero dependencies —
|
|
5
|
+
// prompts with node:readline.
|
|
6
|
+
//
|
|
7
|
+
// npm create @useguano [dir] [--no-install] [--no-connect | --connect]
|
|
8
|
+
// [--main] [--publish] [--config <claude config path>]
|
|
9
|
+
//
|
|
10
|
+
// The MCP connect runs through the just-installed package's own
|
|
11
|
+
// `guano connect --offline`: it mints a PENDING API token into <dir>/data,
|
|
12
|
+
// writes the two agent permissions there, and writes Claude Desktop's config.
|
|
13
|
+
// The server binds the token to the first admin at /admin setup, and the
|
|
14
|
+
// setup form shows the permissions instead of asking them again.
|
|
5
15
|
import { spawnSync } from 'node:child_process'
|
|
16
|
+
import { existsSync } from 'node:fs'
|
|
6
17
|
import { mkdir, readdir, writeFile } from 'node:fs/promises'
|
|
7
18
|
import { basename, join, resolve } from 'node:path'
|
|
19
|
+
import { pathToFileURL } from 'node:url'
|
|
8
20
|
import readline from 'node:readline/promises'
|
|
9
21
|
|
|
10
22
|
const args = process.argv.slice(2)
|
|
11
|
-
const
|
|
12
|
-
|
|
23
|
+
const flag = (name) => args.includes(name)
|
|
24
|
+
const opt = (name) => {
|
|
25
|
+
const i = args.indexOf(name)
|
|
26
|
+
return i !== -1 ? args[i + 1] : undefined
|
|
27
|
+
}
|
|
28
|
+
const install = !flag('--no-install')
|
|
29
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY
|
|
30
|
+
let dir = args.find((a, i) => !a.startsWith('--') && args[i - 1] !== '--config')
|
|
13
31
|
const DEFAULT_DIR = 'my-site'
|
|
32
|
+
|
|
33
|
+
// ---- every question first, so a ^C anywhere here changes nothing ----
|
|
34
|
+
const rl = interactive ? readline.createInterface({ input: process.stdin, output: process.stdout }) : null
|
|
35
|
+
const ask = (q) => rl.question(q)
|
|
36
|
+
const yes = async (q, dflt) => {
|
|
37
|
+
const a = (await ask(`${q} [${dflt ? 'Y/n' : 'y/N'}] `)).trim()
|
|
38
|
+
return a ? /^y(es)?$/i.test(a) : dflt
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
if (!dir) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
dir = rl ? (await ask(`Project folder (${DEFAULT_DIR}): `)).trim() || DEFAULT_DIR : DEFAULT_DIR
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Connect Claude Desktop? Flags decide non-interactively (CI never asks and
|
|
46
|
+
// never connects); a terminal asks, default yes.
|
|
47
|
+
let connect = false
|
|
48
|
+
let policyFlags = []
|
|
49
|
+
if (flag('--no-connect')) {
|
|
50
|
+
connect = false
|
|
51
|
+
} else if (flag('--connect') || flag('--main') || flag('--publish')) {
|
|
52
|
+
connect = true
|
|
53
|
+
} else if (rl) {
|
|
54
|
+
connect = await yes('Connect Claude Desktop to this site over MCP?', true)
|
|
55
|
+
}
|
|
56
|
+
if (connect) {
|
|
57
|
+
if (flag('--main')) policyFlags.push('--main')
|
|
58
|
+
if (flag('--publish')) policyFlags.push('--publish')
|
|
59
|
+
if (!policyFlags.length && rl && !flag('--connect')) {
|
|
60
|
+
console.log('\nTwo questions about what the agent may do (change them any time in\n/admin → Settings → MCP → Agent permissions):\n')
|
|
61
|
+
if (await yes('Let agents edit the live project (Main)? Off, they work in a draft you apply yourself.', false))
|
|
62
|
+
policyFlags.push('--main')
|
|
63
|
+
if (await yes('Let agents publish the site? Off, they can still preview their work.', false))
|
|
64
|
+
policyFlags.push('--publish')
|
|
65
|
+
console.log('')
|
|
66
|
+
}
|
|
18
67
|
}
|
|
19
68
|
|
|
20
69
|
const target = resolve(dir)
|
|
21
|
-
const name =
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
70
|
+
const name =
|
|
71
|
+
basename(target)
|
|
72
|
+
.toLowerCase()
|
|
73
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
74
|
+
.replace(/^-+|-+$/g, '') || DEFAULT_DIR
|
|
25
75
|
|
|
26
76
|
await mkdir(target, { recursive: true })
|
|
27
77
|
if ((await readdir(target)).length) {
|
|
78
|
+
rl?.close()
|
|
28
79
|
console.error(`${target} is not empty — refusing to scaffold over existing files`)
|
|
29
80
|
process.exit(1)
|
|
30
81
|
}
|
|
@@ -77,12 +128,17 @@ A [Guano](https://www.npmjs.com/package/@useguano/guano) site.
|
|
|
77
128
|
npm run dev
|
|
78
129
|
\`\`\`
|
|
79
130
|
|
|
80
|
-
Open http://localhost:4174/admin, create the first account, build your
|
|
81
|
-
Your published site is served at http://localhost:4174/.
|
|
131
|
+
Open http://localhost:4174/admin, create the first account, then build your
|
|
132
|
+
site. Your published site is served at http://localhost:4174/.
|
|
133
|
+
|
|
134
|
+
If you said yes to Claude Desktop when scaffolding, it is already connected:
|
|
135
|
+
the agent token is bound to the admin account you create, and the permissions
|
|
136
|
+
you chose (edit the live project, publish) show on the setup form and stay
|
|
137
|
+
editable in Settings → MCP → Agent permissions.
|
|
82
138
|
|
|
83
139
|
- \`npm run start\` — production server (\`NODE_ENV=production\`)
|
|
84
140
|
- \`npm run build\` — export the site as static files to \`./dist-site\`
|
|
85
|
-
- \`npm run connect\` — connect Claude Desktop
|
|
141
|
+
- \`npm run connect\` — connect Claude Desktop later, or from another machine (quit Claude first; \`--main\` / \`--publish\` grant the agent permissions, or it asks)
|
|
86
142
|
- everything lives in \`./data\` — **backing up = copying that directory**
|
|
87
143
|
|
|
88
144
|
See the @useguano/guano package README for deploy notes and environment variables.
|
|
@@ -104,8 +160,45 @@ if (install) {
|
|
|
104
160
|
if (!installed) console.error('\nnpm install failed — run it yourself in the project folder.')
|
|
105
161
|
}
|
|
106
162
|
|
|
163
|
+
// ---- connect Claude Desktop, through the installed package ----
|
|
164
|
+
// GUANO_CREATE_LOCAL_PKG is a dev-only escape hatch: with --no-install there
|
|
165
|
+
// is no node_modules, so a repo checkout names its own packages/guano here.
|
|
166
|
+
let connected = false
|
|
167
|
+
if (connect) {
|
|
168
|
+
const pkgDir = existsSync(join(target, 'node_modules', '@useguano', 'guano'))
|
|
169
|
+
? join(target, 'node_modules', '@useguano', 'guano')
|
|
170
|
+
: process.env.GUANO_CREATE_LOCAL_PKG
|
|
171
|
+
if (!pkgDir) {
|
|
172
|
+
console.error('\nClaude Desktop not connected: guano is not installed yet — run `npm run connect` inside the project later.')
|
|
173
|
+
} else {
|
|
174
|
+
try {
|
|
175
|
+
// the package resolves its data dir from this env at import time
|
|
176
|
+
const dataDir = join(target, 'data')
|
|
177
|
+
await mkdir(dataDir, { recursive: true, mode: 0o700 })
|
|
178
|
+
process.env.GUANO_DATA_DIR = dataDir
|
|
179
|
+
const { main } = await import(pathToFileURL(join(pkgDir, 'mcp', 'connect.mjs')).href)
|
|
180
|
+
const connectArgs = [...policyFlags, '--offline', '--yes']
|
|
181
|
+
if (opt('--config')) connectArgs.push('--config', resolve(opt('--config')))
|
|
182
|
+
console.log('\nConnecting Claude Desktop…')
|
|
183
|
+
await main(connectArgs, {
|
|
184
|
+
whenClaudeRunning: async () => {
|
|
185
|
+
if (!rl) return 'skip'
|
|
186
|
+
const a = (
|
|
187
|
+
await ask('Claude Desktop is running and overwrites its config on quit. Quit it, then press Enter (or type skip): ')
|
|
188
|
+
).trim()
|
|
189
|
+
return /^s(kip)?$/i.test(a) ? 'skip' : 'retry'
|
|
190
|
+
},
|
|
191
|
+
})
|
|
192
|
+
connected = true
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error(`\n✗ ${e?.message ?? e}\n Claude Desktop not connected — run \`npm run connect\` inside the project later.`)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
rl?.close()
|
|
199
|
+
|
|
107
200
|
console.log(`
|
|
108
201
|
Next:
|
|
109
|
-
cd ${dir}${installed ? '' : '\n npm install'}
|
|
110
|
-
npm run dev → http://localhost:4174/admin
|
|
111
|
-
`)
|
|
202
|
+
cd ${dir}${installed || !install ? '' : '\n npm install'}
|
|
203
|
+
npm run dev → http://localhost:4174/admin (create your admin account)
|
|
204
|
+
${connected ? '\nThen open Claude Desktop — guano is connected; the agent token is bound to the admin you create.\n' : ''}`)
|