@puzzmo/cli 1.0.32 → 1.0.34

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.
Files changed (58) hide show
  1. package/lib/agents/claude.d.ts +5 -0
  2. package/lib/agents/claude.js +69 -0
  3. package/lib/agents/claude.js.map +1 -0
  4. package/lib/agents/codex.d.ts +4 -0
  5. package/lib/agents/codex.js +90 -0
  6. package/lib/agents/codex.js.map +1 -0
  7. package/lib/agents/copilot.d.ts +4 -0
  8. package/lib/agents/copilot.js +123 -0
  9. package/lib/agents/copilot.js.map +1 -0
  10. package/lib/agents/gemini.d.ts +5 -0
  11. package/lib/agents/gemini.js +69 -0
  12. package/lib/agents/gemini.js.map +1 -0
  13. package/lib/agents/index.d.ts +5 -0
  14. package/lib/agents/index.js +15 -0
  15. package/lib/agents/index.js.map +1 -0
  16. package/lib/agents/opencode.d.ts +5 -0
  17. package/lib/agents/opencode.js +159 -0
  18. package/lib/agents/opencode.js.map +1 -0
  19. package/lib/agents/stream-json-cli.d.ts +12 -0
  20. package/lib/agents/stream-json-cli.js +75 -0
  21. package/lib/agents/stream-json-cli.js.map +1 -0
  22. package/lib/agents/types.d.ts +37 -0
  23. package/lib/agents/types.js +2 -0
  24. package/lib/agents/types.js.map +1 -0
  25. package/lib/commands/agent-test.d.ts +3 -0
  26. package/lib/commands/agent-test.js +79 -0
  27. package/lib/commands/agent-test.js.map +1 -0
  28. package/lib/commands/game/create.js +6 -4
  29. package/lib/commands/game/create.js.map +1 -1
  30. package/lib/commands/upload.d.ts +5 -1
  31. package/lib/commands/upload.js +41 -40
  32. package/lib/commands/upload.js.map +1 -1
  33. package/lib/index.js +10 -4
  34. package/lib/index.js.map +1 -1
  35. package/lib/tui/pipeline.d.ts +1 -2
  36. package/lib/tui/pipeline.js +160 -267
  37. package/lib/tui/pipeline.js.map +1 -1
  38. package/lib/util/api.d.ts +5 -1
  39. package/lib/util/api.js +29 -16
  40. package/lib/util/api.js.map +1 -1
  41. package/lib/util/validatePuzzmoFile.js +31 -6
  42. package/lib/util/validatePuzzmoFile.js.map +1 -1
  43. package/package.json +4 -1
  44. package/src/agents/claude.ts +72 -0
  45. package/src/agents/codex.ts +82 -0
  46. package/src/agents/copilot.ts +118 -0
  47. package/src/agents/gemini.ts +74 -0
  48. package/src/agents/index.ts +20 -0
  49. package/src/agents/opencode.ts +154 -0
  50. package/src/agents/stream-json-cli.ts +86 -0
  51. package/src/agents/types.ts +22 -0
  52. package/src/commands/agent-test.ts +81 -0
  53. package/src/commands/game/create.ts +6 -4
  54. package/src/commands/upload.ts +53 -39
  55. package/src/index.ts +10 -4
  56. package/src/tui/pipeline.ts +151 -268
  57. package/src/util/api.ts +29 -13
  58. package/src/util/validatePuzzmoFile.ts +30 -7
package/src/util/api.ts CHANGED
@@ -22,39 +22,49 @@ type CompleteResponse = { assetsBase: string; versionID: string; error?: string
22
22
  /** Callback for reporting batch upload progress */
23
23
  export type UploadProgress = (batch: number, totalBatches: number, uploaded: number) => void
24
24
 
25
+ /** Options for uploadFiles */
26
+ export type UploadFilesOptions = {
27
+ verbose?: boolean
28
+ }
29
+
25
30
  /** Wraps fetch to surface the underlying network cause (DNS, ECONNREFUSED, TLS, etc.) */
26
- const fetchWithContext = async (url: string, init: RequestInit, step: string): Promise<Response> => {
31
+ const fetchWithContext = async (url: string, init: RequestInit, step: string, verbose: boolean): Promise<Response> => {
32
+ if (verbose) console.log(` → ${init.method ?? "GET"} ${url} [${step}]`)
27
33
  try {
28
- return await fetch(url, init)
34
+ const res = await fetch(url, init)
35
+ if (verbose) console.log(` ← ${res.status} ${res.statusText} [${step}]`)
36
+ return res
29
37
  } catch (e) {
30
38
  const cause = (e as { cause?: unknown }).cause
31
39
  const causeMsg = cause instanceof Error ? cause.message : cause ? String(cause) : (e as Error).message
40
+ if (verbose && e instanceof Error && e.stack) console.error(e.stack)
32
41
  throw new Error(`Network error during ${step} (${url}): ${causeMsg}`)
33
42
  }
34
43
  }
35
44
 
36
45
  /** Reads a response body, parsing JSON when possible and including status + body in errors */
37
- const readResponse = async (res: Response, url: string, step: string) => {
46
+ const readResponse = async (res: Response, url: string, step: string, verbose: boolean) => {
38
47
  const text = await res.text()
39
48
  let json: unknown
40
49
  try {
41
50
  json = text ? JSON.parse(text) : {}
42
51
  } catch {
43
52
  if (!res.ok) {
44
- const snippet = text.slice(0, 200).replace(/\s+/g, " ").trim()
53
+ const snippet = verbose ? text : text.slice(0, 200).replace(/\s+/g, " ").trim()
45
54
  throw new Error(`Server error during ${step} (${res.status} ${res.statusText} from ${url}): ${snippet || "no body"}`)
46
55
  }
47
- throw new Error(`Invalid JSON response during ${step} (${url}): ${text.slice(0, 200)}`)
56
+ throw new Error(`Invalid JSON response during ${step} (${url}): ${verbose ? text : text.slice(0, 200)}`)
48
57
  }
49
58
  if (!res.ok) {
50
59
  const serverMsg = (json as { error?: string }).error || res.statusText || "Unknown error"
51
- throw new Error(`Server error during ${step} (${res.status} from ${url}): ${serverMsg}`)
60
+ const detail = verbose ? `\n${JSON.stringify(json, null, 2)}` : ""
61
+ throw new Error(`Server error during ${step} (${res.status} from ${url}): ${serverMsg}${detail}`)
52
62
  }
53
63
  return json
54
64
  }
55
65
 
56
66
  /** Sends a JSON POST request */
57
- const jsonPost = async (url: string, token: string, body: object, step: string) => {
67
+ const jsonPost = async (url: string, token: string, body: object, step: string, verbose: boolean) => {
58
68
  const res = await fetchWithContext(
59
69
  url,
60
70
  {
@@ -63,12 +73,13 @@ const jsonPost = async (url: string, token: string, body: object, step: string)
63
73
  body: JSON.stringify(body),
64
74
  },
65
75
  step,
76
+ verbose,
66
77
  )
67
- return readResponse(res, url, step)
78
+ return readResponse(res, url, step, verbose)
68
79
  }
69
80
 
70
81
  /** Uploads a single file as raw binary with filename in query param */
71
- const uploadFile = async (url: string, token: string, filePath: string, baseDir: string): Promise<FileResponse> => {
82
+ const uploadFile = async (url: string, token: string, filePath: string, baseDir: string, verbose: boolean): Promise<FileResponse> => {
72
83
  const relativePath = path.relative(baseDir, filePath)
73
84
  const content = fs.readFileSync(filePath)
74
85
  const step = `file upload (${relativePath})`
@@ -85,8 +96,9 @@ const uploadFile = async (url: string, token: string, filePath: string, baseDir:
85
96
  body: content,
86
97
  },
87
98
  step,
99
+ verbose,
88
100
  )
89
- return (await readResponse(res, fullURL, step)) as FileResponse
101
+ return (await readResponse(res, fullURL, step, verbose)) as FileResponse
90
102
  }
91
103
 
92
104
  /** Multi-step upload: init -> batched file uploads -> complete */
@@ -98,11 +110,15 @@ export const uploadFiles = async (
98
110
  baseDir: string,
99
111
  puzzmoFile: PuzzmoFile,
100
112
  onProgress?: UploadProgress,
113
+ options: UploadFilesOptions = {},
101
114
  ): Promise<CompleteResponse> => {
115
+ const { verbose = false } = options
102
116
  const apiURL = getAPIURL()
117
+ if (verbose) console.log(` API URL: ${apiURL}`)
103
118
 
104
119
  // Step 1: Init session (includes puzzmo.json metadata)
105
- const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha, puzzmoFile }, "upload init")) as InitResponse
120
+ const init = (await jsonPost(`${apiURL}/cliUpload`, token, { gameSlug, sha, puzzmoFile }, "upload init", verbose)) as InitResponse
121
+ if (verbose) console.log(` session: ${init.sessionID}`)
106
122
 
107
123
  // Step 2: Upload files in concurrent batches
108
124
  const fileURL = `${apiURL}/cliUpload/${init.sessionID}/file`
@@ -112,11 +128,11 @@ export const uploadFiles = async (
112
128
  for (let i = 0; i < filePaths.length; i += BATCH_SIZE) {
113
129
  const batch = filePaths.slice(i, i + BATCH_SIZE)
114
130
  const batchNum = Math.floor(i / BATCH_SIZE) + 1
115
- await Promise.all(batch.map((fp) => uploadFile(fileURL, token, fp, baseDir)))
131
+ await Promise.all(batch.map((fp) => uploadFile(fileURL, token, fp, baseDir, verbose)))
116
132
  totalUploaded += batch.length
117
133
  onProgress?.(batchNum, totalBatches, totalUploaded)
118
134
  }
119
135
 
120
136
  // Step 3: Complete
121
- return (await jsonPost(`${apiURL}/cliUpload/${init.sessionID}/complete`, token, {}, "upload complete")) as CompleteResponse
137
+ return (await jsonPost(`${apiURL}/cliUpload/${init.sessionID}/complete`, token, {}, "upload complete", verbose)) as CompleteResponse
122
138
  }
@@ -1,18 +1,32 @@
1
- import { Validator } from "@cfworker/json-schema"
1
+ import { Validator, type Schema } from "@cfworker/json-schema"
2
2
 
3
3
  import type { PuzzmoFile } from "./api.js"
4
4
 
5
- const schemaURL = "https://dev.puzzmo.com/schema/puzzmo-file-schema.json"
5
+ const schemaURL = "https://dev-dj9e.onrender.com/schema/puzzmo-file-schema.json" //"https://dev.puzzmo.com/schema/puzzmo-file-schema.json"
6
6
 
7
7
  let cachedValidator: Validator | undefined
8
8
 
9
9
  /** Fetches and caches a JSON schema validator for puzzmo.json files */
10
10
  const getValidator = async () => {
11
11
  if (cachedValidator) return cachedValidator
12
- const res = await fetch(schemaURL)
13
- if (!res.ok) throw new Error(`Failed to fetch puzzmo file schema: ${res.status}`)
14
- const puzzmoSchema = await res.json()
15
- cachedValidator = new Validator(puzzmoSchema, "7")
12
+ let res: Response
13
+ try {
14
+ res = await fetch(schemaURL)
15
+ } catch (e) {
16
+ const cause = (e as { cause?: unknown }).cause
17
+ const causeMsg = cause instanceof Error ? cause.message : cause ? String(cause) : (e as Error).message
18
+ throw new Error(`Network error fetching puzzmo file schema (${schemaURL}): ${causeMsg}`)
19
+ }
20
+ if (!res.ok) {
21
+ throw new Error(`Failed to fetch puzzmo file schema: ${res.status} ${res.statusText} from ${schemaURL}`)
22
+ }
23
+ let puzzmoSchema: unknown
24
+ try {
25
+ puzzmoSchema = await res.json()
26
+ } catch (e) {
27
+ throw new Error(`Invalid JSON in puzzmo file schema (${schemaURL}): ${e instanceof Error ? e.message : e}`)
28
+ }
29
+ cachedValidator = new Validator(puzzmoSchema as Schema, "7")
16
30
  return cachedValidator
17
31
  }
18
32
 
@@ -21,7 +35,8 @@ type ValidationResult = { valid: true; data: PuzzmoFile } | { valid: false; erro
21
35
  /** Validates a parsed object against the puzzmo.json JSON schema */
22
36
  export const validatePuzzmoJson = async (data: unknown): Promise<ValidationResult> => {
23
37
  const validator = await getValidator()
24
- const result = validator.validate(data)
38
+ const toValidate = stripSchemaProperty(data)
39
+ const result = validator.validate(toValidate)
25
40
  if (result.valid) return { valid: true, data: data as PuzzmoFile }
26
41
  return {
27
42
  valid: false,
@@ -30,3 +45,11 @@ export const validatePuzzmoJson = async (data: unknown): Promise<ValidationResul
30
45
  .map((e) => `${e.instanceLocation}: ${e.error}`),
31
46
  }
32
47
  }
48
+
49
+ /** Removes the `$schema` editor-hint property so it doesn't trip additionalProperties checks */
50
+ const stripSchemaProperty = (data: unknown): unknown => {
51
+ if (!data || typeof data !== "object" || Array.isArray(data)) return data
52
+ if (!("$schema" in data)) return data
53
+ const { $schema: _, ...rest } = data as Record<string, unknown>
54
+ return rest
55
+ }