Skip to content

Commit 111588e

Browse files
Sync packages from monorepo (8c5fa9b)
1 parent 9b4681d commit 111588e

8 files changed

Lines changed: 190 additions & 56 deletions

File tree

packages/cli/src/commands/game/create.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { downloadPage } from "../../download/page-downloader.js"
1010
import { runCommand, gitCommit } from "../../util/exec.js"
1111
import { runSkillsPipelineTUI, runAgentWithBuildLoop } from "../../skills/runner.js"
1212
import { login } from "../login.js"
13-
import { getToken } from "../../util/config.js"
13+
import { getDefaultToken } from "../../util/config.js"
1414
import { detectRepoContext, type RepoType } from "./detectRepo.js"
1515

1616
type Strategy = "import" | "blank" | "prompt"
@@ -62,7 +62,7 @@ const slugify = (text: string) =>
6262

6363
/** Writes .mcp.json with dev server config */
6464
const writeMcpConfig = (dir: string) => {
65-
const token = getToken()
65+
const token = getDefaultToken()
6666
const mcpConfig = {
6767
mcpServers: {
6868
"dev.puzzmo.com": {

packages/cli/src/commands/login.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import * as p from "@clack/prompts"
22

3-
import { writeConfig, readConfig } from "../util/config.js"
3+
import { defaultSource, addToken, decodeTokenPayload, normalizeSource } from "../util/config.js"
44

5-
/** Saves a CLI token to ~/.puzzmo/config.json */
6-
export const login = (token: string) => {
5+
/** Saves a CLI token to ~/.puzzmo/config.json under the given source server */
6+
export const login = (token: string, source: string = defaultSource) => {
77
if (!token.startsWith("pzt-")) {
88
p.log.error("Invalid CLI token. Generate one from dev.puzzmo.com.")
99
process.exit(1)
1010
}
1111

12-
const config = readConfig()
13-
config.token = token
14-
writeConfig(config)
12+
const payload = decodeTokenPayload(token)
13+
if (!payload?.teamID) {
14+
p.log.error("Could not decode the team from this token. Make sure you're using a token from dev.puzzmo.com.")
15+
process.exit(1)
16+
}
1517

16-
p.log.success("Logged in successfully. Token saved to ~/.puzzmo/config.json")
18+
const normalized = normalizeSource(source)
19+
addToken(normalized, token)
20+
p.log.success(`Logged in to ${normalized} for team ${payload.teamID}. Token saved to ~/.puzzmo/config.json`)
1721
}

packages/cli/src/commands/upload.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from "node:path"
55
import * as p from "@clack/prompts"
66

77
import { GameNotFoundError, uploadFiles } from "../util/api.js"
8-
import { getAPIURL, getToken } from "../util/config.js"
8+
import { type TokenEntry, findTokensForTeam, getTokens, resolveServerForTeam, sourceToURL } from "../util/config.js"
99
import { createUserGame } from "../util/createGame.js"
1010
import { discoverGames, type DiscoveredGame } from "../util/discoverGames.js"
1111

@@ -16,6 +16,7 @@ type UploadOptions = {
1616
type GameSuccess = {
1717
ok: true
1818
slug: string
19+
source: string
1920
fileCount: number
2021
totalBytes: number
2122
versionID: string
@@ -33,8 +34,8 @@ type GameResult = GameSuccess | GameFailure
3334
/** Uploads game build artifacts to Puzzmo by discovering puzzmo.json files in `dir` */
3435
export const upload = async (dir: string, options: UploadOptions = {}) => {
3536
const { verbose = false } = options
36-
const token = getToken()
37-
if (!token) {
37+
38+
if (getTokens().length === 0) {
3839
console.error("Not logged in. Run `puzzmo login <token>` or set PUZZMO_TOKEN.")
3940
process.exit(1)
4041
}
@@ -70,33 +71,47 @@ export const upload = async (dir: string, options: UploadOptions = {}) => {
7071
if (description) console.log(`\nMessage: ${description}`)
7172
if (repoURL) console.log(`Repo: ${repoURL}`)
7273

73-
const apiURL = getAPIURL()
74-
const defaultURL = "https://api.puzzmo.com"
75-
if (apiURL !== defaultURL) console.log(`API: ${apiURL}`)
76-
7774
const results: GameResult[] = []
7875
for (const err of discoveryErrors) {
7976
results.push({ ok: false, slug: err.slug ?? path.relative(rootDir, err.puzzmoJsonPath), error: err.errors.join("; ") })
8077
}
8178

8279
for (let i = 0; i < games.length; i++) {
8380
const game = games[i]
84-
console.log(`\n[${i + 1}/${games.length}] Uploading ${game.puzzmoFile.game.slug}`)
81+
const slug = game.puzzmoFile.game.slug
82+
console.log(`\n[${i + 1}/${games.length}] Uploading ${slug}`)
83+
84+
const teamID = game.puzzmoFile.game.teamID
85+
const credential = await resolveServerForTeam(teamID)
86+
if (!credential) {
87+
const matches = findTokensForTeam(teamID)
88+
const message =
89+
matches.length === 0
90+
? `No saved token for team ${teamID}. Run \`puzzmo login <token>\` (use \`--source\` if the token is for a non-default server).`
91+
: `Token for team ${teamID} is registered against ${matches.map((m) => m.source).join(", ")} but none of those servers are reachable.`
92+
console.error(` ${message}`)
93+
results.push({ ok: false, slug, error: message })
94+
continue
95+
}
96+
97+
const apiURL = sourceToURL(credential.source)
98+
console.log(` Server: ${credential.source}`)
99+
85100
try {
86101
let result: GameSuccess
87102
try {
88-
result = await uploadOneGame(game, { token, sha, description, repoURL, verbose, rootDir })
103+
result = await uploadOneGame(game, { credential, apiURL, sha, description, repoURL, verbose, rootDir })
89104
} catch (e) {
90105
if (!(e instanceof GameNotFoundError)) throw e
91-
const created = await maybeCreateMissingGame(e, game, { teamAccessToken: token })
106+
const created = await maybeCreateMissingGame(e, game, { apiURL, teamAccessToken: credential.token })
92107
if (!created) throw e
93-
result = await uploadOneGame(game, { token, sha, description, repoURL, verbose, rootDir })
108+
result = await uploadOneGame(game, { credential, apiURL, sha, description, repoURL, verbose, rootDir })
94109
}
95110
results.push(result)
96111
} catch (e) {
97112
const message = e instanceof Error ? e.message : String(e)
98113
console.error(` Failed: ${message}`)
99-
results.push({ ok: false, slug: game.puzzmoFile.game.slug, error: message })
114+
results.push({ ok: false, slug, error: message })
100115
}
101116
}
102117

@@ -107,7 +122,8 @@ export const upload = async (dir: string, options: UploadOptions = {}) => {
107122
}
108123

109124
type UploadOneOptions = {
110-
token: string
125+
credential: TokenEntry
126+
apiURL: string
111127
sha: string
112128
description: string | null
113129
repoURL: string | null
@@ -117,7 +133,7 @@ type UploadOneOptions = {
117133

118134
/** Uploads a single discovered game; throws on failure */
119135
const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Promise<GameSuccess> => {
120-
const { token, sha, description, repoURL, verbose, rootDir } = opts
136+
const { credential, apiURL, sha, description, repoURL, verbose, rootDir } = opts
121137
const { puzzmoFile, distDir } = game
122138
const gameSlug = puzzmoFile.game.slug
123139

@@ -130,7 +146,8 @@ const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Prom
130146
console.log(` ${files.length} file(s), ${formatBytes(totalBytes)} total (sha ${sha.slice(0, 8)})`)
131147

132148
const result = await uploadFiles(
133-
token,
149+
apiURL,
150+
credential.token,
134151
gameSlug,
135152
sha,
136153
files,
@@ -146,6 +163,7 @@ const uploadOneGame = async (game: DiscoveredGame, opts: UploadOneOptions): Prom
146163
return {
147164
ok: true,
148165
slug: gameSlug,
166+
source: credential.source,
149167
fileCount: files.length,
150168
totalBytes,
151169
versionID: result.versionID,
@@ -159,7 +177,7 @@ const printReport = (results: GameResult[]) => {
159177
const successes = results.filter((r): r is GameSuccess => r.ok)
160178
const failures = results.filter((r): r is GameFailure => !r.ok)
161179
for (const r of successes) {
162-
console.log(` OK ${r.slug.padEnd(24)} ${formatBytes(r.totalBytes).padStart(8)} ${r.versionID}`)
180+
console.log(` OK ${r.slug.padEnd(24)} ${formatBytes(r.totalBytes).padStart(8)} ${r.source.padEnd(20)} ${r.versionID}`)
163181
}
164182
for (const r of failures) {
165183
console.log(` FAIL ${r.slug.padEnd(24)} ${r.error}`)
@@ -241,7 +259,7 @@ const hashGames = (games: DiscoveredGame[]): string => {
241259
const maybeCreateMissingGame = async (
242260
err: GameNotFoundError,
243261
game: DiscoveredGame,
244-
opts: { teamAccessToken: string },
262+
opts: { apiURL: string; teamAccessToken: string },
245263
): Promise<boolean> => {
246264
const slug = err.slug
247265
const displayName = game.puzzmoFile.game.displayName
@@ -258,7 +276,7 @@ const maybeCreateMissingGame = async (
258276
if (p.isCancel(consent) || !consent) return false
259277

260278
try {
261-
const created = await createUserGame({ displayName, teamAccessToken: opts.teamAccessToken })
279+
const created = await createUserGame({ apiURL: opts.apiURL, displayName, teamAccessToken: opts.teamAccessToken })
262280
console.log(` Created game ${created.slug} (${created.id})`)
263281
if (created.slug !== slug) {
264282
console.warn(` Server picked slug "${created.slug}" but your puzzmo.json uses "${slug}". Update puzzmo.json before re-uploading.`)

packages/cli/src/index.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,23 +11,32 @@ const [command, ...args] = process.argv.slice(2)
1111

1212
const printUsage = () => {
1313
console.log(`Usage:
14-
puzzmo login <token> Save a CLI auth token
15-
puzzmo game create [token] Create a new Puzzmo game project
14+
puzzmo login <token> [--source <url>] Save a CLI auth token. You can have many stored tokens for different teams.
15+
--source defaults to https://api.puzzmo.com;
1616
17-
puzzmo upload [dir] [-v] Discover puzzmo.json files in [dir] (default: .) and upload each game's build. -v/--verbose prints request URLs and full error bodies. If a game does not exist on your account and stdin is a TTY, you'll be asked whether to create it (using your saved CLI token).
18-
puzzmo validate [dir] Discover and validate every puzzmo.json under [dir] (default: .)
19-
puzzmo migrate List and select migration skills from dev.puzzmo.com`)
17+
puzzmo game create [token] Create a new Puzzmo game project
18+
19+
puzzmo upload [dir] [-v] Discover puzzmo.json files in [dir] (default: .) and upload each game's build.
20+
puzzmo validate [dir] Discover and validate every puzzmo.json under [dir] (default: .)
21+
puzzmo migrate List and select migration skills from dev.puzzmo.com`)
2022
}
2123

2224
const run = async () => {
2325
switch (command) {
2426
case "login": {
25-
const token = args[0]
27+
const sourceIndex = args.findIndex((a) => a === "--source")
28+
const source = sourceIndex >= 0 ? args[sourceIndex + 1] : undefined
29+
if (sourceIndex >= 0 && !source) {
30+
console.error("Usage: puzzmo login <token> [--source <url>]")
31+
process.exit(1)
32+
}
33+
const sourceValueIndex = sourceIndex >= 0 ? sourceIndex + 1 : -1
34+
const token = args.find((a, i) => !a.startsWith("-") && i !== sourceValueIndex)
2635
if (!token) {
27-
console.error("Usage: puzzmo login <token>")
36+
console.error("Usage: puzzmo login <token> [--source <url>]")
2837
process.exit(1)
2938
}
30-
login(token)
39+
login(token, source)
3140
break
3241
}
3342
case "upload": {

packages/cli/src/util/api.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import fs from "node:fs"
22
import path from "node:path"
33

4-
import { getAPIURL } from "./config.js"
5-
64
/** The schema for a puzzmo.json file - mirrors PuzzmoFile from @puzzmo-com/shared/hostAPI */
75
export type PuzzmoFile = {
86
game: {
@@ -132,6 +130,7 @@ const formatBytes = (bytes: number): string => {
132130

133131
/** Multi-step upload: init -> batched file uploads -> complete */
134132
export const uploadFiles = async (
133+
apiURL: string,
135134
token: string,
136135
gameSlug: string,
137136
sha: string,
@@ -142,7 +141,6 @@ export const uploadFiles = async (
142141
options: UploadFilesOptions = {},
143142
): Promise<CompleteResponse> => {
144143
const { verbose = false, description, repoURL } = options
145-
const apiURL = getAPIURL()
146144
if (verbose) console.log(` API URL: ${apiURL}`)
147145

148146
// Step 1: Init session (includes puzzmo.json metadata)

0 commit comments

Comments
 (0)