hub-launch 1.30.0 → 1.31.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 (96) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +52 -0
  3. package/dist/commands/errorWatcher.d.ts +2 -0
  4. package/dist/commands/errorWatcher.d.ts.map +1 -1
  5. package/dist/commands/errorWatcher.js +13 -0
  6. package/dist/commands/errorWatcher.js.map +1 -1
  7. package/dist/commands/init.d.ts +12 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +52 -1
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/launch.d.ts +36 -0
  12. package/dist/commands/launch.d.ts.map +1 -1
  13. package/dist/commands/launch.js +267 -6
  14. package/dist/commands/launch.js.map +1 -1
  15. package/dist/commands/login.d.ts +14 -1
  16. package/dist/commands/login.d.ts.map +1 -1
  17. package/dist/commands/login.js +26 -4
  18. package/dist/commands/login.js.map +1 -1
  19. package/dist/commands/schedule.d.ts.map +1 -1
  20. package/dist/commands/schedule.js +12 -0
  21. package/dist/commands/schedule.js.map +1 -1
  22. package/dist/commands/script.d.ts +1 -1
  23. package/dist/commands/script.d.ts.map +1 -1
  24. package/dist/commands/script.js +3 -0
  25. package/dist/commands/script.js.map +1 -1
  26. package/dist/commands/session-hook.d.ts +9 -0
  27. package/dist/commands/session-hook.d.ts.map +1 -1
  28. package/dist/commands/session-hook.js +60 -15
  29. package/dist/commands/session-hook.js.map +1 -1
  30. package/dist/index.js +2 -1
  31. package/dist/index.js.map +1 -1
  32. package/dist/scripts/backlog-add.d.ts +2 -0
  33. package/dist/scripts/backlog-add.d.ts.map +1 -0
  34. package/dist/scripts/backlog-add.js +145 -0
  35. package/dist/scripts/backlog-add.js.map +1 -0
  36. package/dist/scripts/backlog-init.d.ts +2 -0
  37. package/dist/scripts/backlog-init.d.ts.map +1 -0
  38. package/dist/scripts/backlog-init.js +84 -0
  39. package/dist/scripts/backlog-init.js.map +1 -0
  40. package/dist/scripts/backlog-resync.d.ts +2 -0
  41. package/dist/scripts/backlog-resync.d.ts.map +1 -0
  42. package/dist/scripts/backlog-resync.js +156 -0
  43. package/dist/scripts/backlog-resync.js.map +1 -0
  44. package/dist/scripts/launch-run.d.ts +1 -0
  45. package/dist/scripts/launch-run.d.ts.map +1 -1
  46. package/dist/scripts/launch-run.js +47 -3
  47. package/dist/scripts/launch-run.js.map +1 -1
  48. package/dist/scripts/lib/backlog-sync.d.ts +27 -0
  49. package/dist/scripts/lib/backlog-sync.d.ts.map +1 -0
  50. package/dist/scripts/lib/backlog-sync.js +48 -0
  51. package/dist/scripts/lib/backlog-sync.js.map +1 -0
  52. package/dist/scripts/lib/read-config.d.ts +16 -0
  53. package/dist/scripts/lib/read-config.d.ts.map +1 -1
  54. package/dist/scripts/lib/read-config.js +34 -0
  55. package/dist/scripts/lib/read-config.js.map +1 -1
  56. package/dist/services/api/HulaApiClient.d.ts +39 -0
  57. package/dist/services/api/HulaApiClient.d.ts.map +1 -1
  58. package/dist/services/api/HulaApiClient.js +15 -0
  59. package/dist/services/api/HulaApiClient.js.map +1 -1
  60. package/dist/services/github/IssueService.d.ts.map +1 -1
  61. package/dist/services/github/IssueService.js +3 -0
  62. package/dist/services/github/IssueService.js.map +1 -1
  63. package/dist/services/plan/GitHubSourcePlanService.d.ts +67 -0
  64. package/dist/services/plan/GitHubSourcePlanService.d.ts.map +1 -0
  65. package/dist/services/plan/GitHubSourcePlanService.js +148 -0
  66. package/dist/services/plan/GitHubSourcePlanService.js.map +1 -0
  67. package/dist/services/plan/PlanBranchService.d.ts +5 -1
  68. package/dist/services/plan/PlanBranchService.d.ts.map +1 -1
  69. package/dist/services/plan/PlanBranchService.js +21 -3
  70. package/dist/services/plan/PlanBranchService.js.map +1 -1
  71. package/dist/templates/proceed-instructions.md +2 -1
  72. package/dist/templates/skills/hula-approve/SKILL.md +31 -0
  73. package/dist/templates/skills/hula-fix/SKILL.md +10 -0
  74. package/dist/templates/skills/hula-launch/SKILL.md +40 -1
  75. package/dist/templates/skills/hula-plan/SKILL.md +39 -4
  76. package/dist/types/config.schema.d.ts +24 -0
  77. package/dist/types/config.schema.d.ts.map +1 -1
  78. package/dist/types/config.schema.js +11 -0
  79. package/dist/types/config.schema.js.map +1 -1
  80. package/dist/types/issue.schema.d.ts +6 -0
  81. package/dist/types/issue.schema.d.ts.map +1 -1
  82. package/dist/types/issue.schema.js +8 -0
  83. package/dist/types/issue.schema.js.map +1 -1
  84. package/dist/types/pr.schema.d.ts +38 -0
  85. package/dist/types/pr.schema.d.ts.map +1 -1
  86. package/dist/types/pr.schema.js +7 -0
  87. package/dist/types/pr.schema.js.map +1 -1
  88. package/dist/utils/client-session.d.ts +16 -0
  89. package/dist/utils/client-session.d.ts.map +1 -1
  90. package/dist/utils/client-session.js +29 -0
  91. package/dist/utils/client-session.js.map +1 -1
  92. package/dist/utils/sandbox-profile.d.ts +44 -0
  93. package/dist/utils/sandbox-profile.d.ts.map +1 -0
  94. package/dist/utils/sandbox-profile.js +78 -0
  95. package/dist/utils/sandbox-profile.js.map +1 -0
  96. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import type { Config } from '../types/index.js';
3
3
  import type { LlmProviderType } from '../types/config.schema.js';
4
+ import { type SandboxProfile } from '../utils/sandbox-profile.js';
4
5
  /**
5
6
  * Launch command - Trigger a launch job on hula-project server
6
7
  *
@@ -30,6 +31,12 @@ interface LaunchOptions {
30
31
  verbose?: boolean;
31
32
  test?: boolean;
32
33
  handoff?: string;
34
+ /** --issue: existing GitHub issue reference (number, #number, or URL). */
35
+ issue?: string;
36
+ /** --pr: existing pull request reference (number, #number, or URL). */
37
+ pr?: string;
38
+ /** --base: branch to cut the work branch from and open the PR into. */
39
+ base?: string;
33
40
  show?: string;
34
41
  logs?: string;
35
42
  lines?: number;
@@ -42,6 +49,8 @@ interface LaunchOptions {
42
49
  containerCpu?: number;
43
50
  containerMemory?: number;
44
51
  containerDisk?: number;
52
+ /** --sandbox-profile: base | android[:17|21]; overrides config.sandboxProfile. */
53
+ sandboxProfile?: string;
45
54
  updateNotificationUrl?: string;
46
55
  updateNotificationNameTag?: string;
47
56
  /** Parent folder containing sibling repos to fan out over. */
@@ -79,6 +88,19 @@ export declare function validateKillFlags(options: LaunchOptions): string | null
79
88
  * `--kill` needs only the tracking name; every other mode needs a plan.
80
89
  */
81
90
  export declare function requiresPlanPath(options: LaunchOptions): boolean;
91
+ /**
92
+ * Validate the `--issue` / `--pr` / `--base` flag matrix.
93
+ *
94
+ * - A plan file may accompany `--issue` OR `--pr`, but not both — with both and
95
+ * a plan file there is no single instruction source. Without a plan file,
96
+ * `--issue` and `--pr` combine (plan generated from both).
97
+ * - `--base` is meaningless with `--pr` (the base comes from the PR itself).
98
+ * - `--issue` / `--pr` are single-repo launch flows, incompatible with the
99
+ * read/group/kill flags in {@link SOURCE_INCOMPATIBLE_FLAGS}.
100
+ *
101
+ * Returns an error message when the combination is invalid, or `null` when OK.
102
+ */
103
+ export declare function validateSourceFlags(options: LaunchOptions, hasPlanPathPositional: boolean): string | null;
82
104
  /**
83
105
  * Validate the multi-repo `--folder` / `--group-id` / `--plan` flag matrix.
84
106
  *
@@ -175,6 +197,8 @@ export interface RalphRunBodyInput {
175
197
  memory: number;
176
198
  disk: number;
177
199
  };
200
+ /** Non-base sandbox profile; omitted for base (the server default). */
201
+ sandboxProfile?: SandboxProfile;
178
202
  updateNotificationUrl?: string;
179
203
  updateNotificationNameTag?: string;
180
204
  githubToken?: string;
@@ -185,6 +209,18 @@ export interface RalphRunBodyInput {
185
209
  * branch in the sandbox instead of creating one from the base branch.
186
210
  */
187
211
  branchExists?: boolean;
212
+ /**
213
+ * Launch-from-existing linkage (optional; omitted when absent so legacy
214
+ * bodies stay byte-identical). An older server ignores unknown fields.
215
+ */
216
+ /** Existing GitHub issue to link (`Closes #N`). */
217
+ issueNumber?: number;
218
+ /** Existing pull request being continued. */
219
+ prNumber?: number;
220
+ /** The PR's head branch (the work branch; may contain `/`, unsanitized). */
221
+ headBranch?: string;
222
+ /** The PR's / --base target branch the PR opens into. */
223
+ baseBranch?: string;
188
224
  }
189
225
  /**
190
226
  * Parse a beforeLaunch hook's stdout into an envVars object to merge into the
@@ -1 +1 @@
1
- {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"launch.d.ts","sourceRoot":"","sources":["../../src/commands/launch.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,7 @@ import { GroupLaunchService, validateGroupId, } from '../services/group/GroupLau
14
14
  import { TempFileService } from '../services/logs/TempFileService.js';
15
15
  import { EditorService } from '../services/editor/EditorService.js';
16
16
  import { ensurePlanBranch, PlanNotFoundError, } from '../services/plan/PlanBranchService.js';
17
+ import { parseGitHubRef, findLinkedIssueNumber, resolveIssueSource, resolvePrSource, buildPlanMarkdown, writeGeneratedPlan, } from '../services/plan/GitHubSourcePlanService.js';
17
18
  import { loadGitHubToken } from './login.js';
18
19
  import { resolveProject } from '../utils/project-resolver.js';
19
20
  import { HULA_PROJECT_URL } from '../config/constants.js';
@@ -25,6 +26,7 @@ import { findGitRoot } from '../utils/git-utils.js';
25
26
  import { upsertProviderApiKey } from '../utils/config-file-edit.js';
26
27
  import { validateNoReservedVars, readAndFilterEnvVars, RESERVED_ENV_VARS, } from '../utils/env-vars.js';
27
28
  import { HookExecutor } from '../services/hooks/HookExecutor.js';
29
+ import { describeSandboxProfile, resolveSandboxProfile, sandboxProfileForRequest, } from '../utils/sandbox-profile.js';
28
30
  const DEFAULT_CONTAINER_RESOURCES = { cpu: 2, memory: 4, disk: 10 };
29
31
  /**
30
32
  * Launch command - Trigger a launch job on hula-project server
@@ -69,11 +71,15 @@ export function launchCommand(program, config) {
69
71
  // 'Run in test mode (server uses a mock Claude — fast E2E run, still creates a real PR)',
70
72
  // )
71
73
  .option('--handoff <username>', 'Assign the created issue to this GitHub user')
74
+ .option('--issue <ref>', 'Work on an existing GitHub issue (number, #number, or URL). Never creates a new issue. A single positional is the tracking name, not a plan path.')
75
+ .option('--pr <ref>', 'Continue an existing pull request in this repository (number, #number, or URL). Pushes to its branch. A single positional is the tracking name, not a plan path.')
76
+ .option('--base <branch>', 'Branch to cut the new work branch from and open the PR into (default: the repository default branch)')
72
77
  .option('--provider <type>', 'LLM provider: claude | openai | openrouter (default: config or claude)')
73
78
  .option('--provider-key <key>', 'Provider credential (ephemeral; never stored server-side beyond the run)')
74
79
  .option('--container-cpu <n>', 'vCPU cores for the sandbox (1–32)', parseInt)
75
80
  .option('--container-memory <n>', 'GiB RAM for the sandbox (1–128)', parseInt)
76
81
  .option('--container-disk <n>', 'GiB disk for the sandbox (5–200)', parseInt)
82
+ .option('--sandbox-profile <profile>', 'Sandbox image: base | android | android:17 | android:21 (default: config sandboxProfile, else base)')
77
83
  .option('--update-notification-url <url>', 'Webhook URL to POST a notification to on every terminal task transition')
78
84
  .option('--update-notification-name-tag <tag>', 'Verbatim "initiated by" label for notifications (Slack mention markup pings). Falls back to config then HULA_UPDATE_NOTIFICATION_NAME_TAG.')
79
85
  .option('--show <name>', 'Show job status for the specified issue')
@@ -121,6 +127,13 @@ export function launchCommand(program, config) {
121
127
  logger.error(folderFlagError);
122
128
  process.exit(1);
123
129
  }
130
+ // Validate the launch-from-existing `--issue` / `--pr` / `--base`
131
+ // flag combination before any work.
132
+ const sourceFlagError = validateSourceFlags(options, Boolean(planPath));
133
+ if (sourceFlagError) {
134
+ logger.error(sourceFlagError);
135
+ process.exit(1);
136
+ }
124
137
  // Validate an explicitly-supplied group id against the server format.
125
138
  if (typeof options.groupId === 'string') {
126
139
  const groupIdError = validateGroupId(options.groupId);
@@ -162,6 +175,13 @@ export function launchCommand(program, config) {
162
175
  await executeKill(config, issueName, options);
163
176
  return;
164
177
  }
178
+ // With --issue / --pr the plan (and tracking name) are derived from
179
+ // GitHub, so neither positional is required. A single positional is
180
+ // the tracking name; the plan path is generated.
181
+ if (options.issue || options.pr) {
182
+ await executeLaunch(config, issueName, planPath, options);
183
+ return;
184
+ }
165
185
  // Regular launch / --kill-and-relaunch requires issueName and planPath
166
186
  if (!issueName || !planPath) {
167
187
  logger.error('Usage: hula launch <issueName> <planPath>');
@@ -217,7 +237,52 @@ export function validateKillFlags(options) {
217
237
  * `--kill` needs only the tracking name; every other mode needs a plan.
218
238
  */
219
239
  export function requiresPlanPath(options) {
220
- return !options.kill;
240
+ return !options.kill && !options.issue && !options.pr;
241
+ }
242
+ /**
243
+ * Launch-only flags that cannot be combined with `--issue` / `--pr`. These are
244
+ * either read/group modes or the kill flows, none of which make sense when
245
+ * starting from an existing issue or PR (single-repo launch only).
246
+ */
247
+ const SOURCE_INCOMPATIBLE_FLAGS = [
248
+ { key: 'folder', flag: '--folder' },
249
+ { key: 'groupId', flag: '--group-id' },
250
+ { key: 'kill', flag: '--kill' },
251
+ { key: 'killAndRelaunch', flag: '--kill-and-relaunch' },
252
+ { key: 'show', flag: '--show' },
253
+ { key: 'logs', flag: '--logs' },
254
+ { key: 'showGroup', flag: '--show-group' },
255
+ ];
256
+ /**
257
+ * Validate the `--issue` / `--pr` / `--base` flag matrix.
258
+ *
259
+ * - A plan file may accompany `--issue` OR `--pr`, but not both — with both and
260
+ * a plan file there is no single instruction source. Without a plan file,
261
+ * `--issue` and `--pr` combine (plan generated from both).
262
+ * - `--base` is meaningless with `--pr` (the base comes from the PR itself).
263
+ * - `--issue` / `--pr` are single-repo launch flows, incompatible with the
264
+ * read/group/kill flags in {@link SOURCE_INCOMPATIBLE_FLAGS}.
265
+ *
266
+ * Returns an error message when the combination is invalid, or `null` when OK.
267
+ */
268
+ export function validateSourceFlags(options, hasPlanPathPositional) {
269
+ const hasIssue = Boolean(options.issue);
270
+ const hasPr = Boolean(options.pr);
271
+ if (hasPlanPathPositional && hasIssue && hasPr) {
272
+ return 'Pass a plan file with --issue or with --pr, not with both. Without a plan file, --issue and --pr can be combined.';
273
+ }
274
+ if (hasPr && options.base) {
275
+ return '--base cannot be used with --pr: the base branch comes from the pull request';
276
+ }
277
+ if (hasIssue || hasPr) {
278
+ for (const { key, flag } of SOURCE_INCOMPATIBLE_FLAGS) {
279
+ const value = options[key];
280
+ if (value !== undefined && value !== false) {
281
+ return `--issue/--pr cannot be combined with ${flag}`;
282
+ }
283
+ }
284
+ }
285
+ return null;
221
286
  }
222
287
  /**
223
288
  * Validate the multi-repo `--folder` / `--group-id` / `--plan` flag matrix.
@@ -720,6 +785,11 @@ export function buildRalphRunRequestBody(input) {
720
785
  if (input.containerResources) {
721
786
  body.containerResources = input.containerResources;
722
787
  }
788
+ // Maps to RalphRunRequest.sandboxProfile. Only non-base profiles are sent so
789
+ // base launches stay byte-identical (see sandboxProfileForRequest).
790
+ if (input.sandboxProfile) {
791
+ body.sandboxProfile = input.sandboxProfile;
792
+ }
723
793
  if (input.updateNotificationUrl) {
724
794
  body.updateNotificationUrl = input.updateNotificationUrl;
725
795
  }
@@ -737,8 +807,69 @@ export function buildRalphRunRequestBody(input) {
737
807
  if (input.branchExists) {
738
808
  body.branchExists = true;
739
809
  }
810
+ // Launch-from-existing linkage (appended after branchExists, each only when
811
+ // defined, so non-source launches stay byte-identical). An older server
812
+ // ignores these unknown fields.
813
+ if (input.issueNumber !== undefined) {
814
+ body.issueNumber = input.issueNumber;
815
+ }
816
+ if (input.prNumber !== undefined) {
817
+ body.prNumber = input.prNumber;
818
+ }
819
+ if (input.headBranch !== undefined) {
820
+ body.headBranch = input.headBranch;
821
+ }
822
+ if (input.baseBranch !== undefined) {
823
+ body.baseBranch = input.baseBranch;
824
+ }
740
825
  return body;
741
826
  }
827
+ /**
828
+ * True when `msg` is one of the deliberate validation errors thrown by
829
+ * {@link resolveIssueSource} / {@link resolvePrSource} (closed, merged, fork,
830
+ * wrong kind) — those must pass through verbatim, not be re-wrapped as a fetch
831
+ * failure.
832
+ */
833
+ function isGitHubSourceValidationError(msg) {
834
+ return /is a pull request, not an issue|is closed\. Reopen|is already merged|comes from a fork/.test(msg);
835
+ }
836
+ /**
837
+ * {@link resolveIssueSource} with the GitHub-fetch error mapping from the plan's
838
+ * edge cases: 404 → a clear "not found in <owner/repo>", any other fetch error
839
+ * → "Could not fetch issue #<n>: …". Validation errors pass through unchanged.
840
+ */
841
+ async function resolveIssueSourceWrapped(n, repo, repoFullName) {
842
+ try {
843
+ return await resolveIssueSource(n, repo);
844
+ }
845
+ catch (err) {
846
+ const msg = err instanceof Error ? err.message : String(err);
847
+ if (isGitHubSourceValidationError(msg))
848
+ throw err;
849
+ if (/404|not found/i.test(msg)) {
850
+ throw new Error(`Issue #${n} not found in ${repoFullName}`);
851
+ }
852
+ throw new Error(`Could not fetch issue #${n}: ${msg}`);
853
+ }
854
+ }
855
+ /**
856
+ * {@link resolvePrSource} with the same GitHub-fetch error mapping as
857
+ * {@link resolveIssueSourceWrapped}.
858
+ */
859
+ async function resolvePrSourceWrapped(n, repoFullName) {
860
+ try {
861
+ return await resolvePrSource(n);
862
+ }
863
+ catch (err) {
864
+ const msg = err instanceof Error ? err.message : String(err);
865
+ if (isGitHubSourceValidationError(msg))
866
+ throw err;
867
+ if (/404|not found/i.test(msg)) {
868
+ throw new Error(`PR #${n} not found in ${repoFullName}`);
869
+ }
870
+ throw new Error(`Could not fetch PR #${n}: ${msg}`);
871
+ }
872
+ }
742
873
  /**
743
874
  * Single-repo launch entry point. Runs the plan-sync/upload check, then defers
744
875
  * the body-build + POST to {@link launchSingleRepo} in single mode (keeps the
@@ -776,11 +907,121 @@ conflictModeOverride) {
776
907
  * @param options - Parsed CLI options.
777
908
  * @param ctx - Mode + shared-once context (see {@link SingleRepoLaunchContext}).
778
909
  */
779
- async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, options, ctx) {
910
+ async function launchSingleRepo(repoConfig, repoRoot, issueNameInput, planPathInput, options, ctx) {
780
911
  const isGroup = ctx.mode === 'group';
781
912
  // Effective conflict mode: an explicit override (from the 409 resolver) or the
782
913
  // --kill-and-relaunch flag. Undefined = default 'reject' (byte-identical body).
783
914
  const conflictMode = deriveConflictMode(options, ctx.conflictModeOverride);
915
+ // ── Launch-from-existing resolution (FR 8) ────────────────────────────────
916
+ // With --issue / --pr the tracking name and plan are derived from GitHub, and
917
+ // --base / --pr change the branch the plan is published to and the PR base.
918
+ // Resolve all of that here, before ensurePlanBranch, so the proven publish
919
+ // path is reused with overrides and the request body gains the linkage fields.
920
+ let resolvedIssueName = issueNameInput;
921
+ let resolvedPlanPath = planPathInput;
922
+ let planBranchOpts;
923
+ const sourceBody = {};
924
+ if (options.issue || options.pr || options.base) {
925
+ try {
926
+ const repoFullName = ctx.repositoryId ??
927
+ (await resolveProject(repoConfig, undefined, repoRoot));
928
+ const [repoOwner, repoName] = repoFullName.split('/');
929
+ // Reject a URL that points at a different repository than this one.
930
+ const assertSameRepo = (ref, kind) => {
931
+ if (ref.owner &&
932
+ ref.repo &&
933
+ (ref.owner.toLowerCase() !== (repoOwner ?? '').toLowerCase() ||
934
+ ref.repo.toLowerCase() !== (repoName ?? '').toLowerCase())) {
935
+ throw new Error(`--${kind} URL points to ${ref.owner}/${ref.repo}, but this repository is ${repoOwner}/${repoName}`);
936
+ }
937
+ };
938
+ let pr;
939
+ let issue;
940
+ if (options.pr) {
941
+ const prRef = parseGitHubRef(options.pr, 'pr');
942
+ assertSameRepo(prRef, 'pr');
943
+ pr = await resolvePrSourceWrapped(prRef.number, repoFullName);
944
+ // Linked issue: an explicit --issue wins; otherwise auto-detect from the
945
+ // PR body and drop a closed linked issue with a warning.
946
+ if (options.issue) {
947
+ const issueRef = parseGitHubRef(options.issue, 'issue');
948
+ assertSameRepo(issueRef, 'issue');
949
+ issue = await resolveIssueSourceWrapped(issueRef.number, { owner: issueRef.owner, name: issueRef.repo }, repoFullName);
950
+ }
951
+ else {
952
+ const linked = findLinkedIssueNumber(pr.body);
953
+ if (linked !== null) {
954
+ try {
955
+ issue = await resolveIssueSourceWrapped(linked, {}, repoFullName);
956
+ }
957
+ catch (linkedErr) {
958
+ const linkedMsg = linkedErr instanceof Error
959
+ ? linkedErr.message
960
+ : String(linkedErr);
961
+ if (linkedMsg.includes('is closed')) {
962
+ logger.warning(`Linked issue #${linked} is closed — continuing without linking it`);
963
+ }
964
+ else {
965
+ throw linkedErr;
966
+ }
967
+ }
968
+ }
969
+ }
970
+ }
971
+ else if (options.issue) {
972
+ const issueRef = parseGitHubRef(options.issue, 'issue');
973
+ assertSameRepo(issueRef, 'issue');
974
+ issue = await resolveIssueSourceWrapped(issueRef.number, { owner: issueRef.owner, name: issueRef.repo }, repoFullName);
975
+ }
976
+ // Tracking name: the positional if given, else pr-<n> / issue-<n>.
977
+ if (!resolvedIssueName) {
978
+ resolvedIssueName = pr
979
+ ? `pr-${pr.number}`
980
+ : issue
981
+ ? `issue-${issue.number}`
982
+ : resolvedIssueName;
983
+ }
984
+ // Plan path: the positional if given, else a generated plan from the
985
+ // GitHub text (only reachable with --issue / --pr; --base alone still
986
+ // requires a positional plan because requiresPlanPath stays true).
987
+ if (!resolvedPlanPath && (pr || issue)) {
988
+ const slug = pr ? `pr-${pr.number}` : `issue-${issue.number}`;
989
+ const markdown = buildPlanMarkdown({ issue, pr, repoFullName });
990
+ resolvedPlanPath = writeGeneratedPlan(repoConfig.planPath, slug, markdown, repoRoot);
991
+ logger.info(`Generated plan from ${pr ? `PR #${pr.number}` : `issue #${issue.number}`}: ${resolvedPlanPath}`);
992
+ }
993
+ // Branch overrides for ensurePlanBranch.
994
+ if (pr) {
995
+ planBranchOpts = { branch: pr.head.ref, requireExistingBranch: true };
996
+ }
997
+ else if (options.base) {
998
+ planBranchOpts = { baseBranch: options.base };
999
+ }
1000
+ // Request-body linkage fields.
1001
+ if (pr) {
1002
+ sourceBody.prNumber = pr.number;
1003
+ sourceBody.headBranch = pr.head.ref;
1004
+ sourceBody.baseBranch = pr.base.ref;
1005
+ }
1006
+ else if (options.base) {
1007
+ sourceBody.baseBranch = options.base;
1008
+ }
1009
+ if (issue) {
1010
+ sourceBody.issueNumber = issue.number;
1011
+ }
1012
+ }
1013
+ catch (sourceErr) {
1014
+ const msg = sourceErr instanceof Error ? sourceErr.message : String(sourceErr);
1015
+ logger.error(msg);
1016
+ if (isGroup)
1017
+ return { ok: false, message: msg };
1018
+ process.exit(1);
1019
+ }
1020
+ }
1021
+ // After resolution both are guaranteed: the handler requires positionals
1022
+ // except with --issue / --pr, which populate them above.
1023
+ const issueName = resolvedIssueName;
1024
+ const planPath = resolvedPlanPath;
784
1025
  // Plan-branch flow: guarantee the feature branch exists on origin with the
785
1026
  // current plan content BEFORE anything else — the beforeLaunch hook below
786
1027
  // may register branch-scoped external resources (e.g. Vercel Preview env
@@ -791,7 +1032,7 @@ async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, optio
791
1032
  // that only exist on origin/main.
792
1033
  let planBranch;
793
1034
  try {
794
- const ensured = await ensurePlanBranch(repoConfig, planPath, issueName, repoRoot);
1035
+ const ensured = await ensurePlanBranch(repoConfig, planPath, issueName, repoRoot, planBranchOpts);
795
1036
  planBranch = ensured.branch;
796
1037
  if (ensured.created) {
797
1038
  logger.success(`Branch '${ensured.branch}' created on origin with the plan`);
@@ -1009,6 +1250,18 @@ async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, optio
1009
1250
  containerResources = { cpu, memory, disk };
1010
1251
  logger.log(` Container Resources: cpu=${cpu} memory=${memory}GiB disk=${disk}GiB`);
1011
1252
  }
1253
+ // Resolve the sandbox profile: --sandbox-profile, then config, then base.
1254
+ const profileResult = resolveSandboxProfile(options.sandboxProfile, repoConfig.sandboxProfile);
1255
+ if (profileResult.error) {
1256
+ logger.error(profileResult.error);
1257
+ if (isGroup)
1258
+ return { ok: false, message: profileResult.error };
1259
+ process.exit(1);
1260
+ }
1261
+ const sandboxProfile = sandboxProfileForRequest(profileResult.profile);
1262
+ if (sandboxProfile) {
1263
+ logger.log(` Sandbox Profile: ${describeSandboxProfile(sandboxProfile)}`);
1264
+ }
1012
1265
  // Resolve the GitHub token: shared (group mode) or loaded (single mode).
1013
1266
  const githubToken = ctx.skipTokenLoad
1014
1267
  ? ctx.githubToken
@@ -1124,6 +1377,7 @@ async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, optio
1124
1377
  clientSessionId: clientSessionId ?? undefined,
1125
1378
  provider: { type: providerType, authToken },
1126
1379
  containerResources,
1380
+ sandboxProfile,
1127
1381
  updateNotificationUrl,
1128
1382
  updateNotificationNameTag,
1129
1383
  githubToken,
@@ -1132,6 +1386,11 @@ async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, optio
1132
1386
  // plan committed) exists on origin, so the server must check out the
1133
1387
  // existing branch instead of creating one from the base branch.
1134
1388
  branchExists: true,
1389
+ // Launch-from-existing linkage (undefined unless --issue / --pr / --base).
1390
+ issueNumber: sourceBody.issueNumber,
1391
+ prNumber: sourceBody.prNumber,
1392
+ headBranch: sourceBody.headBranch,
1393
+ baseBranch: sourceBody.baseBranch,
1135
1394
  });
1136
1395
  try {
1137
1396
  const response = await axios.post(endpoint, requestBody, {
@@ -1149,13 +1408,15 @@ async function launchSingleRepo(repoConfig, repoRoot, issueName, planPath, optio
1149
1408
  }
1150
1409
  logger.success(response.data.message);
1151
1410
  logger.blank();
1411
+ // Link to the dashboard of the server we launched on, so a preview or
1412
+ // self-hosted launch is not sent to hublaunch.site.
1413
+ const dashboardUrl = `${serverUrl.replace(/\/$/, '')}/dashboard`;
1152
1414
  if (response.data.planId) {
1153
- const planUrl = `https://www.hublaunch.site/dashboard/plans/${response.data.planId}`;
1154
1415
  logger.info(`Plan ID: ${response.data.planId}`);
1155
- logger.info(`Track it: ${planUrl}`);
1416
+ logger.info(`Track it: ${dashboardUrl}/plans/${response.data.planId}`);
1156
1417
  }
1157
1418
  else {
1158
- logger.info('Track it: https://www.hublaunch.site/dashboard');
1419
+ logger.info(`Track it: ${dashboardUrl}`);
1159
1420
  }
1160
1421
  if (!updateNotificationUrl) {
1161
1422
  logger.info('Set updateNotificationUrl in your local hublaunch.config.js file to be able to get update messages about the status.');