promptslide 0.2.1 → 0.2.2

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.
@@ -4,7 +4,7 @@ import { join, resolve, dirname } from "node:path"
4
4
  import { fileURLToPath } from "node:url"
5
5
 
6
6
  import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
7
- import { hexToOklch, hexToOklchDark, isValidHex } from "../utils/colors.mjs"
7
+ import { hexToOklch, isValidHex } from "../utils/colors.mjs"
8
8
  import { prompt, confirm, closePrompts } from "../utils/prompts.mjs"
9
9
  import { ensureTsConfig } from "../utils/tsconfig.mjs"
10
10
 
@@ -56,21 +56,33 @@ export async function create(args) {
56
56
  console.log(` ${bold("promptslide")} ${dim("create")}`)
57
57
  console.log()
58
58
 
59
+ // Parse flags
60
+ const useDefaults = args.includes("--yes") || args.includes("-y")
61
+ const filteredArgs = args.filter(a => a !== "--yes" && a !== "-y")
62
+
59
63
  // 1. Parse directory name from args or prompt
60
- let dirName = args[0]
64
+ let dirName = filteredArgs[0]
61
65
 
62
66
  if (dirName === "--help" || dirName === "-h") {
63
- console.log(` ${bold("Usage:")} promptslide create ${dim("<project-directory>")}`)
67
+ console.log(` ${bold("Usage:")} promptslide create ${dim("<project-directory>")} ${dim("[options]")}`)
64
68
  console.log()
65
69
  console.log(` Scaffolds a new PromptSlide slide deck project.`)
66
70
  console.log()
71
+ console.log(` ${bold("Options:")}`)
72
+ console.log(` -y, --yes Skip prompts and use defaults`)
73
+ console.log()
67
74
  console.log(` ${bold("Example:")}`)
68
75
  console.log(` promptslide create my-pitch-deck`)
76
+ console.log(` promptslide create my-pitch-deck --yes`)
69
77
  console.log()
70
78
  process.exit(0)
71
79
  }
72
80
 
73
81
  if (!dirName) {
82
+ if (useDefaults) {
83
+ console.error(` ${red("Error:")} Please provide a project directory name when using --yes.`)
84
+ process.exit(1)
85
+ }
74
86
  dirName = await prompt("Project directory:")
75
87
  }
76
88
 
@@ -95,13 +107,16 @@ export async function create(args) {
95
107
 
96
108
  // 2. Ask for project/brand name
97
109
  const defaultName = titleCase(dirName)
98
- const projectName = await prompt("Project name:", defaultName)
110
+ const projectName = useDefaults ? defaultName : await prompt("Project name:", defaultName)
99
111
 
100
112
  // 3. Ask for primary brand color (optional)
101
- let primaryHex = await prompt("Primary brand color (hex):", "#3B82F6")
102
- if (!isValidHex(primaryHex)) {
103
- console.log(` ${dim("Invalid hex color, using default #3B82F6")}`)
104
- primaryHex = "#3B82F6"
113
+ let primaryHex = "#3B82F6"
114
+ if (!useDefaults) {
115
+ primaryHex = await prompt("Primary brand color (hex):", "#3B82F6")
116
+ if (!isValidHex(primaryHex)) {
117
+ console.log(` ${dim("Invalid hex color, using default #3B82F6")}`)
118
+ primaryHex = "#3B82F6"
119
+ }
105
120
  }
106
121
 
107
122
  console.log()
@@ -116,7 +131,6 @@ export async function create(args) {
116
131
 
117
132
  // 5. Replace placeholders
118
133
  const primaryOklch = hexToOklch(primaryHex)
119
- const primaryOklchDark = hexToOklchDark(primaryHex)
120
134
 
121
135
  const replacements = [
122
136
  {
@@ -138,8 +152,7 @@ export async function create(args) {
138
152
  {
139
153
  path: join(targetDir, "src", "globals.css"),
140
154
  values: {
141
- "{{PRIMARY_COLOR}}": primaryOklch,
142
- "{{PRIMARY_COLOR_DARK}}": primaryOklchDark
155
+ "{{PRIMARY_COLOR}}": primaryOklch
143
156
  }
144
157
  }
145
158
  ]
@@ -161,21 +174,25 @@ export async function create(args) {
161
174
  // 7. Generate tsconfig.json for editor support
162
175
  ensureTsConfig(targetDir)
163
176
 
164
- // 8. Install PromptSlide agent skill
165
- const installSkill = await confirm("Install PromptSlide agent skill?")
166
-
167
- if (installSkill) {
168
- console.log()
169
- console.log(` ${dim("Running: npx skills add prompticeu/promptslide")}`)
170
- try {
171
- execSync("npx skills add prompticeu/promptslide", {
172
- cwd: targetDir,
173
- stdio: "inherit"
174
- })
175
- console.log(` ${green("✓")} PromptSlide skill installed`)
176
- } catch {
177
- console.log(` ${red("⚠")} Skill installation failed. You can install it later with:`)
178
- console.log(` npx skills add prompticeu/promptslide`)
177
+ // 8. Install PromptSlide agent skill (defaults to yes; skipped with --yes since skills CLI is interactive)
178
+ if (useDefaults) {
179
+ console.log(` ${dim("Tip: Run")} npx skills add prompticeu/promptslide ${dim("to install the agent skill")}`)
180
+ } else {
181
+ const installSkill = await confirm("Install PromptSlide agent skill?")
182
+
183
+ if (installSkill) {
184
+ console.log()
185
+ console.log(` ${dim("Running: npx skills add prompticeu/promptslide")}`)
186
+ try {
187
+ execSync("npx skills add prompticeu/promptslide", {
188
+ cwd: targetDir,
189
+ stdio: "inherit"
190
+ })
191
+ console.log(` ${green("✓")} PromptSlide skill installed`)
192
+ } catch {
193
+ console.log(` ${red("⚠")} Skill installation failed. You can install it later with:`)
194
+ console.log(` npx skills add prompticeu/promptslide`)
195
+ }
179
196
  }
180
197
  }
181
198
 
@@ -190,7 +207,6 @@ export async function create(args) {
190
207
  console.log(` bun run dev`)
191
208
  console.log()
192
209
  console.log(` Then open your coding agent and start building slides!`)
193
- console.log(` The agent will read ${cyan("AGENTS.md")} to understand the framework.`)
194
210
  console.log()
195
211
 
196
212
  closePrompts()
@@ -0,0 +1,79 @@
1
+ import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
2
+ import { requireAuth } from "../utils/auth.mjs"
3
+ import { fetchRegistryItem, readLockfile } from "../utils/registry.mjs"
4
+
5
+ export async function info(args) {
6
+ console.log()
7
+ console.log(` ${bold("promptslide")} ${dim("info")}`)
8
+ console.log()
9
+
10
+ const name = args[0]
11
+ if (!name || name === "--help" || name === "-h") {
12
+ console.log(` ${bold("Usage:")} promptslide info ${dim("<name>")}`)
13
+ console.log()
14
+ console.log(` Show details about a registry item.`)
15
+ console.log()
16
+ console.log(` ${bold("Examples:")}`)
17
+ console.log(` promptslide info slide-hero-gradient`)
18
+ console.log(` promptslide info deck-pitch`)
19
+ console.log()
20
+ process.exit(0)
21
+ }
22
+
23
+ const auth = requireAuth()
24
+
25
+ let item
26
+ try {
27
+ item = await fetchRegistryItem(name, auth)
28
+ } catch (err) {
29
+ console.error(` ${red("Error:")} ${err.message}`)
30
+ process.exit(1)
31
+ }
32
+
33
+ // Check local install status
34
+ const cwd = process.cwd()
35
+ const lock = readLockfile(cwd)
36
+ const installed = lock.items[item.name]
37
+
38
+ const lines = [
39
+ ["Name", item.name],
40
+ ["Title", item.title || dim("—")],
41
+ ["Type", item.type],
42
+ ["Version", `v${item.version}`],
43
+ ["Steps", item.steps ?? item.meta?.steps ?? 0],
44
+ ["Downloads", item.downloads ?? 0],
45
+ ["Tags", item.tags?.length ? item.tags.join(", ") : dim("none")],
46
+ ["Description", item.description || dim("—")],
47
+ ]
48
+
49
+ if (item.dependencies && Object.keys(item.dependencies).length > 0) {
50
+ lines.push(["Dependencies", Object.entries(item.dependencies).map(([n, v]) => `${n}@${v}`).join(", ")])
51
+ }
52
+
53
+ if (item.registryDependencies?.length) {
54
+ lines.push(["Registry deps", item.registryDependencies.join(", ")])
55
+ }
56
+
57
+ if (item.files?.length) {
58
+ lines.push(["Files", item.files.map(f => `${f.target}${f.path}`).join(", ")])
59
+ }
60
+
61
+ if (installed) {
62
+ lines.push(["Installed", `${green("yes")} — v${installed.version} (${installed.installedAt})`])
63
+ if (Number(installed.version) < Number(item.version)) {
64
+ lines.push(["", `${cyan("Update available:")} v${installed.version} → v${item.version}`])
65
+ }
66
+ } else {
67
+ lines.push(["Installed", dim("no")])
68
+ }
69
+
70
+ // Find max label width for alignment
71
+ const maxLabel = Math.max(...lines.map(([label]) => label.length))
72
+
73
+ for (const [label, value] of lines) {
74
+ const paddedLabel = label ? `${label}:`.padEnd(maxLabel + 2) : " ".repeat(maxLabel + 2)
75
+ console.log(` ${dim(paddedLabel)} ${value}`)
76
+ }
77
+
78
+ console.log()
79
+ }
@@ -0,0 +1,37 @@
1
+ import { bold, cyan, red, dim } from "../utils/ansi.mjs"
2
+ import { requireAuth } from "../utils/auth.mjs"
3
+ import { searchRegistry } from "../utils/registry.mjs"
4
+ import { printTable } from "./search.mjs"
5
+
6
+ export async function list(args) {
7
+ console.log()
8
+ console.log(` ${bold("promptslide")} ${dim("list")}`)
9
+ console.log()
10
+
11
+ // Parse --type flag
12
+ let type = null
13
+ const typeIdx = args.indexOf("--type")
14
+ if (typeIdx !== -1 && args[typeIdx + 1]) {
15
+ type = args[typeIdx + 1]
16
+ const validTypes = ["slide", "layout", "deck", "theme"]
17
+ if (!validTypes.includes(type)) {
18
+ console.error(` ${red("Error:")} Invalid type "${type}". Use: ${validTypes.join(", ")}`)
19
+ process.exit(1)
20
+ }
21
+ }
22
+
23
+ const auth = requireAuth()
24
+
25
+ try {
26
+ const results = await searchRegistry({ type }, auth)
27
+ const label = type ? `${type}s` : "items"
28
+ console.log(` Registry ${label}${auth.organizationName ? ` for ${cyan(auth.organizationName)}` : ""}:`)
29
+ console.log()
30
+ printTable(Array.isArray(results) ? results : results.items || [])
31
+ } catch (err) {
32
+ console.error(` ${red("Error:")} ${err.message}`)
33
+ process.exit(1)
34
+ }
35
+
36
+ console.log()
37
+ }
@@ -0,0 +1,176 @@
1
+ import { execFileSync } from "node:child_process"
2
+
3
+ import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
4
+ import { saveAuth, DEFAULT_REGISTRY } from "../utils/auth.mjs"
5
+ import { prompt, closePrompts } from "../utils/prompts.mjs"
6
+ import { fetchOrganizations } from "../utils/registry.mjs"
7
+
8
+ function sleep(ms) {
9
+ return new Promise(resolve => setTimeout(resolve, ms))
10
+ }
11
+
12
+ function openBrowser(url) {
13
+ try {
14
+ const platform = process.platform
15
+ if (platform === "darwin") {
16
+ execFileSync("open", [url], { stdio: "ignore" })
17
+ } else if (platform === "win32") {
18
+ execFileSync("cmd", ["/c", "start", "", url], { stdio: "ignore" })
19
+ } else {
20
+ execFileSync("xdg-open", [url], { stdio: "ignore" })
21
+ }
22
+ } catch {}
23
+ }
24
+
25
+ export async function login(args) {
26
+ console.log()
27
+ console.log(` ${bold("promptslide")} ${dim("login")}`)
28
+ console.log()
29
+
30
+ // Parse --registry flag
31
+ let registry = DEFAULT_REGISTRY
32
+ const registryIdx = args.indexOf("--registry")
33
+ if (registryIdx !== -1 && args[registryIdx + 1]) {
34
+ registry = args[registryIdx + 1].replace(/\/$/, "")
35
+ }
36
+
37
+ console.log(` Registry: ${cyan(registry)}`)
38
+ console.log()
39
+
40
+ // Step 1: Request device code
41
+ let deviceData
42
+ try {
43
+ const res = await fetch(`${registry}/api/auth/device/code`, {
44
+ method: "POST",
45
+ headers: { "Content-Type": "application/json" },
46
+ body: JSON.stringify({
47
+ client_id: "promptslide-cli",
48
+ scope: "openid profile email"
49
+ })
50
+ })
51
+
52
+ if (!res.ok) {
53
+ const body = await res.text()
54
+ throw new Error(`${res.status}: ${body}`)
55
+ }
56
+
57
+ deviceData = await res.json()
58
+ } catch (err) {
59
+ console.error(` ${red("Error:")} Could not connect to registry.`)
60
+ console.error(` ${dim(err.message)}`)
61
+ process.exit(1)
62
+ }
63
+
64
+ const { device_code, user_code, verification_uri_complete, verification_uri, interval: initialInterval, expires_in } = deviceData
65
+ let pollInterval = (initialInterval || 5) * 1000
66
+ const timeoutMs = (expires_in || 600) * 1000 // Default 10 min
67
+
68
+ // Step 2: Show verification info
69
+ const verifyUrl = verification_uri_complete || verification_uri || `${registry}/device`
70
+ console.log(` ${green("✓")} Open this URL in your browser:`)
71
+ console.log(` ${cyan(verifyUrl)}`)
72
+ console.log()
73
+ if (user_code) {
74
+ console.log(` Enter code: ${bold(user_code)}`)
75
+ console.log()
76
+ }
77
+ console.log(` ${dim("Waiting for authorization...")}`)
78
+
79
+ openBrowser(verifyUrl)
80
+
81
+ // Step 3: Poll for authorization
82
+ let tokenData
83
+ const deadline = Date.now() + timeoutMs
84
+ while (Date.now() < deadline) {
85
+ await sleep(pollInterval)
86
+
87
+ try {
88
+ const res = await fetch(`${registry}/api/auth/device/token`, {
89
+ method: "POST",
90
+ headers: { "Content-Type": "application/json" },
91
+ body: JSON.stringify({
92
+ device_code,
93
+ client_id: "promptslide-cli",
94
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
95
+ })
96
+ })
97
+
98
+ const data = await res.json()
99
+
100
+ if (data.error === "authorization_pending") continue
101
+ if (data.error === "slow_down") {
102
+ pollInterval += 5000
103
+ continue
104
+ }
105
+ if (data.error) {
106
+ throw new Error(data.error_description || data.error)
107
+ }
108
+
109
+ tokenData = data
110
+ break
111
+ } catch (err) {
112
+ if (err.message === "authorization_pending") continue
113
+ console.error(`\n ${red("Error:")} ${err.message}`)
114
+ process.exit(1)
115
+ }
116
+ }
117
+
118
+ if (!tokenData) {
119
+ console.error(`\n ${red("Error:")} Authorization timed out. Please try again.`)
120
+ process.exit(1)
121
+ }
122
+
123
+ const accessToken = tokenData.access_token || tokenData.token
124
+ console.log(` ${green("✓")} Authenticated`)
125
+
126
+ // Step 4: Fetch user's organizations and let them pick
127
+ let organizationId = null
128
+ let organizationName = null
129
+
130
+ try {
131
+ const orgs = await fetchOrganizations({ registry, token: accessToken })
132
+
133
+ if (orgs.length === 0) {
134
+ console.log(` ${dim("No organizations found. Create one at your dashboard.")}`)
135
+ } else if (orgs.length === 1) {
136
+ organizationId = orgs[0].id
137
+ organizationName = orgs[0].name
138
+ } else {
139
+ console.log()
140
+ console.log(` ${bold("Select organization:")}`)
141
+ orgs.forEach((org, i) => {
142
+ console.log(` ${dim(`${i + 1}.`)} ${org.name} ${dim(`(${org.slug})`)}`)
143
+ })
144
+ console.log()
145
+
146
+ const choice = await prompt("Organization number:", "1")
147
+ const idx = parseInt(choice, 10) - 1
148
+ if (idx >= 0 && idx < orgs.length) {
149
+ organizationId = orgs[idx].id
150
+ organizationName = orgs[idx].name
151
+ } else {
152
+ console.error(` ${red("Error:")} Invalid selection. Using first organization.`)
153
+ organizationId = orgs[0].id
154
+ organizationName = orgs[0].name
155
+ }
156
+ }
157
+ } catch (err) {
158
+ console.log(` ${dim("Could not fetch organizations: " + err.message)}`)
159
+ }
160
+
161
+ // Step 5: Save credentials
162
+ saveAuth({
163
+ registry,
164
+ token: accessToken,
165
+ organizationId,
166
+ organizationName
167
+ })
168
+
169
+ if (organizationName) {
170
+ console.log(` Organization: ${bold(organizationName)}`)
171
+ }
172
+ console.log(` Credentials saved to ${dim("~/.promptslide/auth.json")}`)
173
+ console.log()
174
+
175
+ closePrompts()
176
+ }
@@ -0,0 +1,17 @@
1
+ import { bold, green, dim } from "../utils/ansi.mjs"
2
+ import { clearAuth } from "../utils/auth.mjs"
3
+
4
+ export async function logout() {
5
+ console.log()
6
+ console.log(` ${bold("promptslide")} ${dim("logout")}`)
7
+ console.log()
8
+
9
+ const removed = clearAuth()
10
+
11
+ if (removed) {
12
+ console.log(` ${green("✓")} Logged out. Credentials removed.`)
13
+ } else {
14
+ console.log(` ${dim("Not logged in.")}`)
15
+ }
16
+ console.log()
17
+ }
@@ -0,0 +1,68 @@
1
+ import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
2
+ import { requireAuth, saveAuth } from "../utils/auth.mjs"
3
+ import { fetchOrganizations } from "../utils/registry.mjs"
4
+ import { prompt, closePrompts } from "../utils/prompts.mjs"
5
+
6
+ export async function org(args) {
7
+ console.log()
8
+ console.log(` ${bold("promptslide")} ${dim("org")}`)
9
+ console.log()
10
+
11
+ const auth = requireAuth()
12
+
13
+ let orgs
14
+ try {
15
+ orgs = await fetchOrganizations(auth)
16
+ } catch (err) {
17
+ console.error(` ${red("Error:")} ${err.message}`)
18
+ process.exit(1)
19
+ }
20
+
21
+ if (orgs.length === 0) {
22
+ console.log(` No organizations found. Create one at your dashboard.`)
23
+ console.log()
24
+ closePrompts()
25
+ return
26
+ }
27
+
28
+ console.log(` ${bold("Your organizations:")}`)
29
+ orgs.forEach((o, i) => {
30
+ const current = o.id === auth.organizationId ? ` ${green("← current")}` : ""
31
+ console.log(` ${dim(`${i + 1}.`)} ${o.name} ${dim(`(${o.slug})`)}${current}`)
32
+ })
33
+ console.log()
34
+
35
+ if (orgs.length === 1) {
36
+ if (auth.organizationId !== orgs[0].id) {
37
+ saveAuth({ ...auth, organizationId: orgs[0].id, organizationName: orgs[0].name })
38
+ console.log(` ${green("✓")} Switched to ${bold(orgs[0].name)}`)
39
+ } else {
40
+ console.log(` Already using ${bold(orgs[0].name)}.`)
41
+ }
42
+ console.log()
43
+ closePrompts()
44
+ return
45
+ }
46
+
47
+ const choice = await prompt("Switch to organization number:", "")
48
+ if (!choice) {
49
+ console.log()
50
+ closePrompts()
51
+ return
52
+ }
53
+
54
+ const idx = parseInt(choice, 10) - 1
55
+ if (idx < 0 || idx >= orgs.length) {
56
+ console.error(` ${red("Error:")} Invalid selection.`)
57
+ console.log()
58
+ closePrompts()
59
+ process.exit(1)
60
+ }
61
+
62
+ const selected = orgs[idx]
63
+ saveAuth({ ...auth, organizationId: selected.id, organizationName: selected.name })
64
+ console.log(` ${green("✓")} Switched to ${bold(selected.name)}`)
65
+ console.log()
66
+
67
+ closePrompts()
68
+ }