@playdrop/playdrop-cli 0.9.6 → 0.10.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 (69) hide show
  1. package/config/client-meta.json +1 -2
  2. package/dist/apiClient.d.ts +2 -0
  3. package/dist/apiClient.js +27 -2
  4. package/dist/appUrls.d.ts +1 -0
  5. package/dist/appUrls.js +9 -0
  6. package/dist/apps/build.js +39 -28
  7. package/dist/apps/index.d.ts +1 -0
  8. package/dist/apps/index.js +2 -0
  9. package/dist/apps/launchCheck.d.ts +2 -0
  10. package/dist/apps/launchCheck.js +31 -6
  11. package/dist/apps/registration.d.ts +1 -0
  12. package/dist/apps/registration.js +1 -0
  13. package/dist/apps/upload.d.ts +1 -0
  14. package/dist/apps/upload.js +4 -17
  15. package/dist/captureRuntime.d.ts +1 -0
  16. package/dist/captureRuntime.js +308 -0
  17. package/dist/catalogue.d.ts +4 -2
  18. package/dist/catalogue.js +50 -7
  19. package/dist/commandContext.js +42 -3
  20. package/dist/commands/capture.d.ts +1 -0
  21. package/dist/commands/capture.js +30 -13
  22. package/dist/commands/create.d.ts +0 -1
  23. package/dist/commands/create.js +2 -151
  24. package/dist/commands/creations.d.ts +0 -13
  25. package/dist/commands/creations.js +0 -141
  26. package/dist/commands/dev.d.ts +2 -1
  27. package/dist/commands/dev.js +23 -6
  28. package/dist/commands/devServer.js +3 -1
  29. package/dist/commands/generation.d.ts +1 -0
  30. package/dist/commands/generation.js +274 -0
  31. package/dist/commands/upload.d.ts +27 -1
  32. package/dist/commands/upload.js +962 -21
  33. package/dist/commands/validate.js +5 -0
  34. package/dist/commands/worker/runtime.d.ts +69 -0
  35. package/dist/commands/worker/runtime.js +414 -0
  36. package/dist/commands/worker.d.ts +144 -0
  37. package/dist/commands/worker.js +2219 -0
  38. package/dist/config.d.ts +2 -0
  39. package/dist/config.js +23 -0
  40. package/dist/index.js +71 -30
  41. package/dist/shellProbe.d.ts +1 -1
  42. package/dist/shellProbe.js +3 -3
  43. package/dist/workspaceAuth.d.ts +1 -0
  44. package/dist/workspaceAuth.js +8 -0
  45. package/node_modules/@playdrop/api-client/dist/client.d.ts +31 -14
  46. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  47. package/node_modules/@playdrop/api-client/dist/client.js +2 -2
  48. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +3 -1
  49. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  50. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  51. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +72 -0
  52. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -0
  53. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +442 -0
  54. package/node_modules/@playdrop/api-client/dist/index.d.ts +31 -14
  55. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  56. package/node_modules/@playdrop/api-client/dist/index.js +134 -38
  57. package/node_modules/@playdrop/config/client-meta.json +1 -2
  58. package/node_modules/@playdrop/types/dist/api.d.ts +501 -74
  59. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  60. package/node_modules/@playdrop/types/dist/api.js +90 -9
  61. package/node_modules/@playdrop/types/dist/app.d.ts +2 -0
  62. package/node_modules/@playdrop/types/dist/app.d.ts.map +1 -1
  63. package/node_modules/@playdrop/types/dist/app.js +3 -0
  64. package/node_modules/@playdrop/types/dist/version.d.ts +1 -0
  65. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  66. package/package.json +2 -1
  67. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts +0 -46
  68. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.d.ts.map +0 -1
  69. package/node_modules/@playdrop/api-client/dist/domains/game-ideas.js +0 -177
@@ -1,10 +1,44 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
3
36
  exports.resolveImageAttachment = resolveImageAttachment;
4
37
  exports.generate = generate;
5
38
  exports.browseGenerationJobs = browseGenerationJobs;
6
39
  exports.showGenerationJob = showGenerationJob;
7
40
  exports.generations = generations;
41
+ const config_1 = require("@playdrop/config");
8
42
  const types_1 = require("@playdrop/types");
9
43
  const node_fs_1 = require("node:fs");
10
44
  const node_path_1 = require("node:path");
@@ -40,6 +74,8 @@ const LOCAL_IMAGE_MIME_BY_EXTENSION = {
40
74
  '.jpeg': 'image/jpeg',
41
75
  '.webp': 'image/webp',
42
76
  };
77
+ const OUTPUT_IMAGE_MIME_BY_EXTENSION = LOCAL_IMAGE_MIME_BY_EXTENSION;
78
+ const GENERATED_PNG_OUTPUT_MAX_BYTES = config_1.MAX_VERSION_HERO_BYTES;
43
79
  function isPlaydropSpeechVoiceId(value) {
44
80
  return types_1.PLAYDROP_SPEECH_VOICE_IDS.includes(value);
45
81
  }
@@ -596,6 +632,211 @@ function printGenerateResult(modality, payload) {
596
632
  console.log('Generated Asset: none');
597
633
  }
598
634
  }
635
+ let pngOutputCrcTable = null;
636
+ function getPngOutputCrcTable() {
637
+ if (pngOutputCrcTable) {
638
+ return pngOutputCrcTable;
639
+ }
640
+ const table = new Uint32Array(256);
641
+ for (let n = 0; n < 256; n++) {
642
+ let c = n;
643
+ for (let k = 0; k < 8; k++) {
644
+ c = (c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1);
645
+ }
646
+ table[n] = c >>> 0;
647
+ }
648
+ pngOutputCrcTable = table;
649
+ return table;
650
+ }
651
+ function pngOutputCrc32(buffer) {
652
+ const table = getPngOutputCrcTable();
653
+ let crc = 0xffffffff;
654
+ for (let index = 0; index < buffer.length; index++) {
655
+ const byte = buffer[index];
656
+ crc = table[(crc ^ byte) & 0xff] ^ (crc >>> 8);
657
+ }
658
+ return (crc ^ 0xffffffff) >>> 0;
659
+ }
660
+ function createPngOutputTextChunk(keyword, value) {
661
+ const keywordText = keyword.trim().slice(0, 79);
662
+ if (!/^[\x20-\x7e]+$/.test(keywordText) || keywordText.includes('\0')) {
663
+ throw new Error('ai_generation_image_output_metadata_failed');
664
+ }
665
+ const textValue = value.replace(/[^\x20-\xff]/g, ' ').slice(0, 4096);
666
+ const data = Buffer.concat([
667
+ Buffer.from(keywordText, 'latin1'),
668
+ Buffer.from([0]),
669
+ Buffer.from(textValue, 'latin1'),
670
+ ]);
671
+ const type = Buffer.from('tEXt', 'ascii');
672
+ const length = Buffer.alloc(4);
673
+ length.writeUInt32BE(data.length, 0);
674
+ const crc = Buffer.alloc(4);
675
+ crc.writeUInt32BE(pngOutputCrc32(Buffer.concat([type, data])), 0);
676
+ return Buffer.concat([length, type, data, crc]);
677
+ }
678
+ function extractGeneratedImageUrl(payload) {
679
+ if (typeof payload?.imageUrl === 'string' && payload.imageUrl.trim()) {
680
+ return payload.imageUrl.trim();
681
+ }
682
+ if (typeof payload?.outputImage === 'string' && payload.outputImage.trim()) {
683
+ return payload.outputImage.trim();
684
+ }
685
+ return null;
686
+ }
687
+ function embedGeneratedImageOutputMetadata(buffer, inputPrompt, payload) {
688
+ if (!isPngSignature(buffer)) {
689
+ return buffer;
690
+ }
691
+ const generationId = typeof payload?.billing?.generationId === 'string'
692
+ ? payload.billing.generationId
693
+ : typeof payload?.generationId === 'string'
694
+ ? payload.generationId
695
+ : '';
696
+ const chunks = [
697
+ createPngOutputTextChunk('playdrop:cliOutputTool', 'playdrop ai create image --output'),
698
+ createPngOutputTextChunk('playdrop:cliOutputPrompt', inputPrompt),
699
+ ...(generationId ? [createPngOutputTextChunk('playdrop:generationId', generationId)] : []),
700
+ ];
701
+ const signature = buffer.subarray(0, 8);
702
+ const parts = [signature];
703
+ let offset = 8;
704
+ let inserted = false;
705
+ while (offset + 12 <= buffer.length) {
706
+ const length = buffer.readUInt32BE(offset);
707
+ const chunkEnd = offset + 12 + length;
708
+ if (chunkEnd > buffer.length) {
709
+ throw new Error('ai_generation_image_output_metadata_failed');
710
+ }
711
+ const chunkType = buffer.toString('ascii', offset + 4, offset + 8);
712
+ if (chunkType === 'IEND' && !inserted) {
713
+ parts.push(...chunks);
714
+ inserted = true;
715
+ }
716
+ parts.push(buffer.subarray(offset, chunkEnd));
717
+ offset = chunkEnd;
718
+ if (chunkType === 'IEND') {
719
+ break;
720
+ }
721
+ }
722
+ if (!inserted) {
723
+ throw new Error('ai_generation_image_output_metadata_failed');
724
+ }
725
+ return Buffer.concat(parts);
726
+ }
727
+ async function writeGeneratedImageOutput(payload, outputPath, inputPrompt) {
728
+ const imageUrl = extractGeneratedImageUrl(payload);
729
+ if (!imageUrl) {
730
+ throw new Error('ai_generation_image_output_missing');
731
+ }
732
+ const response = await fetch(imageUrl);
733
+ if (!response.ok) {
734
+ throw new Error(`ai_generation_image_output_fetch_failed:${response.status}`);
735
+ }
736
+ const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
737
+ if (!contentType.startsWith('image/')) {
738
+ throw new Error(`ai_generation_image_output_invalid_content_type:${contentType || 'unknown'}`);
739
+ }
740
+ const buffer = Buffer.from(await response.arrayBuffer());
741
+ if (buffer.length === 0) {
742
+ throw new Error('ai_generation_image_output_empty');
743
+ }
744
+ const normalizedBuffer = await normalizeGeneratedImageOutputBuffer(buffer, outputPath);
745
+ const outputBuffer = embedGeneratedImageOutputMetadata(normalizedBuffer, inputPrompt, payload);
746
+ const resolvedPath = (0, node_path_1.resolve)(process.cwd(), outputPath);
747
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(resolvedPath), { recursive: true });
748
+ (0, node_fs_1.writeFileSync)(resolvedPath, outputBuffer);
749
+ console.log(`Saved Image: ${resolvedPath}`);
750
+ }
751
+ async function normalizeGeneratedImageOutputBuffer(buffer, outputPath) {
752
+ const extension = (0, node_path_1.extname)(outputPath).toLowerCase();
753
+ const targetMimeType = OUTPUT_IMAGE_MIME_BY_EXTENSION[extension];
754
+ if (!targetMimeType) {
755
+ return buffer;
756
+ }
757
+ const detectedMimeType = detectLocalImageMimeType(buffer);
758
+ if (!detectedMimeType) {
759
+ throw new Error('ai_generation_image_output_unsupported_type');
760
+ }
761
+ if (detectedMimeType === targetMimeType) {
762
+ if (targetMimeType === 'image/png' && buffer.length > GENERATED_PNG_OUTPUT_MAX_BYTES) {
763
+ return optimizeGeneratedPngOutputBuffer(buffer);
764
+ }
765
+ return buffer;
766
+ }
767
+ let sharp;
768
+ try {
769
+ const sharpModule = await Promise.resolve().then(() => __importStar(require('sharp')));
770
+ sharp = sharpModule.default ?? sharpModule;
771
+ }
772
+ catch {
773
+ throw new Error('ai_generation_image_output_conversion_unavailable');
774
+ }
775
+ try {
776
+ if (targetMimeType === 'image/png') {
777
+ return await optimizeGeneratedPngOutputBuffer(buffer, sharp);
778
+ }
779
+ const pipeline = sharp(buffer, { failOn: 'error' });
780
+ if (targetMimeType === 'image/jpeg') {
781
+ return await pipeline.jpeg({ quality: 92 }).toBuffer();
782
+ }
783
+ if (targetMimeType === 'image/webp') {
784
+ return await pipeline.webp({ quality: 92 }).toBuffer();
785
+ }
786
+ }
787
+ catch {
788
+ throw new Error('ai_generation_image_output_conversion_failed');
789
+ }
790
+ return buffer;
791
+ }
792
+ async function optimizeGeneratedPngOutputBuffer(buffer, loadedSharp) {
793
+ let sharp = loadedSharp;
794
+ if (!sharp) {
795
+ try {
796
+ const sharpModule = await Promise.resolve().then(() => __importStar(require('sharp')));
797
+ sharp = sharpModule.default ?? sharpModule;
798
+ }
799
+ catch {
800
+ throw new Error('ai_generation_image_output_conversion_unavailable');
801
+ }
802
+ }
803
+ const metadata = await sharp(buffer, { failOn: 'error' }).metadata();
804
+ const sourceWidth = typeof metadata.width === 'number' && metadata.width > 0 ? metadata.width : null;
805
+ const sourceHeight = typeof metadata.height === 'number' && metadata.height > 0 ? metadata.height : null;
806
+ const attempts = [
807
+ { scale: 1, palette: false, quality: 100 },
808
+ { scale: 1, palette: true, quality: 92 },
809
+ { scale: 0.9, palette: true, quality: 88 },
810
+ { scale: 0.8, palette: true, quality: 84 },
811
+ { scale: 0.7, palette: true, quality: 80 },
812
+ { scale: 0.6, palette: true, quality: 76 },
813
+ { scale: 0.5, palette: true, quality: 72 },
814
+ ];
815
+ let smallest = null;
816
+ for (const attempt of attempts) {
817
+ let pipeline = sharp(buffer, { failOn: 'error' }).rotate();
818
+ if (attempt.scale < 1 && sourceWidth && sourceHeight) {
819
+ pipeline = pipeline.resize({
820
+ width: Math.max(1, Math.round(sourceWidth * attempt.scale)),
821
+ height: Math.max(1, Math.round(sourceHeight * attempt.scale)),
822
+ fit: 'inside',
823
+ withoutEnlargement: true,
824
+ });
825
+ }
826
+ const output = await pipeline.png({
827
+ compressionLevel: 9,
828
+ adaptiveFiltering: true,
829
+ ...(attempt.palette ? { palette: true, quality: attempt.quality } : {}),
830
+ }).toBuffer();
831
+ if (!smallest || output.length < smallest.length) {
832
+ smallest = output;
833
+ }
834
+ if (output.length <= GENERATED_PNG_OUTPUT_MAX_BYTES) {
835
+ return output;
836
+ }
837
+ }
838
+ throw new Error(`ai_generation_image_output_too_large:${smallest?.length ?? buffer.length}:${GENERATED_PNG_OUTPUT_MAX_BYTES}`);
839
+ }
599
840
  function formatJobStatus(job) {
600
841
  const progress = typeof job.progressPercent === 'number'
601
842
  ? ` progress=${Math.max(0, Math.min(100, Math.round(job.progressPercent)))}%`
@@ -624,6 +865,16 @@ async function generate(typeInput, promptInput, options = {}) {
624
865
  process.exitCode = 1;
625
866
  return;
626
867
  }
868
+ const outputPath = typeof options.output === 'string' && options.output.trim().length > 0
869
+ ? options.output.trim()
870
+ : null;
871
+ if (outputPath && parsed.modality !== 'IMAGE') {
872
+ (0, messages_1.printErrorWithHelp)('The --output option is only supported for image generation.', ['Use "playdrop ai create image ... --output <file>".'], {
873
+ command: 'ai create',
874
+ });
875
+ process.exitCode = 1;
876
+ return;
877
+ }
627
878
  await (0, commandContext_1.withEnvironment)('ai create', 'Generating AI assets', async ({ aiClient }) => {
628
879
  try {
629
880
  const created = await aiClient.createGenerationJob(parsed.request);
@@ -656,6 +907,9 @@ async function generate(typeInput, promptInput, options = {}) {
656
907
  throw new Error(`${errorCode}: ${errorMessage}`);
657
908
  }
658
909
  const payload = (job.resultData && typeof job.resultData === 'object') ? job.resultData : {};
910
+ if (outputPath) {
911
+ await writeGeneratedImageOutput(payload, outputPath, parsed.request.input);
912
+ }
659
913
  if (options.json) {
660
914
  (0, output_1.printJson)({ job, result: payload });
661
915
  return;
@@ -711,6 +965,26 @@ function handleAiFailure(error, command, context) {
711
965
  process.exitCode = 1;
712
966
  return true;
713
967
  }
968
+ if (message === 'ai_generation_image_output_unsupported_type') {
969
+ (0, messages_1.printErrorWithHelp)('AI create returned an image format that Playdrop CLI could not identify.', ['Retry the command. If the problem persists, contact Playdrop support.'], { command });
970
+ process.exitCode = 1;
971
+ return true;
972
+ }
973
+ if (message === 'ai_generation_image_output_conversion_unavailable') {
974
+ (0, messages_1.printErrorWithHelp)('AI create could not convert the generated image to the requested output file type.', ['Install the Playdrop CLI dependencies again, then retry.'], { command });
975
+ process.exitCode = 1;
976
+ return true;
977
+ }
978
+ if (message === 'ai_generation_image_output_conversion_failed') {
979
+ (0, messages_1.printErrorWithHelp)('AI create failed while converting the generated image to the requested output file type.', ['Retry with a matching output extension such as .jpg, or retry the generation.'], { command });
980
+ process.exitCode = 1;
981
+ return true;
982
+ }
983
+ if (message === 'ai_generation_image_output_metadata_failed') {
984
+ (0, messages_1.printErrorWithHelp)('AI create failed while writing PlayDrop metadata to the generated image.', ['Retry the generation. The output image must keep prompt metadata for worker validation.'], { command });
985
+ process.exitCode = 1;
986
+ return true;
987
+ }
714
988
  if (message.startsWith('ai_generation_job_timeout:')) {
715
989
  const detail = message.split(':').slice(1).join(':').trim();
716
990
  (0, messages_1.printErrorWithHelp)(detail || 'AI create timed out while waiting for the job to finish.', ['Retry the command with a higher --timeout value if the job normally takes longer.'], { command });
@@ -1,8 +1,34 @@
1
+ import type { ApiClient } from '@playdrop/api-client';
2
+ import type { UserResponse } from '@playdrop/types';
1
3
  export type UploadCommandOptions = {
2
4
  env?: string;
3
5
  skipEcs?: boolean;
4
6
  skipReview?: boolean;
5
7
  clearTags?: boolean;
6
- gameIdea?: string;
7
8
  };
8
9
  export declare function upload(pathOrName: string, options?: UploadCommandOptions): Promise<void>;
10
+ export type WorkerAppPublishInput = {
11
+ client: ApiClient;
12
+ taskId: number;
13
+ kind: 'NEW_GAME' | 'GAME_UPDATE';
14
+ expectedAppName?: string | null;
15
+ playdropAssetRequirement?: WorkerPlaydropAssetRequirement | null;
16
+ creatorRequest?: string | null;
17
+ projectDir: string;
18
+ creatorUsername: string;
19
+ apiBase: string;
20
+ webBase?: string | null;
21
+ token: string;
22
+ user: UserResponse;
23
+ };
24
+ export type WorkerAppPublishResult = {
25
+ appId: number;
26
+ appVersionId: number;
27
+ versionNodeId: string;
28
+ version: string;
29
+ appName: string;
30
+ creatorUsername: string;
31
+ warnings: string[];
32
+ };
33
+ export type WorkerPlaydropAssetRequirement = 'PACK' | 'ASSET_OR_PACK';
34
+ export declare function publishWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPublishResult>;