lecodes-cli 0.20.2 → 1.0.0

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 (105) hide show
  1. package/README.md +56 -57
  2. package/dist/index.js +5918 -5020
  3. package/package.json +7 -5
  4. package/runtime/materials/decal-relief.mat +12 -1
  5. package/runtime/materials/decal.mat +17 -3
  6. package/runtime/materials/lightmap-baked-lite.mat +6 -1
  7. package/runtime/materials/lightmap-baked.mat +6 -1
  8. package/runtime/materials/lightmap.mat +3 -0
  9. package/runtime/scene-harness.json +1 -1
  10. package/runtime/sdk-types.json +1 -1
  11. package/runtime/web/assets/createViewerLite-DHqTFhWB.js +866 -0
  12. package/runtime/web/assets/{index-BMt7AnC5.js → index-Q06c6oHx.js} +1 -1
  13. package/runtime/web/embed.js +1 -1
  14. package/src/api.ts +1 -302
  15. package/src/cli/args.ts +37 -0
  16. package/src/cli/command.ts +113 -0
  17. package/src/cli/errors.ts +13 -0
  18. package/src/cli/help.ts +133 -0
  19. package/src/cli/index.ts +7 -0
  20. package/src/cli/output.ts +102 -0
  21. package/src/cli/run.ts +106 -0
  22. package/src/commands/{appAndroid.ts → app/android.ts} +451 -42
  23. package/src/commands/{appDesktop.ts → app/desktop.ts} +63 -32
  24. package/src/commands/{appDesktopMac.ts → app/desktopMac.ts} +24 -18
  25. package/src/commands/{appIcon.ts → app/icon.ts} +2 -2
  26. package/src/commands/{app.ts → app/index.ts} +397 -289
  27. package/src/commands/{appShared.ts → app/shared.ts} +30 -39
  28. package/src/commands/{appTemplatesAndroid.ts → app/templates/android.ts} +62 -28
  29. package/src/commands/{appTemplatesGradlew.ts → app/templates/gradlew.ts} +1 -1
  30. package/src/commands/{appTemplates.ts → app/templates/ios.ts} +1 -1
  31. package/src/commands/assets.ts +23 -21
  32. package/src/commands/clone.ts +48 -51
  33. package/src/commands/compile.ts +62 -88
  34. package/src/commands/create.ts +46 -47
  35. package/src/commands/design/comments.ts +361 -0
  36. package/src/commands/design/context.ts +101 -0
  37. package/src/commands/design/index.ts +391 -0
  38. package/src/commands/design/snapshot.ts +134 -0
  39. package/src/commands/{designTemplates.ts → design/templates.ts} +4 -3
  40. package/src/commands/desktop.ts +90 -82
  41. package/src/commands/dev.ts +201 -176
  42. package/src/commands/diff.ts +39 -51
  43. package/src/commands/index.ts +60 -0
  44. package/src/commands/{init.ts → init/index.ts} +199 -191
  45. package/src/commands/install.ts +80 -67
  46. package/src/commands/lightmap.ts +203 -171
  47. package/src/commands/link.ts +87 -94
  48. package/src/commands/login.ts +25 -23
  49. package/src/commands/navmesh.ts +168 -138
  50. package/src/commands/pn.ts +201 -244
  51. package/src/commands/pull.ts +49 -56
  52. package/src/commands/push.ts +48 -54
  53. package/src/commands/render.ts +158 -99
  54. package/src/commands/scene.ts +58 -55
  55. package/src/commands/{shaders.ts → shaders/index.ts} +61 -42
  56. package/src/commands/{shadersNew.ts → shaders/new.ts} +2 -2
  57. package/src/commands/shared.ts +79 -0
  58. package/src/commands/status.ts +22 -26
  59. package/src/commands/test.ts +398 -371
  60. package/src/commands/thumbs.ts +185 -175
  61. package/src/commands/update/index.ts +226 -0
  62. package/src/commands/{types.ts → update/types.ts} +31 -22
  63. package/src/compile/collect.ts +5 -5
  64. package/src/compile/collectLocal.ts +3 -3
  65. package/src/compile/designCompile.ts +3 -3
  66. package/src/compile/headlessBundle.ts +133 -129
  67. package/src/compile/projectCompile.ts +2 -2
  68. package/src/compile/sceneCompile.ts +10 -8
  69. package/src/compile/screenEntry.ts +123 -127
  70. package/src/compile/shaders.ts +3 -3
  71. package/src/dev/androidDev.ts +1 -1
  72. package/src/dev/devServer.ts +3 -3
  73. package/src/dev/webRunner.ts +1 -1
  74. package/src/{cmgenTool.ts → hosts/cmgenTool.ts} +1 -1
  75. package/src/{desktopRenderer.ts → hosts/desktopRenderer.ts} +6 -5
  76. package/src/{desktopScript.ts → hosts/desktopScript.ts} +1 -1
  77. package/src/{distRoot.ts → hosts/distRoot.ts} +9 -5
  78. package/src/{matcTool.ts → hosts/matcTool.ts} +1 -1
  79. package/src/{peerInstall.ts → hosts/peerInstall.ts} +2 -2
  80. package/src/{releases.ts → hosts/releases.ts} +1 -1
  81. package/src/index.ts +32 -480
  82. package/src/platform/api.ts +302 -0
  83. package/src/{browserAuth.ts → platform/browserAuth.ts} +1 -1
  84. package/src/{config.ts → platform/config.ts} +1 -1
  85. package/src/{serverDiff.ts → platform/serverDiff.ts} +3 -3
  86. package/src/{projectEnv.ts → project/env.ts} +33 -2
  87. package/src/{localFiles.ts → project/localFiles.ts} +1 -1
  88. package/src/{manifest.ts → project/manifest.ts} +1 -1
  89. package/src/{project.ts → project/materialize.ts} +2 -2
  90. package/src/project/paths.ts +20 -0
  91. package/src/{textDiff.ts → project/textDiff.ts} +1 -1
  92. package/src/{types.ts → project/types.ts} +0 -0
  93. package/runtime/web/assets/createViewerLite-Ct_PZdof.js +0 -852
  94. package/src/commands/design.ts +0 -846
  95. package/src/commands/update.ts +0 -211
  96. package/src/util.ts +0 -146
  97. /package/src/commands/{projectTemplates.ts → init/templates.ts} +0 -0
  98. /package/src/{lecodes-3d-editor.d.ts → declarations/lecodes-3d-editor.d.ts} +0 -0
  99. /package/src/{lecodes-assets.d.ts → declarations/lecodes-assets.d.ts} +0 -0
  100. /package/src/{lecodes-design.d.ts → declarations/lecodes-design.d.ts} +0 -0
  101. /package/src/{lecodes-renderer.d.ts → declarations/lecodes-renderer.d.ts} +0 -0
  102. /package/src/{qrcode-terminal.d.ts → declarations/qrcode-terminal.d.ts} +0 -0
  103. /package/src/{peers.ts → hosts/peers.ts} +0 -0
  104. /package/src/{designMeta.ts → project/designMeta.ts} +0 -0
  105. /package/src/{ignore.ts → project/ignore.ts} +0 -0
@@ -1,23 +1,22 @@
1
1
  import { writeFileSync } from "node:fs"
2
2
  import { basename, resolve } from "node:path"
3
- import { getProject } from "../api"
4
- import { loadConfig, requireApiUrl, requireToken } from "../config"
5
- import { findProjectRoot, readManifest, type Manifest } from "../manifest"
3
+ import { getProject } from "../platform/api"
4
+ import { findProjectRoot, readManifest, type Manifest } from "../project/manifest"
6
5
  import { collectEntries } from "../compile/collect"
7
6
  import { collectLocalEntries } from "../compile/collectLocal"
8
7
  import { ambiguousEntryWarning, compileProject, type CompileEntry } from "../compile/projectCompile"
9
8
  import { applyProjectFonts } from "../compile/fonts"
10
9
  import { applyLocalShaders, desktopShaderPlatform, type StagedShader } from "../compile/shaders"
11
- import type { DesktopRenderer } from "../desktopRenderer"
10
+ import type { DesktopRenderer } from "../hosts/desktopRenderer"
12
11
  import { applyAppAssetIcons } from "../compile/assetIcons"
13
- import { buildGlobMatcher } from "../ignore"
14
- import { readAppConfigAt } from "./appShared"
15
- import { flagBool, flagStr, isNullSink, note, warnErr, type Args } from "../util"
12
+ import { buildGlobMatcher } from "../project/ignore"
13
+ import { readAppConfigAt } from "./app/shared"
14
+ import { defineCommand, isNullSink, note, warnErr } from "../cli"
15
+ import { bundleFlags, bundleOptions, outFlag, session, type BundleOptions } from "./shared"
16
16
 
17
17
  /*
18
- * `lecodes compile` — compile the local project into one JS bundle (same pipeline as the server)
19
- * and print it to stdout (pipeable), or write it with -o/--out. Diagnostics go to stderr.
20
- * `lecodes preview` is the legacy alias for this command (see `previewLegacy` below).
18
+ * `lecodes compile` — the local project as one JS bundle (the server's pipeline), printed to stdout
19
+ * or written with -o. Diagnostics go to stderr, so `lecodes compile > app.js` stays clean.
21
20
  *
22
21
  * Two modes, picked automatically:
23
22
  * - Server-backed project (a cloned/created one with .lecodes/manifest.json): code/.svg/scene
@@ -34,14 +33,14 @@ import { flagBool, flagStr, isNullSink, note, warnErr, type Args } from "../util
34
33
  * `lecodes render` has. `--remote-assets` (or an explicit `--public-url`) opts back into server
35
34
  * URLs; app.json `resources.remote` matches stay URLs either way (they stream in the built app too).
36
35
  *
37
- * A third mode exists for `lecodes app sync` only (the `shell` parameter): compile for a native
38
- * shell that ships its assets inside the app. Resources resolve locally (`id:` + fetchLocal) in
39
- * BOTH project modes, fonts vendor into assets/fonts/ (never CDN — text must render offline,
40
- * before any network), and resources matching `shell.remotePatterns` keep their server URL for
41
- * streaming. Returns every resource entry so the caller can copy the bundled ones into the shell.
36
+ * A third mode exists for `lecodes app sync` only (`compileShell`): compile for a native shell that
37
+ * ships its assets inside the app. Resources resolve locally (`id:` + fetchLocal) in BOTH project
38
+ * modes, fonts vendor into assets/fonts/ (never CDN — text must render offline, before any
39
+ * network), and resources matching `shell.remotePatterns` keep their server URL for streaming.
40
+ * Returns every resource entry so the caller can copy the bundled ones into the shell.
42
41
  */
43
42
 
44
- /** One project resource as the shell compile resolved it (returned to `lecodes app sync`). */
43
+ /** One project resource as the compile resolved it (returned to `lecodes app sync`). */
45
44
  export type BundleResource = {
46
45
  /** Root-relative project path. */
47
46
  path: string
@@ -60,14 +59,6 @@ export type BundleResource = {
60
59
  /** Shell-compile options (see the mode note above) — only `lecodes app sync` passes this. */
61
60
  export type ShellCompile = { remotePatterns: string[] }
62
61
 
63
- /** The `lecodes compile` command. */
64
- export const compile = async (args: Args): Promise<void> => { await compileImpl(args) }
65
-
66
- /** `lecodes app sync`'s entry: shell mode (see above), returning the resource inventory. */
67
- export const compileShell = (args: Args, shell: ShellCompile): Promise<BundleResource[]> => compileImpl(args, shell)
68
-
69
- /** The compiled project without any output handling — `lecodes render --desktop` runs this bundle in
70
- * the native desktop host, copying the local-asset resources next to it (fetchLocal by basename). */
71
62
  export type BuiltBundle = { js: string, resources: BundleResource[] }
72
63
 
73
64
  /** Extra knobs for the desktop-host callers (see the mode note above). */
@@ -81,32 +72,46 @@ export type BuildBundleOptions = {
81
72
  /** The desktop renderer this bundle is being built FOR, when the caller knows (it stages or spawns
82
73
  * the very exe that will run it). Narrows the shader compile + staging to the one artifact that
83
74
  * host asks for, instead of shipping every native variant. Leave unset when the target is unknown
84
- * — a generic `preview --local` bundle can be carried anywhere. */
75
+ * — a generic bundle can be carried anywhere. */
85
76
  desktopRenderer?: DesktopRenderer
86
77
  /** Synthetic in-memory entries added to the project's own (a virtual entry module, say — see
87
78
  * `lecodes lightmap bake`). Paths use the entry convention ("/.name.ts" at the root). */
88
79
  extraEntries?: CompileEntry[]
89
- /** Compile from this entry instead of the detected one / `--entry` (pairs with `extraEntries`). */
80
+ /** Compile from this entry instead of the detected one / `entry` (pairs with `extraEntries`). */
90
81
  entryOverride?: string
82
+ /** Shell mode (`lecodes app sync`) — see above. */
83
+ shell?: ShellCompile
91
84
  }
92
85
 
93
- const compileImpl = async (args: Args, shell?: ShellCompile): Promise<BundleResource[]> => {
94
- const { js, resources } = await buildBundle(args, shell)
86
+ export default defineCommand({
87
+ name: "compile",
88
+ summary: "Compile the local project to one JS bundle (stdout, or -o <file>)",
89
+ description: "Works on a cloned / created project (binary asset URLs resolved from the server) or a local `init` project (no login — asset() resources resolve on the host via fetchLocal, so the bundle is self-contained for LeCodes Desktop). Diagnostics go to stderr; the bundle is the only thing on stdout.",
90
+ aliases: ["preview"],
91
+ flags: { ...outFlag, ...bundleFlags },
92
+ examples: ["lecodes compile -o app.js", "lecodes compile --no-minify > app.js"],
93
+ run: async ({ flags }) => {
94
+ const { js } = await buildBundle(bundleOptions(flags))
95
+ writeBundle(js, flags.out)
96
+ },
97
+ })
98
+
99
+ /** Print or write a bundle (`-o /dev/null` discards it). */
100
+ export const writeBundle = (js: string, out: string | undefined): void => {
101
+ if (out && isNullSink(out)) note(`Discarded output (${out})`)
102
+ else if (out) { writeFileSync(resolve(out), js); note(`Wrote ${out} (${js.length} bytes)`) }
103
+ else { process.stdout.write(js); if (!js.endsWith("\n")) process.stdout.write("\n") }
104
+ }
95
105
 
96
- const out = flagStr(args, "o", "out")
97
- if (out && isNullSink(out)) {
98
- note(`Discarded output (${out})`)
99
- } else if (out) {
100
- writeFileSync(resolve(out), js)
101
- note(`Wrote ${out} (${js.length} bytes)`)
102
- } else {
103
- process.stdout.write(js)
104
- if (!js.endsWith("\n")) process.stdout.write("\n")
105
- }
106
+ /** `lecodes app sync`'s entry: shell mode (see above), the bundle written to `out`, the resource inventory returned. */
107
+ export const compileShell = async (out: string, shell: ShellCompile): Promise<BundleResource[]> => {
108
+ const { js, resources } = await buildBundle({}, { shell })
109
+ writeBundle(js, out)
106
110
  return resources
107
111
  }
108
112
 
109
- export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildBundleOptions = {}): Promise<BuiltBundle> => {
113
+ export const buildBundle = async (bundle: BundleOptions, opts: BuildBundleOptions = {}): Promise<BuiltBundle> => {
114
+ const shell = opts.shell
110
115
  let root: string
111
116
  let manifest: Manifest | null
112
117
  try {
@@ -124,13 +129,9 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
124
129
  let localAssets = false
125
130
 
126
131
  if (manifest) {
127
- const config = loadConfig()
128
- const apiUrl = requireApiUrl(config)
129
- const token = requireToken(config)
130
-
131
- const publicUrlFlag = flagStr(args, "public-url")
132
+ const { apiUrl, token } = session()
132
133
  // Shell compiles always bundle; a desktop-host run reads the tree unless told to use the server.
133
- localAssets = !!shell || (!!opts.localAssets && publicUrlFlag === undefined && !flagBool(args, "remote-assets"))
134
+ localAssets = !!shell || (!!opts.localAssets && bundle.publicUrl === undefined && !bundle.remoteAssets)
134
135
 
135
136
  note("Resolving project assets…")
136
137
  const project = await getProject(apiUrl, token, manifest.uuid)
@@ -138,7 +139,7 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
138
139
  // there (an unpushed resources.remote match still warns below).
139
140
  ;({ entries, warnings } = collectEntries(root, manifest, project, { warnMissingUrl: !localAssets }))
140
141
  name = manifest.name
141
- publicUrl = publicUrlFlag ?? apiUrl
142
+ publicUrl = bundle.publicUrl ?? apiUrl
142
143
 
143
144
  // Local-asset compile: everything resolves from the tree except the resources.remote matches,
144
145
  // which keep their server URL — and can't without one, so an unpushed match falls back to local.
@@ -155,20 +156,15 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
155
156
  // Local project (no server): compile from the tree. Without an explicit --public-url the assets
156
157
  // resolve locally (host fetchLocal) so the bundle is self-contained for LeCodes Desktop.
157
158
  // fileSrc stays a path (assetBase "") — compileProject prefixes publicUrl exactly once, same as
158
- // the server/dev conventions. (Passing publicUrl as assetBase too used to double the prefix.)
159
- const publicUrlFlag = flagStr(args, "public-url")
160
- localAssets = shell ? true : publicUrlFlag === undefined
161
- publicUrl = publicUrlFlag ?? ""
162
- if (opts.localAssets && flagBool(args, "remote-assets")) {
163
- warnErr("This is a local project (no server) — assets always load from the working tree.")
164
- }
159
+ // the server/dev conventions.
160
+ localAssets = shell ? true : bundle.publicUrl === undefined
161
+ publicUrl = bundle.publicUrl ?? ""
162
+ if (opts.localAssets && bundle.remoteAssets) warnErr("This is a local project (no server) — assets always load from the working tree.")
165
163
  // localShaders: a local-asset compile compiles the .mat files itself below, so the
166
164
  // "push the project once" degradation notice would be wrong.
167
165
  ;({ entries, warnings } = collectLocalEntries(root, null, "", { localShaders: localAssets && (opts.localShaders ?? !shell) }))
168
166
  name = basename(root)
169
- if (shell && shell.remotePatterns.length > 0) {
170
- warnErr("resources.remote needs a server-backed project (a streamed asset needs a server URL) — bundling everything.")
171
- }
167
+ if (shell && shell.remotePatterns.length > 0) warnErr("resources.remote needs a server-backed project (a streamed asset needs a server URL) — bundling everything.")
172
168
  }
173
169
 
174
170
  // Custom shaders (.mat) for a host with no asset server: compile them locally (matc) and stage
@@ -181,9 +177,7 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
181
177
  // A caller that knows its target host (every desktop path) narrows this to the single artifact
182
178
  // that host will fetch; otherwise every native variant is compiled and staged, since the
183
179
  // runtime picks by `_creator.backend` and the bundle could be carried anywhere.
184
- const platforms = opts.desktopRenderer
185
- ? [desktopShaderPlatform(process.platform, opts.desktopRenderer)]
186
- : undefined
180
+ const platforms = opts.desktopRenderer ? [desktopShaderPlatform(process.platform, opts.desktopRenderer)] : undefined
187
181
  const local = await applyLocalShaders(entries, { root, local: true, platforms })
188
182
  warnings.push(...local.warnings)
189
183
  stagedShaders = local.artifacts
@@ -207,23 +201,17 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
207
201
  const iconsHeader = await applyAppAssetIcons(entries)
208
202
 
209
203
  note("Compiling…")
210
- const entryFlag = opts.entryOverride ?? flagStr(args, "entry")
211
- if (!entryFlag) {
204
+ const entryOverride = opts.entryOverride ?? bundle.entry
205
+ if (!entryOverride) {
212
206
  const ambiguous = ambiguousEntryWarning(entries)
213
207
  if (ambiguous) warnErr(ambiguous)
214
208
  }
215
209
  const js = await compileProject({
216
- entries,
217
- name,
218
- publicUrl,
219
- entryOverride: entryFlag,
220
- header: !flagBool(args, "no-header"),
221
- localAssets,
222
- fontsHeader,
223
- iconsHeader,
224
- // Both default on; `false` only when the flag is present, so the SDK's own defaults stand.
225
- minify: flagBool(args, "no-minify") ? false : undefined,
226
- sourcemap: flagBool(args, "no-sourcemap", "no-source-map") ? false : undefined,
210
+ entries, name, publicUrl, entryOverride,
211
+ header: bundle.header ?? true,
212
+ localAssets, fontsHeader, iconsHeader,
213
+ minify: bundle.minify,
214
+ sourcemap: bundle.sourcemap,
227
215
  })
228
216
 
229
217
  for (const w of warnings) warnErr(w)
@@ -231,23 +219,9 @@ export const buildBundle = async (args: Args, shell?: ShellCompile, opts: BuildB
231
219
  // Resource inventory for shell/render callers (includes faces the font pass vendored into entries).
232
220
  const resources: BundleResource[] = entries
233
221
  .filter((e) => e.type === "resource")
234
- .map((e) => ({
235
- path: e.path.replace(/^\//, ""),
236
- name: e.path.split(/[\\/]/).pop() ?? e.path,
237
- absPath: e.absPath,
238
- remote: !!e.remote,
239
- }))
222
+ .map((e) => ({ path: e.path.replace(/^\//, ""), name: e.path.split(/[\\/]/).pop() ?? e.path, absPath: e.absPath, remote: !!e.remote }))
240
223
  // Compiled shader artifacts ship the same way (copied next to app.js, loaded by name), flagged
241
224
  // so the desktop packager keeps them — they have no asset() reference for its scan to match.
242
- for (const artifact of stagedShaders) {
243
- resources.push({ path: artifact.name, name: artifact.name, absPath: artifact.absPath, remote: false, shader: true })
244
- }
225
+ for (const artifact of stagedShaders) resources.push({ path: artifact.name, name: artifact.name, absPath: artifact.absPath, remote: false, shader: true })
245
226
  return { js, resources }
246
227
  }
247
-
248
- /** `lecodes preview` — the command's former name, kept working for existing scripts. The deprecation
249
- * goes to stderr, so a piped `lecodes preview > app.js` still yields a clean bundle. */
250
- export const previewLegacy = async (args: Args) => {
251
- warnErr("'lecodes preview' is now 'lecodes compile' — the old name still works.")
252
- await compile(args)
253
- }
@@ -1,18 +1,16 @@
1
1
  import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs"
2
2
  import { join, resolve } from "node:path"
3
- import { createProject, getCommits } from "../api"
4
- import { loadConfig, requireApiUrl, requireToken } from "../config"
5
- import { writeManifest } from "../manifest"
6
- import { materializeTypes, writeTsconfig } from "../types"
7
- import { writeDefaultIgnore } from "../ignore"
8
- import { appClaudeMd, CLAUDE_SETTINGS } from "./projectTemplates"
9
- import { CliError, c, flagStr, info, success, type Args } from "../util"
10
-
11
- /* `lecodes create <name> [dir]` — create a brand-new project on the server, then scaffold a local
12
- * folder for it: the current type definitions (fetched from the server, so IntelliSense matches the
13
- * deployed runtime), a tsconfig, an ignore file, and an import-free starter. Edit, then `lecodes push`. */
14
-
15
- const slug = (name: string) => name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project"
3
+ import { CliError, UsageError, c, defineCommand, info, str, success } from "../cli"
4
+ import { createProject, getCommits } from "../platform/api"
5
+ import { writeDefaultIgnore } from "../project/ignore"
6
+ import { writeManifest } from "../project/manifest"
7
+ import { materializeTypes, writeTsconfig } from "../project/types"
8
+ import { appClaudeMd, CLAUDE_SETTINGS } from "./init/templates"
9
+ import { session, slugOf } from "./shared"
10
+
11
+ /* `lecodes create <name> [dir]` — a brand-new project on the server plus a local folder for it:
12
+ * the server's type definitions (IntelliSense matches the deployed runtime), a tsconfig, an ignore
13
+ * file and an import-free starter. Edit, then `lecodes push`. */
16
14
 
17
15
  const STARTER = `// Welcome to le.codes — no imports needed, the whole SDK is available as globals.
18
16
  const scene = new Scene2D({ background: '#10131a' })
@@ -23,37 +21,38 @@ setLoop((dt) => {
23
21
  })
24
22
  `
25
23
 
26
- export const create = async (args: Args) => {
27
- const config = loadConfig()
28
- const apiUrl = requireApiUrl(config)
29
- const token = requireToken(config)
30
-
31
- const name = args._[0]
32
- if (!name) throw new CliError("Usage: lecodes create <name> [directory]")
33
-
34
- const dir = args._[1] ?? slug(name)
35
- const root = resolve(dir)
36
- if (existsSync(root) && readdirSync(root).length > 0) {
37
- throw new CliError(`Directory "${dir}" already exists and is not empty.`)
38
- }
39
- mkdirSync(root, { recursive: true })
40
-
41
- info(c.dim(`Creating project ${c.bold(name)}…`))
42
- const { uuid } = await createProject(apiUrl, token, name, flagStr(args, "team"))
43
-
44
- info(c.dim("Fetching type definitions…"))
45
- await materializeTypes(root, apiUrl)
46
- writeTsconfig(root)
47
- writeDefaultIgnore(root)
48
- writeFileSync(join(root, "main.ts"), STARTER)
49
- writeFileSync(join(root, "CLAUDE.md"), appClaudeMd(name))
50
- mkdirSync(join(root, ".claude"), { recursive: true })
51
- writeFileSync(join(root, ".claude", "settings.json"), CLAUDE_SETTINGS)
52
-
53
- // A fresh project's tree is empty on the server; the local main.ts is a pending add for the first push.
54
- const commits = await getCommits(apiUrl, token, uuid)
55
- writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} })
56
-
57
- success(`Created ${c.bold(name)} in ${c.bold(dir)}/`)
58
- info(c.dim(` cd ${dir} — edit main.ts, then \`lecodes push -m "first commit"\` to save it`))
59
- }
24
+ export default defineCommand({
25
+ name: "create",
26
+ summary: "Create a new project on the server and scaffold it locally",
27
+ usage: "<name> [dir]",
28
+ flags: { team: str("create it inside a team", { value: "<uuid>" }) },
29
+ run: async ({ args, flags }) => {
30
+ const { apiUrl, token } = session()
31
+ const name = args[0]
32
+ if (!name) throw new UsageError("a project name is required")
33
+
34
+ const dir = args[1] ?? slugOf(name)
35
+ const root = resolve(dir)
36
+ if (existsSync(root) && readdirSync(root).length > 0) throw new CliError(`Directory "${dir}" already exists and is not empty.`)
37
+ mkdirSync(root, { recursive: true })
38
+
39
+ info(c.dim(`Creating project ${c.bold(name)}…`))
40
+ const { uuid } = await createProject(apiUrl, token, name, flags.team)
41
+
42
+ info(c.dim("Fetching type definitions…"))
43
+ await materializeTypes(root, apiUrl)
44
+ writeTsconfig(root)
45
+ writeDefaultIgnore(root)
46
+ writeFileSync(join(root, "main.ts"), STARTER)
47
+ writeFileSync(join(root, "CLAUDE.md"), appClaudeMd(name))
48
+ mkdirSync(join(root, ".claude"), { recursive: true })
49
+ writeFileSync(join(root, ".claude", "settings.json"), CLAUDE_SETTINGS)
50
+
51
+ // A fresh project's tree is empty on the server; the local main.ts is a pending add for the first push.
52
+ const commits = await getCommits(apiUrl, token, uuid)
53
+ writeManifest(root, { uuid, apiUrl, name, baseHeadId: commits.headId, files: {} })
54
+
55
+ success(`Created ${c.bold(name)} in ${c.bold(dir)}/`)
56
+ info(c.dim(` cd ${dir} — edit main.ts, then \`lecodes push -m "first commit"\` to save it`))
57
+ },
58
+ })