create-sl-ide 0.0.0-stage → 0.1.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 CHANGED
@@ -1,3 +1,51 @@
1
- # Temporary Holding Version
1
+ # create-sl-ide
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Start a slide deck with [sl-ide](https://github.com/dberm312/sl-ide-template),
4
+ the IDE for building slides with Claude Code or Codex.
5
+
6
+ ```sh
7
+ pnpm create sl-ide my-deck
8
+ pnpm create sl-ide my-memo --memo # a memo on US Letter pages instead of slides
9
+ # or: npm create sl-ide@latest my-deck · bun create sl-ide my-deck
10
+ ```
11
+
12
+ It asks a few questions, then:
13
+
14
+ 1. Clones the **latest** sl-ide template from GitHub, keeping its history as
15
+ the `upstream` remote, so `git pull upstream main` brings later editor
16
+ updates into your deck. The editor isn't bundled in this package, so every
17
+ new deck starts from the template's current `main`.
18
+ 2. Creates the deck's own GitHub repo (private by default).
19
+ 3. Sets it up to publish to [sl-ide cloud](https://sl-ide.com): signs you in
20
+ through your browser, saves a token for your team, writes
21
+ `sl-ide.config.json` and sets the repo's `SL_IDE_PUBLISH_TOKEN` secret.
22
+ Every push to `main` then updates `sl-ide.com/d/my-deck/`.
23
+ 4. Runs `pnpm install`.
24
+
25
+ Then `cd my-deck && pnpm dev`, open Claude Code or Codex there, and run
26
+ `/new-deck`.
27
+
28
+ ## Options
29
+
30
+ ```
31
+ --format <slides|memo> 16:9 slides, or a US Letter memo (default slides; --memo for short)
32
+ --dir <path> folder for the deck (default ./<name>)
33
+ --github <private|public|none> the deck's repo (default private)
34
+ --team <slug> the sl-ide cloud team that owns the deck
35
+ --share <team|public> who can open its link (default team)
36
+ --no-publish skip sl-ide cloud
37
+ --no-install skip pnpm install
38
+ --no-open print the sign-in link instead of opening it
39
+ -y, --yes take the defaults instead of asking
40
+ ```
41
+
42
+ ```sh
43
+ pnpm create sl-ide login # sign in and save a token for a team
44
+ pnpm create sl-ide logout # forget saved tokens
45
+ ```
46
+
47
+ Tokens are saved in `~/.config/sl-ide/credentials.json`, readable only by
48
+ you, and can be revoked at sl-ide.com/tokens.
49
+
50
+ Needs Node 20.12+, git and pnpm, plus the GitHub CLI (`gh`) for the repo
51
+ and publishing.
package/index.mjs ADDED
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env node
2
+ // pnpm create sl-ide [name] [options] (also npm create sl-ide, bunx create-sl-ide)
3
+ //
4
+ // Starts a new deck: clones the latest sl-ide template from GitHub (with its
5
+ // history, as `upstream`, so `git pull upstream main` brings later editor
6
+ // updates), creates the deck's own repo, sets it up to publish to sl-ide
7
+ // cloud, and installs dependencies. The editor itself is never bundled here,
8
+ // so a new deck always starts from the template's main.
9
+ //
10
+ // pnpm create sl-ide . start the deck in this (empty) folder, named after it
11
+ // pnpm create sl-ide login sign in to sl-ide cloud and save a team token
12
+ // pnpm create sl-ide logout forget saved tokens
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+ import { parseArgs } from 'node:util';
16
+ import * as p from '@clack/prompts';
17
+ import { CLOUD, credsPath, deviceLogin, forget, legacyToken, readCreds, saveToken, slugAvailable, whoami } from './lib/cloud.mjs';
18
+ import { ghReady, has, run } from './lib/run.mjs';
19
+
20
+ const TEMPLATE = process.env.SL_IDE_TEMPLATE || 'https://github.com/dberm312/sl-ide-template.git';
21
+ const SLUG_RE = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
22
+
23
+ const HELP = `
24
+ pnpm create sl-ide [name] [options]
25
+ pnpm create sl-ide . [options] in this empty folder, named after it
26
+
27
+ Start a new deck (or memo) from the latest sl-ide template.
28
+
29
+ Options
30
+ --format <slides|memo> 16:9 slides, or a memo on US Letter pages (default: slides)
31
+ --memo short for --format memo
32
+ --dir <path> folder to create the deck in (default: ./<name>)
33
+ --github <private|public|none> the deck's GitHub repo (default: private)
34
+ --team <slug> the sl-ide cloud team that owns the deck
35
+ --share <team|public> who can open its link (default: team)
36
+ --no-publish don't set up publishing to sl-ide cloud
37
+ --no-install skip pnpm install
38
+ --no-open print the sign-in link instead of opening a browser
39
+ -y, --yes take the defaults instead of asking
40
+ -h, --help
41
+
42
+ Commands
43
+ login sign in to sl-ide cloud and save a team token
44
+ logout [team] forget saved tokens
45
+
46
+ Every push to the deck's repo publishes it to ${CLOUD}/d/<name>/.
47
+ `;
48
+
49
+ const { values: o, positionals } = parseArgs({
50
+ allowPositionals: true,
51
+ options: {
52
+ format: { type: 'string' },
53
+ memo: { type: 'boolean' },
54
+ dir: { type: 'string' },
55
+ github: { type: 'string' },
56
+ team: { type: 'string' },
57
+ share: { type: 'string' },
58
+ publish: { type: 'boolean', default: true },
59
+ 'no-publish': { type: 'boolean' },
60
+ install: { type: 'boolean', default: true },
61
+ 'no-install': { type: 'boolean' },
62
+ open: { type: 'boolean', default: true },
63
+ 'no-open': { type: 'boolean' },
64
+ yes: { type: 'boolean', short: 'y' },
65
+ help: { type: 'boolean', short: 'h' },
66
+ },
67
+ });
68
+ if (o['no-publish']) o.publish = false;
69
+ if (o['no-install']) o.install = false;
70
+ if (o['no-open']) o.open = false;
71
+ if (o.memo) o.format = 'memo';
72
+ if (o.help) { console.log(HELP); process.exit(0); }
73
+
74
+ const interactive = process.stdin.isTTY && !o.yes;
75
+ const bail = (msg) => { p.cancel(msg); process.exit(1); };
76
+ const ask = async (prompt) => {
77
+ const v = await prompt;
78
+ if (p.isCancel(v)) bail('Cancelled.');
79
+ return v;
80
+ };
81
+
82
+ // --- sign-in ---------------------------------------------------------------
83
+
84
+ async function signIn() {
85
+ const s = p.spinner();
86
+ const got = await deviceLogin({
87
+ open: o.open,
88
+ onCode: (code, url) => {
89
+ p.note(`${url}\n\nCode: ${code}`, o.open ? 'Approve in your browser' : 'Open this link to approve');
90
+ s.start('Waiting for approval');
91
+ },
92
+ }).catch((e) => { s.stop('Sign-in failed'); bail(e.message); });
93
+ s.stop(`Signed in for team ${got.team}`);
94
+ saveToken(got.team, got.token);
95
+ return got;
96
+ }
97
+
98
+ // A token for the deck's team: a saved one, the legacy admin token, or a new
99
+ // browser sign-in. Returns { team, token } or null to skip publishing.
100
+ async function publishCredentials() {
101
+ const saved = readCreds().teams;
102
+ const teams = Object.keys(saved);
103
+ const admin = legacyToken();
104
+
105
+ if (o.team) {
106
+ if (!SLUG_RE.test(o.team)) bail(`--team "${o.team}" isn't a team slug`);
107
+ if (saved[o.team]) return { team: o.team, token: saved[o.team].token };
108
+ if (admin) return { team: o.team, token: admin };
109
+ p.log.info(`No saved token for team ${o.team}. Sign in to make one.`);
110
+ const got = await signIn();
111
+ if (got.team !== o.team) p.log.warn(`You approved team ${got.team}; publishing for that team instead.`);
112
+ return got;
113
+ }
114
+
115
+ if (!interactive) {
116
+ if (teams.length === 1) return { team: teams[0], token: saved[teams[0]].token };
117
+ p.log.warn('Not publishing: pass --team <slug>, or run pnpm create sl-ide login first.');
118
+ return null;
119
+ }
120
+
121
+ const choice = await ask(p.select({
122
+ message: 'Publish this deck to sl-ide cloud?',
123
+ options: [
124
+ ...teams.map((t) => ({ value: `team:${t}`, label: `Yes, for team ${t}` })),
125
+ { value: 'login', label: teams.length ? 'Yes, for another team (sign in)' : 'Yes (sign in to sl-ide cloud)', hint: 'opens your browser' },
126
+ { value: 'skip', label: 'Not now', hint: 'add sl-ide.config.json later' },
127
+ ],
128
+ }));
129
+ if (choice === 'skip') return null;
130
+ if (choice === 'login') return signIn();
131
+ const team = choice.slice(5);
132
+ return { team, token: saved[team].token };
133
+ }
134
+
135
+ // --- commands --------------------------------------------------------------
136
+
137
+ async function login() {
138
+ p.intro('sl-ide cloud');
139
+ await signIn();
140
+ p.outro(`Saved to ${credsPath}. New decks can publish for this team.`);
141
+ }
142
+
143
+ function logout() {
144
+ forget(positionals[1]);
145
+ console.log(positionals[1] ? `Forgot the token for ${positionals[1]}.` : 'Forgot all saved sl-ide tokens.');
146
+ }
147
+
148
+ async function create() {
149
+ p.intro('sl-ide · new deck');
150
+
151
+ // `.` builds the deck in the current folder and names it after the folder.
152
+ const here = positionals[0] === '.';
153
+ let name = here ? path.basename(process.cwd()) : positionals[0];
154
+ if (!name) {
155
+ if (!interactive) bail('Give the deck a name: pnpm create sl-ide my-deck');
156
+ name = await ask(p.text({
157
+ message: 'Deck name',
158
+ placeholder: 'acme-pitch',
159
+ validate: (v) => (/^[A-Za-z0-9._-]+$/.test(v ?? '') ? undefined : 'Letters, digits, . _ and - only'),
160
+ }));
161
+ }
162
+ if (!/^[A-Za-z0-9._-]+$/.test(name)) bail(`"${name}" isn't a valid name (letters, digits, . _ -)`);
163
+ const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
164
+ if (!SLUG_RE.test(slug)) bail(`can't make a link name from "${name}"`);
165
+
166
+ // Slides or a memo. A memo is the same template with the memo starter
167
+ // swapped in by its own script (scripts/use-format.mjs).
168
+ let format = o.format;
169
+ if (!format) {
170
+ format = interactive ? await ask(p.select({
171
+ message: 'What are you making?',
172
+ options: [
173
+ { value: 'slides', label: 'Slides', hint: '16:9 pages' },
174
+ { value: 'memo', label: 'A memo', hint: 'US Letter pages, text flows from page to page' },
175
+ ],
176
+ })) : 'slides';
177
+ }
178
+ if (!['slides', 'memo'].includes(format)) bail('--format is slides or memo');
179
+
180
+ const dest = path.resolve(o.dir ?? (here ? '.' : name));
181
+ if (fs.existsSync(dest)) {
182
+ // Finder leaves .DS_Store in folders it opens; it would stop git clone.
183
+ const left = fs.readdirSync(dest).filter((f) => f !== '.DS_Store');
184
+ if (left.length) bail(`${dest} already exists and isn't empty`);
185
+ fs.rmSync(path.join(dest, '.DS_Store'), { force: true });
186
+ }
187
+
188
+ for (const tool of ['git', 'pnpm']) {
189
+ if (!has(tool)) bail(tool === 'pnpm' ? 'pnpm is needed: corepack enable pnpm' : 'git is needed');
190
+ }
191
+
192
+ // GitHub repo
193
+ let github = o.github;
194
+ const gh = ghReady();
195
+ if (!github) {
196
+ github = !gh ? 'none'
197
+ : interactive ? await ask(p.select({
198
+ message: 'GitHub repo for the deck',
199
+ options: [
200
+ { value: 'private', label: 'Private', hint: 'recommended' },
201
+ { value: 'public', label: 'Public' },
202
+ { value: 'none', label: 'None for now' },
203
+ ],
204
+ })) : 'private';
205
+ if (!gh) p.log.warn('GitHub CLI not signed in (gh auth login), so no repo will be made. Publishing needs one.');
206
+ }
207
+ if (!['private', 'public', 'none'].includes(github)) bail('--github is private, public or none');
208
+ if (github !== 'none' && !gh) bail('gh is not installed or not signed in: gh auth login');
209
+ let owner = null;
210
+ if (github !== 'none') {
211
+ owner = await run('gh', ['api', 'user', '-q', '.login']);
212
+ const exists = await run('gh', ['repo', 'view', `${owner}/${name}`]).then(() => true, () => false);
213
+ if (exists) bail(`github.com/${owner}/${name} already exists`);
214
+ }
215
+
216
+ // Publishing
217
+ let pub = null;
218
+ let share = o.share ?? 'team';
219
+ if (o.publish && github !== 'none') {
220
+ pub = await publishCredentials();
221
+ if (pub) {
222
+ const ok = await whoami(pub.token);
223
+ if (!ok) bail(`The saved token for ${pub.team} was rejected (revoked?). Run: pnpm create sl-ide login`);
224
+ const avail = await slugAvailable(pub.token, slug);
225
+ if (!avail.available) bail(`Can't publish as ${slug}: ${avail.reason}. Pick another name.`);
226
+ if (avail.exists) p.log.warn(`Team ${pub.team} already has a deck called ${slug}; this one will publish over it.`);
227
+ if (!o.share && interactive) {
228
+ share = await ask(p.select({
229
+ message: 'Who can open its link?',
230
+ options: [
231
+ { value: 'team', label: `Team ${pub.team} only`, hint: 'signed in' },
232
+ { value: 'public', label: 'Anyone with the link' },
233
+ ],
234
+ }));
235
+ }
236
+ if (!['team', 'public'].includes(share)) bail('--share is team or public');
237
+ }
238
+ } else if (o.publish && github === 'none') {
239
+ p.log.info('Publishing runs in GitHub Actions, so it is off without a repo.');
240
+ }
241
+
242
+ // Do it
243
+ const s = p.spinner();
244
+ try {
245
+ s.start('Cloning the latest sl-ide template');
246
+ await run('git', ['clone', '-q', TEMPLATE, dest]);
247
+ await run('git', ['remote', 'rename', 'origin', 'upstream'], { cwd: dest });
248
+ await run('git', ['config', 'pull.rebase', 'false'], { cwd: dest });
249
+ const head = await run('git', ['log', '-1', '--format=%h %s'], { cwd: dest });
250
+ s.stop(`Cloned sl-ide (${head.split(' ')[0]})`);
251
+
252
+ if (format !== 'slides') {
253
+ if (!fs.existsSync(path.join(dest, 'scripts', 'use-format.mjs'))) {
254
+ throw new Error(`this sl-ide template has no ${format} format yet`);
255
+ }
256
+ await run('node', ['scripts/use-format.mjs', format], { cwd: dest });
257
+ await run('git', ['add', '-A'], { cwd: dest });
258
+ await run('git', ['-c', 'core.hooksPath=/dev/null', 'commit', '-q', '-m', `Start from the ${format} starter`], { cwd: dest });
259
+ p.log.success(`Set up as a ${format}`);
260
+ }
261
+
262
+ if (pub) {
263
+ const config = { slug, team: pub.team, visibility: share };
264
+ fs.writeFileSync(path.join(dest, 'sl-ide.config.json'), `${JSON.stringify(config, null, 2)}\n`);
265
+ await run('git', ['add', 'sl-ide.config.json'], { cwd: dest });
266
+ await run('git', ['-c', 'core.hooksPath=/dev/null', 'commit', '-q', '-m', `Publish to sl-ide cloud as ${slug} (${share}, team ${pub.team})`], { cwd: dest });
267
+ }
268
+
269
+ if (github !== 'none') {
270
+ s.start(`Creating github.com/${owner}/${name}`);
271
+ await run('gh', ['repo', 'create', name, `--${github}`, '--source', '.', '--remote', 'origin'], { cwd: dest });
272
+ // Before the first push, so its CI run publishes.
273
+ if (pub) await run('gh', ['secret', 'set', 'SL_IDE_PUBLISH_TOKEN', '--repo', `${owner}/${name}`], { cwd: dest, input: pub.token });
274
+ await run('git', ['push', '-q', '-u', 'origin', 'main'], { cwd: dest });
275
+ s.stop(`Created github.com/${owner}/${name} (${github})`);
276
+ }
277
+
278
+ if (o.install) {
279
+ s.start('Installing dependencies');
280
+ await run('pnpm', ['install', '--silent'], { cwd: dest });
281
+ s.stop('Installed dependencies');
282
+ }
283
+ } catch (e) {
284
+ s.stop('Stopped');
285
+ bail(e.message);
286
+ }
287
+
288
+ const rel = path.relative(process.cwd(), dest) || '.';
289
+ const lines = [
290
+ ...(rel === '.' ? [] : [`cd ${rel}`]),
291
+ ...(o.install ? [] : ['pnpm install']),
292
+ 'pnpm dev',
293
+ '',
294
+ 'Then open Claude Code or Codex in that folder and run /new-deck',
295
+ `(Codex: $new-deck) to learn your style and outline the ${format === 'memo' ? 'memo' : 'story'}.`,
296
+ ];
297
+ p.note(lines.join('\n'), 'Next');
298
+ if (pub) {
299
+ p.log.success(`Every push to main publishes ${CLOUD}/d/${slug}/ (${share === 'public' ? 'anyone with the link' : `team ${pub.team}`}).`);
300
+ } else {
301
+ p.log.info('Not publishing. To publish later, add sl-ide.config.json (see the deck\'s README).');
302
+ }
303
+ p.outro(format === 'memo' ? 'Memo ready.' : 'Deck ready.');
304
+ }
305
+
306
+ const cmd = positionals[0];
307
+ if (cmd === 'login') await login();
308
+ else if (cmd === 'logout') logout();
309
+ else await create();
package/lib/cloud.mjs ADDED
@@ -0,0 +1,92 @@
1
+ // sl-ide cloud: saved credentials, browser sign-in, and the API calls the
2
+ // create flow needs. Credentials live in ~/.config/sl-ide/credentials.json
3
+ // (mode 600): one publish token per team.
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import { spawn } from 'node:child_process';
8
+
9
+ export const CLOUD = (process.env.SL_IDE_URL || 'https://sl-ide.com').replace(/\/$/, '');
10
+ const CONF = path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'sl-ide');
11
+ const CREDS = path.join(CONF, 'credentials.json');
12
+ const LEGACY = path.join(CONF, 'publish-token');
13
+
14
+ export function readCreds() {
15
+ try {
16
+ const c = JSON.parse(fs.readFileSync(CREDS, 'utf8'));
17
+ return { teams: {}, ...c };
18
+ } catch {
19
+ return { teams: {} };
20
+ }
21
+ }
22
+
23
+ export function saveToken(team, token) {
24
+ const c = readCreds();
25
+ c.url = CLOUD;
26
+ c.teams[team] = { token, savedAt: new Date().toISOString() };
27
+ fs.mkdirSync(CONF, { recursive: true });
28
+ fs.writeFileSync(CREDS, `${JSON.stringify(c, null, 2)}\n`, { mode: 0o600 });
29
+ fs.chmodSync(CREDS, 0o600);
30
+ }
31
+
32
+ export function forget(team) {
33
+ const c = readCreds();
34
+ if (team) delete c.teams[team];
35
+ else c.teams = {};
36
+ if (fs.existsSync(CREDS)) fs.writeFileSync(CREDS, `${JSON.stringify(c, null, 2)}\n`, { mode: 0o600 });
37
+ }
38
+
39
+ // An admin token from before team tokens existed: publishes for any team.
40
+ export function legacyToken() {
41
+ try { return fs.readFileSync(LEGACY, 'utf8').trim() || null; } catch { return null; }
42
+ }
43
+
44
+ export const credsPath = CREDS;
45
+
46
+ async function api(pathname, { token, method = 'GET', body } = {}) {
47
+ const res = await fetch(`${CLOUD}${pathname}`, {
48
+ method,
49
+ headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(body ? { 'Content-Type': 'application/json' } : {}) },
50
+ body: body ? JSON.stringify(body) : undefined,
51
+ });
52
+ const json = await res.json().catch(() => ({}));
53
+ return { status: res.status, ok: res.ok, json };
54
+ }
55
+
56
+ // Is the saved token still good? -> { team } or null.
57
+ export async function whoami(token) {
58
+ const r = await api('/api/cli/whoami', { token }).catch(() => null);
59
+ return r?.ok ? r.json : null;
60
+ }
61
+
62
+ export async function slugAvailable(token, slug) {
63
+ const r = await api(`/api/cli/slug/${slug}`, { token });
64
+ return r.ok ? r.json : { available: false, reason: r.json.error || `HTTP ${r.status}` };
65
+ }
66
+
67
+ export function openBrowser(url) {
68
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
69
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
70
+ try {
71
+ spawn(cmd, args, { stdio: 'ignore', detached: true }).on('error', () => {}).unref();
72
+ } catch { /* no browser: the URL is printed anyway */ }
73
+ }
74
+
75
+ // Browser sign-in. onCode(user_code, url) is called once, to show the code;
76
+ // resolves { team, token } once a team member approves it at sl-ide.com/cli.
77
+ export async function deviceLogin({ onCode, open = true }) {
78
+ const start = await api('/api/cli/login/start', { method: 'POST', body: { client: os.hostname().replace(/\.local$/, '') } });
79
+ if (!start.ok) throw new Error(`could not reach ${CLOUD} (${start.status})`);
80
+ const { device_code: device, user_code: code, verification_uri_complete: url, interval = 2, expires_in: ttl = 600 } = start.json;
81
+ onCode(code, url);
82
+ if (open) openBrowser(url);
83
+ const until = Date.now() + ttl * 1000;
84
+ while (Date.now() < until) {
85
+ await new Promise((r) => setTimeout(r, interval * 1000));
86
+ const r = await api('/api/cli/login/poll', { method: 'POST', body: { device_code: device } }).catch(() => null);
87
+ if (!r || r.status === 202) continue;
88
+ if (r.ok && r.json.token) return { team: r.json.team, token: r.json.token };
89
+ throw new Error('the sign-in code expired; run the command again');
90
+ }
91
+ throw new Error('the sign-in code expired; run the command again');
92
+ }
package/lib/run.mjs ADDED
@@ -0,0 +1,23 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+
3
+ // Run a command; resolve its stdout, reject with its stderr.
4
+ export function run(cmd, args, { cwd, input, quiet = true } = {}) {
5
+ return new Promise((resolve, reject) => {
6
+ const p = spawn(cmd, args, { cwd, stdio: [input == null ? 'ignore' : 'pipe', 'pipe', 'pipe'] });
7
+ let out = '';
8
+ let err = '';
9
+ p.stdout.on('data', (d) => { out += d; if (!quiet) process.stdout.write(d); });
10
+ p.stderr.on('data', (d) => { err += d; });
11
+ if (input != null) { p.stdin.write(input); p.stdin.end(); }
12
+ p.on('error', reject);
13
+ p.on('close', (code) => (code === 0 ? resolve(out.trim()) : reject(new Error(`${cmd} ${args.join(' ')}: ${err.trim() || `exit ${code}`}`))));
14
+ });
15
+ }
16
+
17
+ export function has(cmd) {
18
+ return spawnSync(process.platform === 'win32' ? 'where' : 'which', [cmd], { stdio: 'ignore' }).status === 0;
19
+ }
20
+
21
+ export function ghReady() {
22
+ return has('gh') && spawnSync('gh', ['auth', 'status'], { stdio: 'ignore' }).status === 0;
23
+ }
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "create-sl-ide",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Start a slide deck with sl-ide: pnpm create sl-ide",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-sl-ide": "index.mjs"
8
+ },
9
+ "files": [
10
+ "index.mjs",
11
+ "lib/"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20.12"
15
+ },
16
+ "keywords": [
17
+ "sl-ide",
18
+ "slides",
19
+ "deck",
20
+ "presentation",
21
+ "claude-code",
22
+ "codex",
23
+ "create"
24
+ ],
25
+ "homepage": "https://sl-ide.com",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/dberm312/create-sl-ide.git"
29
+ },
30
+ "author": "David Berman",
31
+ "packageManager": "pnpm@12.11.2",
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.8.1"
34
+ }
35
+ }