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.
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/commands/add.mjs +182 -0
- package/src/commands/build.mjs +1 -1
- package/src/commands/create.mjs +43 -27
- package/src/commands/info.mjs +79 -0
- package/src/commands/list.mjs +37 -0
- package/src/commands/login.mjs +176 -0
- package/src/commands/logout.mjs +17 -0
- package/src/commands/org.mjs +68 -0
- package/src/commands/publish.mjs +384 -0
- package/src/commands/remove.mjs +111 -0
- package/src/commands/search.mjs +57 -0
- package/src/commands/to-image.mjs +52 -0
- package/src/commands/update.mjs +218 -0
- package/src/core/slide-deck.tsx +50 -0
- package/src/index.mjs +70 -0
- package/src/utils/ansi.mjs +1 -0
- package/src/utils/auth.mjs +66 -0
- package/src/utils/colors.mjs +0 -7
- package/src/utils/deck-config.mjs +208 -0
- package/src/utils/export.mjs +115 -0
- package/src/utils/registry.mjs +319 -0
- package/src/vite/config.mjs +1 -1
- package/src/vite/plugin.mjs +92 -11
- package/templates/default/AGENTS.md +42 -408
- package/templates/default/src/globals.css +5 -43
- package/templates/default/src/slides/slide-title.tsx +0 -5
package/src/commands/create.mjs
CHANGED
|
@@ -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,
|
|
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 =
|
|
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 =
|
|
102
|
-
if (!
|
|
103
|
-
|
|
104
|
-
primaryHex
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
+
}
|