@capgo/cli 7.86.1 → 7.88.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.
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
3
  "type": "module",
4
- "version": "7.86.1",
4
+ "version": "7.88.0",
5
5
  "description": "A CLI to upload to capgo servers",
6
6
  "author": "Martin martin@capgo.app",
7
7
  "license": "Apache 2.0",
@@ -91,8 +91,11 @@
91
91
  "@supabase/supabase-js": "^2.79.0",
92
92
  "@tanstack/intent": "^0.0.23",
93
93
  "@types/adm-zip": "^0.5.7",
94
+ "@types/jsonwebtoken": "^9.0.10",
94
95
  "@types/node": "^25.0.0",
96
+ "@types/node-forge": "^1.3.14",
95
97
  "@types/prettyjson": "^0.0.33",
98
+ "@types/react": "^18.3.28",
96
99
  "@types/tmp": "^0.2.6",
97
100
  "@vercel/ncc": "^0.38.4",
98
101
  "adm-zip": "^0.5.16",
@@ -111,5 +114,13 @@
111
114
  "typescript": "^5.9.3",
112
115
  "ws": "^8.18.3",
113
116
  "zod": "^4.3.6"
117
+ },
118
+ "dependencies": {
119
+ "@inkjs/ui": "^2.0.0",
120
+ "ink": "^5.2.1",
121
+ "ink-spinner": "^5.0.0",
122
+ "jsonwebtoken": "^9.0.3",
123
+ "node-forge": "^1.3.3",
124
+ "react": "^18.3.1"
114
125
  }
115
126
  }
@@ -2,6 +2,9 @@ import type { SupabaseClient } from '@supabase/supabase-js';
2
2
  import type { Database } from '../types/supabase.types';
3
3
  import { OrganizationPerm } from '../utils';
4
4
  export declare function checkAppExists(supabase: SupabaseClient<Database>, appid: string): Promise<boolean>;
5
+ export type PendingOnboardingApp = Pick<Database['public']['Tables']['apps']['Row'], 'app_id' | 'name' | 'icon_url' | 'need_onboarding' | 'existing_app' | 'ios_store_url' | 'android_store_url'>;
6
+ export declare function listPendingOnboardingApps(supabase: SupabaseClient<Database>, orgId: string): Promise<PendingOnboardingApp[]>;
7
+ export declare function completePendingOnboardingApp(supabase: SupabaseClient<Database>, orgId: string, appId: string): Promise<void>;
5
8
  /**
6
9
  * Check multiple app IDs at once for batch validation (e.g., for suggestions)
7
10
  */
@@ -277,15 +277,19 @@ export declare function findChannelDevices(supabase: SupabaseClient<Database>, a
277
277
  };
278
278
  apps: {
279
279
  Row: {
280
+ android_store_url: string | null;
280
281
  app_id: string;
281
282
  channel_device_count: number;
282
283
  created_at: string | null;
283
284
  default_upload_channel: string;
285
+ existing_app: boolean;
284
286
  expose_metadata: boolean;
285
287
  icon_url: string;
286
288
  id: string | null;
289
+ ios_store_url: string | null;
287
290
  last_version: string | null;
288
291
  manifest_bundle_count: number;
292
+ need_onboarding: boolean;
289
293
  name: string | null;
290
294
  owner_org: string;
291
295
  retention: number;
@@ -294,15 +298,19 @@ export declare function findChannelDevices(supabase: SupabaseClient<Database>, a
294
298
  user_id: string | null;
295
299
  };
296
300
  Insert: {
301
+ android_store_url?: string | null;
297
302
  app_id: string;
298
303
  channel_device_count?: number;
299
304
  created_at?: string | null;
300
305
  default_upload_channel?: string;
306
+ existing_app?: boolean;
301
307
  expose_metadata?: boolean;
302
308
  icon_url: string;
303
309
  id?: string | null;
310
+ ios_store_url?: string | null;
304
311
  last_version?: string | null;
305
312
  manifest_bundle_count?: number;
313
+ need_onboarding?: boolean;
306
314
  name?: string | null;
307
315
  owner_org: string;
308
316
  retention?: number;
@@ -311,15 +319,19 @@ export declare function findChannelDevices(supabase: SupabaseClient<Database>, a
311
319
  user_id?: string | null;
312
320
  };
313
321
  Update: {
322
+ android_store_url?: string | null;
314
323
  app_id?: string;
315
324
  channel_device_count?: number;
316
325
  created_at?: string | null;
317
326
  default_upload_channel?: string;
327
+ existing_app?: boolean;
318
328
  expose_metadata?: boolean;
319
329
  icon_url?: string;
320
330
  id?: string | null;
331
+ ios_store_url?: string | null;
321
332
  last_version?: string | null;
322
333
  manifest_bundle_count?: number;
334
+ need_onboarding?: boolean;
323
335
  name?: string | null;
324
336
  owner_org?: string;
325
337
  retention?: number;
@@ -3747,15 +3759,19 @@ export declare function delChannelDevices(supabase: SupabaseClient<Database>, ap
3747
3759
  };
3748
3760
  apps: {
3749
3761
  Row: {
3762
+ android_store_url: string | null;
3750
3763
  app_id: string;
3751
3764
  channel_device_count: number;
3752
3765
  created_at: string | null;
3753
3766
  default_upload_channel: string;
3767
+ existing_app: boolean;
3754
3768
  expose_metadata: boolean;
3755
3769
  icon_url: string;
3756
3770
  id: string | null;
3771
+ ios_store_url: string | null;
3757
3772
  last_version: string | null;
3758
3773
  manifest_bundle_count: number;
3774
+ need_onboarding: boolean;
3759
3775
  name: string | null;
3760
3776
  owner_org: string;
3761
3777
  retention: number;
@@ -3764,15 +3780,19 @@ export declare function delChannelDevices(supabase: SupabaseClient<Database>, ap
3764
3780
  user_id: string | null;
3765
3781
  };
3766
3782
  Insert: {
3783
+ android_store_url?: string | null;
3767
3784
  app_id: string;
3768
3785
  channel_device_count?: number;
3769
3786
  created_at?: string | null;
3770
3787
  default_upload_channel?: string;
3788
+ existing_app?: boolean;
3771
3789
  expose_metadata?: boolean;
3772
3790
  icon_url: string;
3773
3791
  id?: string | null;
3792
+ ios_store_url?: string | null;
3774
3793
  last_version?: string | null;
3775
3794
  manifest_bundle_count?: number;
3795
+ need_onboarding?: boolean;
3776
3796
  name?: string | null;
3777
3797
  owner_org: string;
3778
3798
  retention?: number;
@@ -3781,15 +3801,19 @@ export declare function delChannelDevices(supabase: SupabaseClient<Database>, ap
3781
3801
  user_id?: string | null;
3782
3802
  };
3783
3803
  Update: {
3804
+ android_store_url?: string | null;
3784
3805
  app_id?: string;
3785
3806
  channel_device_count?: number;
3786
3807
  created_at?: string | null;
3787
3808
  default_upload_channel?: string;
3809
+ existing_app?: boolean;
3788
3810
  expose_metadata?: boolean;
3789
3811
  icon_url?: string;
3790
3812
  id?: string | null;
3813
+ ios_store_url?: string | null;
3791
3814
  last_version?: string | null;
3792
3815
  manifest_bundle_count?: number;
3816
+ need_onboarding?: boolean;
3793
3817
  name?: string | null;
3794
3818
  owner_org?: string;
3795
3819
  retention?: number;
@@ -1,14 +1,18 @@
1
1
  import type { OptionsBase } from '../schemas/base';
2
2
  export declare function listAppInternal(options: OptionsBase, silent?: boolean): Promise<{
3
+ android_store_url: string | null;
3
4
  app_id: string;
4
5
  channel_device_count: number;
5
6
  created_at: string | null;
6
7
  default_upload_channel: string;
8
+ existing_app: boolean;
7
9
  expose_metadata: boolean;
8
10
  icon_url: string;
9
11
  id: string | null;
12
+ ios_store_url: string | null;
10
13
  last_version: string | null;
11
14
  manifest_bundle_count: number;
15
+ need_onboarding: boolean;
12
16
  name: string | null;
13
17
  owner_org: string;
14
18
  retention: number;
@@ -17,15 +21,19 @@ export declare function listAppInternal(options: OptionsBase, silent?: boolean):
17
21
  user_id: string | null;
18
22
  }[]>;
19
23
  export declare function listApp(options: OptionsBase): Promise<{
24
+ android_store_url: string | null;
20
25
  app_id: string;
21
26
  channel_device_count: number;
22
27
  created_at: string | null;
23
28
  default_upload_channel: string;
29
+ existing_app: boolean;
24
30
  expose_metadata: boolean;
25
31
  icon_url: string;
26
32
  id: string | null;
33
+ ios_store_url: string | null;
27
34
  last_version: string | null;
28
35
  manifest_bundle_count: number;
36
+ need_onboarding: boolean;
29
37
  name: string | null;
30
38
  owner_org: string;
31
39
  retention: number;
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Generate a JWT for App Store Connect API authentication.
3
+ * Uses ES256 algorithm with the .p8 private key.
4
+ */
5
+ export declare function generateJwt(keyId: string, issuerId: string, p8Content: string): string;
6
+ /**
7
+ * Verify the API key works and try to detect the team ID from existing certificates.
8
+ * Throws on 401/403 with a user-friendly message.
9
+ */
10
+ export declare function verifyApiKey(token: string): Promise<{
11
+ valid: true;
12
+ teamId: string;
13
+ }>;
14
+ /**
15
+ * List all iOS distribution certificates.
16
+ */
17
+ export declare function listDistributionCerts(token: string): Promise<Array<{
18
+ id: string;
19
+ name: string;
20
+ serialNumber: string;
21
+ expirationDate: string;
22
+ }>>;
23
+ /**
24
+ * Revoke (delete) a certificate by ID.
25
+ */
26
+ export declare function revokeCertificate(token: string, certId: string): Promise<void>;
27
+ /**
28
+ * Error thrown when certificate limit is reached.
29
+ * Contains the existing certificates so the UI can ask the user which to revoke.
30
+ */
31
+ export declare class CertificateLimitError extends Error {
32
+ readonly certificates: Array<{
33
+ id: string;
34
+ name: string;
35
+ serialNumber: string;
36
+ expirationDate: string;
37
+ }>;
38
+ constructor(certificates: Array<{
39
+ id: string;
40
+ name: string;
41
+ serialNumber: string;
42
+ expirationDate: string;
43
+ }>);
44
+ }
45
+ /**
46
+ * Create a distribution certificate using a CSR.
47
+ * Returns the certificate ID, base64 DER content, expiration date, and team ID.
48
+ *
49
+ * Throws CertificateLimitError if the limit is reached, so the UI can ask
50
+ * the user which certificate to revoke.
51
+ */
52
+ export declare function createCertificate(token: string, csrPem: string): Promise<{
53
+ certificateId: string;
54
+ certificateContent: string;
55
+ expirationDate: string;
56
+ teamId: string;
57
+ }>;
58
+ /**
59
+ * Find an existing bundle ID or register a new one.
60
+ * Returns the Apple resource ID needed for profile creation.
61
+ */
62
+ export declare function ensureBundleId(token: string, identifier: string): Promise<{
63
+ bundleIdResourceId: string;
64
+ }>;
65
+ /**
66
+ * Get the profile name we use for a given appId.
67
+ */
68
+ export declare function getCapgoProfileName(appId: string): string;
69
+ /**
70
+ * Find existing provisioning profiles matching our naming convention.
71
+ * Only returns profiles we created (named "Capgo <appId> AppStore").
72
+ */
73
+ export declare function findCapgoProfiles(token: string, appId: string): Promise<Array<{
74
+ id: string;
75
+ name: string;
76
+ profileType: string;
77
+ }>>;
78
+ /**
79
+ * Delete a provisioning profile by ID.
80
+ */
81
+ export declare function deleteProfile(token: string, profileId: string): Promise<void>;
82
+ /**
83
+ * Create an App Store provisioning profile linking a certificate and bundle ID.
84
+ * Returns the base64 mobileprovision content.
85
+ *
86
+ * Throws a DuplicateProfileError if duplicate profiles exist, so the caller
87
+ * can ask the user whether to delete them and retry.
88
+ */
89
+ export declare class DuplicateProfileError extends Error {
90
+ readonly profiles: Array<{
91
+ id: string;
92
+ name: string;
93
+ profileType: string;
94
+ }>;
95
+ constructor(profiles: Array<{
96
+ id: string;
97
+ name: string;
98
+ profileType: string;
99
+ }>);
100
+ }
101
+ export declare function createProfile(token: string, bundleIdResourceId: string, certificateId: string, appId: string): Promise<{
102
+ profileId: string;
103
+ profileName: string;
104
+ profileContent: string;
105
+ expirationDate: string;
106
+ }>;
@@ -0,0 +1 @@
1
+ export declare function onboardingCommand(): Promise<void>;
@@ -0,0 +1,33 @@
1
+ export interface CsrResult {
2
+ csrPem: string;
3
+ privateKeyPem: string;
4
+ }
5
+ export interface P12Result {
6
+ p12Base64: string;
7
+ }
8
+ /**
9
+ * Generate a 2048-bit RSA key pair and a Certificate Signing Request.
10
+ * The CSR is what Apple needs to create a distribution certificate.
11
+ * The private key must be kept to later create the .p12 file.
12
+ */
13
+ export declare function generateCsr(): CsrResult;
14
+ /**
15
+ * Create a PKCS#12 (.p12) file from Apple's certificate response and the private key.
16
+ *
17
+ * @param certificateContentBase64 - The `certificateContent` field from Apple's
18
+ * POST /v1/certificates response (base64-encoded DER certificate)
19
+ * @param privateKeyPem - The PEM-encoded private key from generateCsr()
20
+ * @param password - Optional password for the .p12 file (defaults to DEFAULT_P12_PASSWORD)
21
+ */
22
+ /**
23
+ * Extract the Apple team ID from a certificate's subject OU field.
24
+ * More reliable than parsing the certificate name string.
25
+ */
26
+ export declare function extractTeamIdFromCert(certificateContentBase64: string): string;
27
+ /**
28
+ * Default P12 password. node-forge P12 with empty password is incompatible
29
+ * with macOS `security import` (MAC verification fails). Using a known
30
+ * non-empty password avoids this issue.
31
+ */
32
+ export declare const DEFAULT_P12_PASSWORD = "capgo";
33
+ export declare function createP12(certificateContentBase64: string, privateKeyPem: string, password?: string): P12Result;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Returns true if we're on macOS and can use the native file picker.
3
+ */
4
+ export declare function canUseFilePicker(): boolean;
5
+ /**
6
+ * Open the macOS native file picker dialog filtered to .p8 files.
7
+ * Returns the selected file path, or null if the user cancelled.
8
+ * Non-blocking — uses async execFile so Ink spinners keep animating.
9
+ */
10
+ export declare function openFilePicker(): Promise<string | null>;
@@ -0,0 +1,19 @@
1
+ import type { OnboardingProgress, OnboardingStep } from './types.js';
2
+ /**
3
+ * Load onboarding progress for an app. Returns null if no progress file exists.
4
+ */
5
+ export declare function loadProgress(appId: string, baseDir?: string): Promise<OnboardingProgress | null>;
6
+ /**
7
+ * Save onboarding progress. Creates the onboarding directory if needed.
8
+ * File is written with mode 0o600, directory with 0o700.
9
+ */
10
+ export declare function saveProgress(appId: string, progress: OnboardingProgress, baseDir?: string): Promise<void>;
11
+ /**
12
+ * Delete the progress file for an app (called on successful completion).
13
+ */
14
+ export declare function deleteProgress(appId: string, baseDir?: string): Promise<void>;
15
+ /**
16
+ * Determine the first incomplete step based on saved progress.
17
+ * Returns the step to resume from.
18
+ */
19
+ export declare function getResumeStep(progress: OnboardingProgress | null): OnboardingStep;
@@ -0,0 +1,37 @@
1
+ export type Platform = 'ios' | 'android';
2
+ export type OnboardingStep = 'welcome' | 'platform-select' | 'credentials-exist' | 'backing-up' | 'api-key-instructions' | 'p8-method-select' | 'input-p8-path' | 'input-key-id' | 'input-issuer-id' | 'verifying-key' | 'creating-certificate' | 'cert-limit-prompt' | 'revoking-certificate' | 'creating-profile' | 'duplicate-profile-prompt' | 'deleting-duplicate-profiles' | 'saving-credentials' | 'ask-build' | 'requesting-build' | 'build-complete' | 'no-platform' | 'error';
3
+ export interface ApiKeyData {
4
+ keyId: string;
5
+ issuerId: string;
6
+ }
7
+ export interface CertificateData {
8
+ certificateId: string;
9
+ expirationDate: string;
10
+ teamId: string;
11
+ p12Base64: string;
12
+ }
13
+ export interface ProfileData {
14
+ profileId: string;
15
+ profileName: string;
16
+ profileBase64: string;
17
+ }
18
+ export interface OnboardingProgress {
19
+ platform: Platform;
20
+ appId: string;
21
+ startedAt: string;
22
+ /** Path to the .p8 file on disk (content is NOT stored, only the path) */
23
+ p8Path?: string;
24
+ /** Partial input — saved incrementally so resume works mid-flow */
25
+ keyId?: string;
26
+ issuerId?: string;
27
+ completedSteps: {
28
+ apiKeyVerified?: ApiKeyData;
29
+ certificateCreated?: CertificateData;
30
+ profileCreated?: ProfileData;
31
+ };
32
+ /** Temporary — wiped after .p12 creation */
33
+ _privateKeyPem?: string;
34
+ }
35
+ /** Maps each step to a progress percentage (0-100) */
36
+ export declare const STEP_PROGRESS: Record<OnboardingStep, number>;
37
+ export declare function getPhaseLabel(step: OnboardingStep): string;
@@ -0,0 +1,10 @@
1
+ import type { FC } from 'react';
2
+ import type { OnboardingProgress } from '../types.js';
3
+ interface AppProps {
4
+ appId: string;
5
+ initialProgress: OnboardingProgress | null;
6
+ /** Resolved iOS directory from capacitor.config (defaults to 'ios') */
7
+ iosDir: string;
8
+ }
9
+ declare const OnboardingApp: FC<AppProps>;
10
+ export default OnboardingApp;
@@ -0,0 +1,25 @@
1
+ import type { FC } from 'react';
2
+ export declare const Divider: FC<{
3
+ width?: number;
4
+ }>;
5
+ export declare const SpinnerLine: FC<{
6
+ text: string;
7
+ }>;
8
+ export declare const SuccessLine: FC<{
9
+ text: string;
10
+ detail?: string;
11
+ }>;
12
+ export declare const ErrorLine: FC<{
13
+ text: string;
14
+ }>;
15
+ /**
16
+ * Custom TextInput that filters out specific characters (e.g. '=').
17
+ * @inkjs/ui's TextInput is uncontrolled and can't filter keystrokes,
18
+ * so we build a minimal one with Ink's useInput.
19
+ */
20
+ export declare const FilteredTextInput: FC<{
21
+ placeholder?: string;
22
+ filter?: string;
23
+ onSubmit: (value: string) => void;
24
+ }>;
25
+ export declare const Header: FC;
@@ -26,6 +26,21 @@
26
26
  * - Use `build credentials clear` to remove saved credentials
27
27
  */
28
28
  import type { BuildOptionsPayload, BuildRequestOptions, BuildRequestResult } from '../schemas/build';
29
+ /**
30
+ * Callback interface for build logging.
31
+ * Allows callers (like the onboarding UI) to capture log output
32
+ * without stdout/stderr interception hacks.
33
+ */
34
+ export interface BuildLogger {
35
+ info: (msg: string) => void;
36
+ error: (msg: string) => void;
37
+ warn: (msg: string) => void;
38
+ success: (msg: string) => void;
39
+ /** Called with build log lines streamed from the builder */
40
+ buildLog: (msg: string) => void;
41
+ /** Called with upload progress percentage (0-100) */
42
+ uploadProgress: (percent: number) => void;
43
+ }
29
44
  export type { BuildCredentials, BuildRequestOptions, BuildRequestResponse, BuildRequestResult } from '../schemas/build';
30
45
  /**
31
46
  * Extract native node_modules roots that contain platform folders.
@@ -74,5 +89,5 @@ export declare function splitPayload(mergedCredentials: Record<string, string |
74
89
  buildOptions: BuildOptionsPayload;
75
90
  buildCredentials: Record<string, string>;
76
91
  };
77
- export declare function requestBuildInternal(appId: string, options: BuildRequestOptions, silent?: boolean): Promise<BuildRequestResult>;
92
+ export declare function requestBuildInternal(appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger): Promise<BuildRequestResult>;
78
93
  export declare function requestBuildCommand(appId: string, options: BuildRequestOptions): Promise<void>;