@proletariat/cli 0.3.9 → 0.3.11

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 (152) hide show
  1. package/README.md +25 -0
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/action/index.js +1 -1
  4. package/dist/commands/action/run.js +8 -12
  5. package/dist/commands/agent/auth.d.ts +30 -0
  6. package/dist/commands/agent/auth.js +172 -0
  7. package/dist/commands/agent/discover.d.ts +9 -0
  8. package/dist/commands/agent/discover.js +67 -0
  9. package/dist/commands/agent/index.js +47 -12
  10. package/dist/commands/agent/list.d.ts +4 -1
  11. package/dist/commands/agent/list.js +78 -16
  12. package/dist/commands/agent/login.js +35 -31
  13. package/dist/commands/agent/restart.js +2 -0
  14. package/dist/commands/agent/shell.js +78 -19
  15. package/dist/commands/agent/staff/add.js +1 -12
  16. package/dist/commands/agent/staff/remove.js +9 -7
  17. package/dist/commands/agent/status.js +17 -4
  18. package/dist/commands/agent/temp/cleanup.js +7 -3
  19. package/dist/commands/agent/themes/index.js +4 -5
  20. package/dist/commands/agent/themes/list.js +5 -5
  21. package/dist/commands/agent/visit.js +17 -4
  22. package/dist/commands/branch/create.d.ts +4 -0
  23. package/dist/commands/branch/create.js +16 -8
  24. package/dist/commands/branch/index.js +1 -1
  25. package/dist/commands/branch/where.js +1 -0
  26. package/dist/commands/claude.d.ts +38 -0
  27. package/dist/commands/claude.js +899 -0
  28. package/dist/commands/commit.js +1 -1
  29. package/dist/commands/config/index.d.ts +12 -0
  30. package/dist/commands/config/index.js +271 -0
  31. package/dist/commands/docker/clean.js +2 -2
  32. package/dist/commands/docker/index.js +2 -2
  33. package/dist/commands/docker/list.js +3 -8
  34. package/dist/commands/docker/logs.js +2 -2
  35. package/dist/commands/docker/prune.js +1 -1
  36. package/dist/commands/docker/restart.js +2 -2
  37. package/dist/commands/docker/shell.js +2 -2
  38. package/dist/commands/docker/start.js +2 -2
  39. package/dist/commands/docker/status.js +1 -1
  40. package/dist/commands/docker/stop.js +2 -2
  41. package/dist/commands/docker/sync.js +2 -2
  42. package/dist/commands/epic/index.js +1 -1
  43. package/dist/commands/epic/link/index.js +25 -14
  44. package/dist/commands/epic/link/remove.js +2 -0
  45. package/dist/commands/epic/list.js +5 -5
  46. package/dist/commands/epic/progress.js +10 -4
  47. package/dist/commands/epic/spec.js +2 -0
  48. package/dist/commands/epic/ticket.js +3 -0
  49. package/dist/commands/execution/stop.js +1 -0
  50. package/dist/commands/init.js +4 -4
  51. package/dist/commands/project/index.js +1 -1
  52. package/dist/commands/project/spec.js +7 -0
  53. package/dist/commands/repo/add.js +1 -0
  54. package/dist/commands/repo/remove.js +1 -0
  55. package/dist/commands/roadmap/add-project.d.ts +18 -0
  56. package/dist/commands/roadmap/add-project.js +135 -0
  57. package/dist/commands/roadmap/create.d.ts +22 -0
  58. package/dist/commands/roadmap/create.js +156 -0
  59. package/dist/commands/roadmap/delete.d.ts +17 -0
  60. package/dist/commands/roadmap/delete.js +104 -0
  61. package/dist/commands/roadmap/generate.d.ts +22 -0
  62. package/dist/commands/roadmap/generate.js +201 -0
  63. package/dist/commands/roadmap/index.d.ts +13 -0
  64. package/dist/commands/roadmap/index.js +61 -0
  65. package/dist/commands/roadmap/list.d.ts +12 -0
  66. package/dist/commands/roadmap/list.js +42 -0
  67. package/dist/commands/roadmap/remove-project.d.ts +18 -0
  68. package/dist/commands/roadmap/remove-project.js +147 -0
  69. package/dist/commands/roadmap/reorder.d.ts +17 -0
  70. package/dist/commands/roadmap/reorder.js +157 -0
  71. package/dist/commands/roadmap/update.d.ts +19 -0
  72. package/dist/commands/roadmap/update.js +136 -0
  73. package/dist/commands/roadmap/view.d.ts +16 -0
  74. package/dist/commands/roadmap/view.js +103 -0
  75. package/dist/commands/spec/index.js +1 -1
  76. package/dist/commands/spec/link/index.js +24 -13
  77. package/dist/commands/spec/link/remove.js +2 -0
  78. package/dist/commands/status/index.js +1 -1
  79. package/dist/commands/status/list.js +0 -8
  80. package/dist/commands/template/delete.js +2 -0
  81. package/dist/commands/terminal/title.d.ts +12 -0
  82. package/dist/commands/terminal/title.js +48 -0
  83. package/dist/commands/ticket/complete.js +2 -0
  84. package/dist/commands/ticket/create.js +4 -2
  85. package/dist/commands/ticket/delete.js +2 -0
  86. package/dist/commands/ticket/edit.js +8 -2
  87. package/dist/commands/ticket/link/index.js +17 -3
  88. package/dist/commands/ticket/link/remove.js +2 -0
  89. package/dist/commands/ticket/list.js +1 -2
  90. package/dist/commands/ticket/move.js +2 -0
  91. package/dist/commands/ticket/project.js +3 -1
  92. package/dist/commands/ticket/reassign.js +2 -0
  93. package/dist/commands/ticket/spec.js +4 -2
  94. package/dist/commands/ticket/template/apply.js +4 -3
  95. package/dist/commands/ticket/template/create.js +2 -0
  96. package/dist/commands/ticket/template/index.js +1 -1
  97. package/dist/commands/ticket/update.js +2 -0
  98. package/dist/commands/work/index.js +1 -1
  99. package/dist/commands/work/revise.js +7 -1
  100. package/dist/commands/work/spawn.d.ts +2 -1
  101. package/dist/commands/work/spawn.js +131 -36
  102. package/dist/commands/work/start.d.ts +2 -1
  103. package/dist/commands/work/start.js +349 -69
  104. package/dist/commands/work/watch.js +10 -2
  105. package/dist/commands/workflow/create.js +3 -3
  106. package/dist/commands/workflow/switch.js +2 -1
  107. package/dist/commands/workspace/remove.js +0 -8
  108. package/dist/commands/workspace/use.js +1 -9
  109. package/dist/lib/agents/commands.js +18 -13
  110. package/dist/lib/database/index.d.ts +19 -12
  111. package/dist/lib/database/index.js +158 -42
  112. package/dist/lib/docker/resolve.js +1 -1
  113. package/dist/lib/execution/config.d.ts +6 -0
  114. package/dist/lib/execution/config.js +15 -2
  115. package/dist/lib/execution/devcontainer.d.ts +2 -0
  116. package/dist/lib/execution/devcontainer.js +41 -9
  117. package/dist/lib/execution/runners.d.ts +85 -3
  118. package/dist/lib/execution/runners.js +925 -228
  119. package/dist/lib/execution/spawner.d.ts +2 -2
  120. package/dist/lib/execution/spawner.js +4 -3
  121. package/dist/lib/execution/storage.d.ts +2 -1
  122. package/dist/lib/execution/storage.js +9 -13
  123. package/dist/lib/execution/types.d.ts +10 -1
  124. package/dist/lib/execution/types.js +3 -1
  125. package/dist/lib/init/index.js +1 -0
  126. package/dist/lib/machine-config.js +1 -1
  127. package/dist/lib/pmo/base-command.js +5 -9
  128. package/dist/lib/pmo/index.js +2 -0
  129. package/dist/lib/pmo/schema.d.ts +6 -0
  130. package/dist/lib/pmo/schema.js +36 -0
  131. package/dist/lib/pmo/storage/base.js +3 -3
  132. package/dist/lib/pmo/storage/index.d.ts +16 -1
  133. package/dist/lib/pmo/storage/index.js +45 -0
  134. package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
  135. package/dist/lib/pmo/storage/roadmaps.js +301 -0
  136. package/dist/lib/pmo/storage/specs.js +2 -0
  137. package/dist/lib/pmo/storage/types.d.ts +14 -0
  138. package/dist/lib/pmo/sync-manager.d.ts +1 -1
  139. package/dist/lib/pmo/sync-manager.js +1 -1
  140. package/dist/lib/pmo/types.d.ts +41 -0
  141. package/dist/lib/pmo/utils.d.ts +2 -0
  142. package/dist/lib/pmo/utils.js +22 -1
  143. package/dist/lib/repos/index.js +7 -1
  144. package/dist/lib/terminal.d.ts +31 -0
  145. package/dist/lib/terminal.js +48 -0
  146. package/dist/lib/themes.d.ts +21 -3
  147. package/dist/lib/themes.js +80 -23
  148. package/dist/lib/workspace-config.d.ts +80 -0
  149. package/dist/lib/workspace-config.js +100 -0
  150. package/oclif.manifest.json +4065 -3225
  151. package/package.json +10 -6
  152. package/LICENSE +0 -21
@@ -8,7 +8,6 @@ export default class WorkStart extends PMOCommand {
8
8
  static flags: {
9
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
10
  all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
- mode: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
11
  executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
12
  action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
13
  prompt: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -17,6 +16,7 @@ export default class WorkStart extends PMOCommand {
17
16
  'vm-host': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
17
  'run-on-host': import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
18
  reconfigure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ 'permission-mode': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
20
  'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
21
  'create-pr': import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
22
  'no-pr': import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -25,6 +25,7 @@ export default class WorkStart extends PMOCommand {
25
25
  session: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
26
26
  agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
27
27
  ephemeral: import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
+ focus: import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
29
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
29
30
  };
30
31
  execute(): Promise<void>;
@@ -5,12 +5,12 @@ import { execSync } from 'node:child_process';
5
5
  import inquirer from 'inquirer';
6
6
  import Database from 'better-sqlite3';
7
7
  import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
8
- import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
8
+ import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
9
9
  import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
10
10
  import { styles } from '../../lib/styles.js';
11
11
  import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxSession, } from '../../lib/agents/commands.js';
12
12
  import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
13
- import { runExecution, isDockerRunning } from '../../lib/execution/runners.js';
13
+ import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo } from '../../lib/execution/runners.js';
14
14
  import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
15
15
  import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getOrPromptCoderName } from '../../lib/execution/config.js';
16
16
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
@@ -92,11 +92,6 @@ export default class WorkStart extends PMOCommand {
92
92
  description: 'Start work on all unassigned backlog tickets (batch mode)',
93
93
  default: false,
94
94
  }),
95
- mode: Flags.string({
96
- char: 'm',
97
- description: 'Runtime mode',
98
- options: ['foreground', 'background', 'tmux', 'terminal', 'devcontainer', 'docker', 'vm'],
99
- }),
100
95
  executor: Flags.string({
101
96
  char: 'e',
102
97
  description: 'Override executor',
@@ -131,8 +126,12 @@ export default class WorkStart extends PMOCommand {
131
126
  description: 'Re-prompt for terminal app preference',
132
127
  default: false,
133
128
  }),
129
+ 'permission-mode': Flags.string({
130
+ description: 'Permission mode for Claude Code (danger=skip checks, safe=require approval)',
131
+ options: ['danger', 'safe'],
132
+ }),
134
133
  'skip-permissions': Flags.boolean({
135
- description: 'Skip permission prompts (danger mode)',
134
+ description: 'Skip permission checks (shorthand for --permission-mode danger)',
136
135
  default: false,
137
136
  }),
138
137
  'create-pr': Flags.boolean({
@@ -150,8 +149,8 @@ export default class WorkStart extends PMOCommand {
150
149
  }),
151
150
  display: Flags.string({
152
151
  char: 'd',
153
- description: 'Display mode for devcontainer (where to show output)',
154
- options: ['terminal', 'background'],
152
+ description: 'Display mode (foreground=current terminal, terminal=new tab, background=detached)',
153
+ options: ['foreground', 'terminal', 'background'],
155
154
  }),
156
155
  session: Flags.string({
157
156
  char: 's',
@@ -166,10 +165,24 @@ export default class WorkStart extends PMOCommand {
166
165
  description: 'Create an ephemeral agent on-demand (auto-generates name)',
167
166
  default: false,
168
167
  }),
168
+ focus: Flags.boolean({
169
+ description: 'Bring terminal to foreground when opening new tabs (default: opens in background)',
170
+ default: false,
171
+ }),
169
172
  };
170
173
  async execute() {
171
174
  const { args, flags } = await this.parse(WorkStart);
172
175
  const projectId = flags.project;
176
+ // Handle --skip-permissions flag (alias for --permission-mode danger)
177
+ // Check for conflicting flags first
178
+ if (flags['skip-permissions'] && flags['permission-mode']) {
179
+ this.error('Cannot use both --skip-permissions and --permission-mode flags.\n' +
180
+ 'Use only one: --skip-permissions OR --permission-mode danger/safe');
181
+ }
182
+ // Apply --skip-permissions as --permission-mode danger
183
+ if (flags['skip-permissions']) {
184
+ flags['permission-mode'] = 'danger';
185
+ }
173
186
  // Check if JSON output mode is active
174
187
  const jsonMode = shouldOutputJson(flags);
175
188
  // Helper to handle errors in JSON mode
@@ -394,7 +407,7 @@ export default class WorkStart extends PMOCommand {
394
407
  // At this point agentName is guaranteed to be set
395
408
  const assignedAgent = agentName;
396
409
  // Validate agent - for non-ephemeral agents, check if it exists in workspace
397
- let agentInfo = workspaceInfo.agents.find((a) => a.name === assignedAgent);
410
+ const agentInfo = workspaceInfo.agents.find((a) => a.name === assignedAgent);
398
411
  if (!isEphemeralAgent && !agentInfo) {
399
412
  db.close();
400
413
  this.error(`Agent "${assignedAgent}" not found in workspace.\n` +
@@ -576,6 +589,7 @@ export default class WorkStart extends PMOCommand {
576
589
  }
577
590
  actionChoices.push(new inquirer.Separator('── Custom ──'));
578
591
  actionChoices.push({ name: 'Custom prompt...', value: '__custom__' });
592
+ actionChoices.push({ name: 'Ad-hoc session - unstructured exploration/debugging', value: '__adhoc__' });
579
593
  const { selectedActionId } = await inquirer.prompt([
580
594
  {
581
595
  type: 'list',
@@ -595,6 +609,19 @@ export default class WorkStart extends PMOCommand {
595
609
  ]);
596
610
  customPrompt = customInput.trim();
597
611
  }
612
+ else if (selectedActionId === '__adhoc__') {
613
+ // Ad-hoc session - no specific action, just launch Claude for exploration
614
+ selectedAction = {
615
+ id: 'adhoc',
616
+ name: 'Ad-hoc',
617
+ description: 'Unstructured exploration and debugging',
618
+ prompt: 'You are working on an ad-hoc session for exploration and debugging. Help the user with whatever they need.',
619
+ modifiesCode: false,
620
+ defaultMoveToCategory: 'started',
621
+ isBuiltin: false,
622
+ createdAt: new Date(),
623
+ };
624
+ }
598
625
  else {
599
626
  selectedAction = await this.storage.getAction(selectedActionId);
600
627
  }
@@ -635,22 +662,37 @@ export default class WorkStart extends PMOCommand {
635
662
  let environment = 'host';
636
663
  let displayMode = 'terminal';
637
664
  let sandboxed = false; // Whether --dangerously-skip-permissions is NOT used
638
- if (hasDevcontainer && !flags.mode && !flags['run-on-host']) {
665
+ if (hasDevcontainer && !flags.display && !flags['run-on-host']) {
639
666
  // Agent has devcontainer - prompt for environment choice
667
+ // Check devcontainer prerequisites upfront
668
+ const dockerRunning = isDockerRunning();
669
+ const devcontainerCliInstalled = isDevcontainerCliInstalled();
670
+ const devcontainerReady = dockerRunning && devcontainerCliInstalled;
671
+ // Build missing requirements message for devcontainer option
672
+ let devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
673
+ if (!devcontainerReady) {
674
+ const missing = [];
675
+ if (!dockerRunning)
676
+ missing.push('Docker');
677
+ if (!devcontainerCliInstalled)
678
+ missing.push('devcontainer CLI');
679
+ devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
680
+ }
640
681
  // Loop to allow re-selection if Docker isn't running
641
682
  let environmentSelected = false;
642
683
  while (!environmentSelected) {
684
+ // eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
643
685
  const { selectedEnvironment } = await inquirer.prompt([
644
686
  {
645
687
  type: 'list',
646
688
  name: 'selectedEnvironment',
647
689
  message: 'Where should the agent run?',
648
690
  choices: [
649
- { name: '🐳 devcontainer (sandboxed, recommended)', value: 'devcontainer' },
691
+ { name: devcontainerLabel, value: 'devcontainer', disabled: !devcontainerReady },
650
692
  { name: '💻 host (runs directly on your machine)', value: 'host' },
651
693
  { name: '✗ cancel', value: 'cancel' },
652
694
  ],
653
- default: 'devcontainer',
695
+ default: devcontainerReady ? 'devcontainer' : 'host',
654
696
  },
655
697
  ]);
656
698
  if (selectedEnvironment === 'cancel') {
@@ -659,7 +701,7 @@ export default class WorkStart extends PMOCommand {
659
701
  return;
660
702
  }
661
703
  if (selectedEnvironment === 'devcontainer') {
662
- // Check Docker is running before proceeding with devcontainer
704
+ // Double-check prerequisites (in case user retried after starting Docker)
663
705
  if (!isDockerRunning()) {
664
706
  this.log('');
665
707
  this.warn('Docker is not running.\n' +
@@ -668,8 +710,74 @@ export default class WorkStart extends PMOCommand {
668
710
  this.log('');
669
711
  continue; // Re-prompt for environment selection
670
712
  }
713
+ // Check devcontainer CLI is installed
714
+ if (!isDevcontainerCliInstalled()) {
715
+ this.log('');
716
+ this.warn('devcontainer CLI is not installed.\n' +
717
+ 'Install with: npm install -g @devcontainers/cli\n' +
718
+ 'Or select "host" to run directly on your machine.');
719
+ this.log('');
720
+ continue; // Re-prompt for environment selection
721
+ }
722
+ // Check GitHub token is available for git push operations
723
+ if (!isGitHubTokenAvailable()) {
724
+ const tokenChoices = [
725
+ { name: 'Yes, continue anyway (git push may fail)', value: 'continue' },
726
+ { name: 'No, let me run gh auth login first', value: 'cancel' },
727
+ { name: 'Switch to host mode instead', value: 'host' },
728
+ ];
729
+ const tokenMessage = 'GitHub token not found. Git push may fail. Continue without token?';
730
+ if (jsonMode) {
731
+ outputPromptAsJson(buildPromptConfig('list', 'tokenAction', tokenMessage, tokenChoices), createMetadata('work start', flags));
732
+ db.close();
733
+ return;
734
+ }
735
+ this.log('');
736
+ this.warn('GitHub token not found.\n' +
737
+ 'Git push operations may fail inside the container.\n' +
738
+ 'Run `gh auth login` to authenticate, or continue without token.');
739
+ this.log('');
740
+ // eslint-disable-next-line no-await-in-loop -- Interactive user prompt in loop
741
+ const { tokenAction } = await inquirer.prompt([
742
+ {
743
+ type: 'list',
744
+ name: 'tokenAction',
745
+ message: tokenMessage,
746
+ choices: tokenChoices,
747
+ default: 'continue',
748
+ },
749
+ ]);
750
+ if (tokenAction === 'cancel') {
751
+ db.close();
752
+ this.log(styles.muted('Run `gh auth login` and try again.'));
753
+ return;
754
+ }
755
+ if (tokenAction === 'host') {
756
+ environment = 'host';
757
+ // Skip to host mode prompts
758
+ // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after user selection
759
+ const { selectedDisplay } = await inquirer.prompt([
760
+ {
761
+ type: 'list',
762
+ name: 'selectedDisplay',
763
+ message: 'How should the agent output be displayed?',
764
+ choices: [
765
+ { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
766
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
767
+ { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
768
+ ],
769
+ default: 'terminal',
770
+ },
771
+ ]);
772
+ displayMode = selectedDisplay;
773
+ environmentSelected = true;
774
+ continue;
775
+ }
776
+ // tokenAction === 'continue' - fall through to devcontainer setup
777
+ }
671
778
  environment = 'devcontainer';
672
779
  // Pick display mode for devcontainer
780
+ // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
673
781
  const { selectedDisplay } = await inquirer.prompt([
674
782
  {
675
783
  type: 'list',
@@ -677,6 +785,7 @@ export default class WorkStart extends PMOCommand {
677
785
  message: 'How should the agent output be displayed?',
678
786
  choices: [
679
787
  { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
788
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
680
789
  { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
681
790
  ],
682
791
  default: 'terminal',
@@ -689,6 +798,7 @@ export default class WorkStart extends PMOCommand {
689
798
  else {
690
799
  // User chose host
691
800
  environment = 'host';
801
+ // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
692
802
  const { selectedDisplay } = await inquirer.prompt([
693
803
  {
694
804
  type: 'list',
@@ -696,6 +806,7 @@ export default class WorkStart extends PMOCommand {
696
806
  message: 'How should the agent output be displayed?',
697
807
  choices: [
698
808
  { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
809
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
699
810
  { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
700
811
  ],
701
812
  default: 'terminal',
@@ -707,15 +818,11 @@ export default class WorkStart extends PMOCommand {
707
818
  }
708
819
  }
709
820
  else if (useDevcontainer) {
710
- // Devcontainer with explicit mode flag
821
+ // Devcontainer with explicit display flag
711
822
  environment = 'devcontainer';
712
- // Use --display flag if provided, otherwise fall back to --mode or default to 'terminal'
713
823
  if (flags.display) {
714
824
  displayMode = flags.display;
715
825
  }
716
- else if (flags.mode && ['terminal', 'background'].includes(flags.mode)) {
717
- displayMode = flags.mode;
718
- }
719
826
  else {
720
827
  // Default to terminal for devcontainer (opens new tab instead of blocking current terminal)
721
828
  displayMode = 'terminal';
@@ -723,27 +830,14 @@ export default class WorkStart extends PMOCommand {
723
830
  }
724
831
  else {
725
832
  // No devcontainer or --run-on-host - host mode selection
726
- if (flags.mode) {
727
- const flagMode = flags.mode;
728
- // Set environment based on mode flag
729
- if (flagMode === 'docker') {
730
- environment = 'docker';
731
- displayMode = 'terminal';
732
- }
733
- else if (flagMode === 'vm') {
734
- environment = 'vm';
735
- displayMode = 'terminal';
736
- }
737
- else {
738
- // Host environment: terminal/background are display modes
739
- environment = 'host';
740
- displayMode = flagMode;
741
- }
833
+ environment = 'host';
834
+ if (flags.display) {
835
+ displayMode = flags.display;
742
836
  }
743
837
  else {
744
838
  const warningMsg = flags['run-on-host']
745
- ? 'Select execution mode (--run-on-host: bypassing devcontainer):'
746
- : 'Select execution mode (no devcontainer - running on host):';
839
+ ? 'Select display mode (--run-on-host: bypassing devcontainer):'
840
+ : 'Select display mode (no devcontainer - running on host):';
747
841
  const { selectedMode } = await inquirer.prompt([
748
842
  {
749
843
  type: 'list',
@@ -751,53 +845,127 @@ export default class WorkStart extends PMOCommand {
751
845
  message: warningMsg,
752
846
  choices: [
753
847
  { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
848
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
754
849
  { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
755
- new inquirer.Separator('── Sandboxed (requires setup) ──'),
756
- { name: '🐳 Docker - Container with worktree mounted', value: 'docker' },
757
- new inquirer.Separator('── Remote ──'),
758
- { name: '☁️ VM - Remote VM via SSH', value: 'vm' },
759
850
  ],
760
851
  default: 'terminal',
761
852
  },
762
853
  ]);
763
- // Set environment based on selection
764
- if (selectedMode === 'docker') {
765
- environment = 'docker';
766
- displayMode = 'terminal';
767
- }
768
- else if (selectedMode === 'vm') {
769
- environment = 'vm';
770
- displayMode = 'terminal';
771
- }
772
- else {
773
- // Host environment: terminal/background are display modes
774
- environment = 'host';
775
- displayMode = selectedMode;
776
- }
854
+ displayMode = selectedMode;
777
855
  }
778
856
  }
779
857
  const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
780
858
  // Default to interactive output mode (streaming UI)
781
859
  // Can be overridden via --output flag if needed
782
- let outputMode = flags.output || DEFAULT_EXECUTION_CONFIG.outputMode;
860
+ const outputMode = flags.output || DEFAULT_EXECUTION_CONFIG.outputMode;
861
+ // Check Docker credentials for devcontainer environment
862
+ if (environment === 'devcontainer') {
863
+ const hasCredentials = dockerCredentialsExist();
864
+ if (!hasCredentials) {
865
+ this.log('');
866
+ this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
867
+ this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
868
+ this.log('');
869
+ const { authAction } = await inquirer.prompt([
870
+ {
871
+ type: 'list',
872
+ name: 'authAction',
873
+ message: 'What would you like to do?',
874
+ choices: [
875
+ { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
876
+ { name: '💻 Switch to host environment instead', value: 'host' },
877
+ { name: '⏩ Continue anyway (must run /login in first agent)', value: 'continue' },
878
+ { name: '✗ Cancel', value: 'cancel' },
879
+ ],
880
+ },
881
+ ]);
882
+ if (authAction === 'cancel') {
883
+ db.close();
884
+ this.log(styles.muted('Cancelled.'));
885
+ return;
886
+ }
887
+ if (authAction === 'host') {
888
+ environment = 'host';
889
+ this.log(styles.muted('Switched to host environment.'));
890
+ }
891
+ else if (authAction === 'auth') {
892
+ this.log('');
893
+ this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
894
+ this.log('');
895
+ // Open auth in a new terminal tab
896
+ const authCmd = `${process.argv[1]} agent auth`;
897
+ try {
898
+ execSync(`osascript -e '
899
+ tell application "iTerm"
900
+ tell current window
901
+ create tab with default profile
902
+ tell current session
903
+ write text "${authCmd}"
904
+ end tell
905
+ end tell
906
+ end tell
907
+ '`);
908
+ }
909
+ catch {
910
+ // Fallback: try Terminal.app
911
+ try {
912
+ execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
913
+ }
914
+ catch {
915
+ this.log(styles.warning('Could not open new terminal tab.'));
916
+ this.log(styles.muted(`Please run manually: ${authCmd}`));
917
+ }
918
+ }
919
+ this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
920
+ this.log('');
921
+ // Wait for user to complete auth
922
+ await inquirer.prompt([{
923
+ type: 'input',
924
+ name: 'done',
925
+ message: 'Press Enter when authentication is complete:',
926
+ }]);
927
+ // Check if credentials now exist
928
+ if (!dockerCredentialsExist()) {
929
+ this.log('');
930
+ this.log(styles.warning('Authentication did not complete. No credentials found.'));
931
+ db.close();
932
+ return;
933
+ }
934
+ const info = getDockerCredentialInfo();
935
+ this.log('');
936
+ this.log(styles.success('✓ Credentials configured'));
937
+ if (info) {
938
+ this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
939
+ this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
940
+ }
941
+ this.log('');
942
+ }
943
+ // authAction === 'continue' falls through
944
+ }
945
+ }
783
946
  // Prompt for permissions mode (all environments)
784
- // Skip prompt if --skip-permissions flag is set
785
- if (flags['skip-permissions']) {
786
- sandboxed = false;
947
+ // Skip prompt if --permission-mode flag is set
948
+ if (flags['permission-mode']) {
949
+ sandboxed = flags['permission-mode'] === 'safe';
787
950
  }
788
951
  else {
789
- const containerNote = (environment === 'devcontainer' || environment === 'docker')
952
+ const containerNote = environment === 'devcontainer'
790
953
  ? ' (container provides additional isolation)'
791
954
  : '';
955
+ const permissionChoices = [
956
+ { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger', command: `prlt work start ${ticketId} --skip-permissions` },
957
+ { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe' },
958
+ ];
959
+ // Handle JSON mode
960
+ if (jsonMode) {
961
+ outputPromptAsJson(buildPromptConfig('list', 'permissionMode', `Permission mode for Claude Code${containerNote}:`, permissionChoices, 'danger'), createMetadata('work start', flags));
962
+ }
792
963
  const { permissionMode } = await inquirer.prompt([
793
964
  {
794
965
  type: 'list',
795
966
  name: 'permissionMode',
796
967
  message: `Permission mode for Claude Code${containerNote}:`,
797
- choices: [
798
- { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
799
- { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe' },
800
- ],
968
+ choices: permissionChoices,
801
969
  default: 'danger',
802
970
  },
803
971
  ]);
@@ -836,7 +1004,7 @@ export default class WorkStart extends PMOCommand {
836
1004
  this.log(styles.muted(` Action: ${context.actionName || 'None'}`));
837
1005
  this.log(styles.muted(` Executor: ${executor}`));
838
1006
  // Environment info
839
- const envIcon = environment === 'devcontainer' ? '🐳' : (environment === 'docker' ? '📦' : '💻');
1007
+ const envIcon = environment === 'devcontainer' ? '🐳' : '💻';
840
1008
  this.log(styles.muted(` Environment: ${envIcon} ${environment}`));
841
1009
  this.log(styles.muted(` Display: ${displayMode}`));
842
1010
  // Permissions info
@@ -1069,6 +1237,10 @@ export default class WorkStart extends PMOCommand {
1069
1237
  executionConfig.outputMode = outputMode;
1070
1238
  // Set sandboxed mode (determines whether --dangerously-skip-permissions is used)
1071
1239
  executionConfig.sandboxed = sandboxed;
1240
+ // Handle --focus flag: when set, bring terminal to foreground instead of opening in background
1241
+ if (flags.focus) {
1242
+ executionConfig.terminal.openInBackground = false;
1243
+ }
1072
1244
  // Run execution
1073
1245
  this.log(styles.muted('Starting agent...'));
1074
1246
  const sessionManager = (flags.session || 'tmux');
@@ -1149,7 +1321,8 @@ export default class WorkStart extends PMOCommand {
1149
1321
  */
1150
1322
  async runBatchMode(workspaceInfo, db, executionStorage, flags) {
1151
1323
  // Get all tickets and filter to backlog/unstarted (not in progress)
1152
- // Note: In batch mode, we use undefined to get all tickets across all projects
1324
+ // Note: In batch mode, we get all tickets across all projects (pass undefined for projectId)
1325
+ // eslint-disable-next-line unicorn/no-useless-undefined
1153
1326
  const allTickets = await this.storage.listTickets(undefined);
1154
1327
  const backlogTickets = allTickets.filter(t => t.statusCategory === 'backlog' || t.statusCategory === 'unstarted' || !t.statusCategory);
1155
1328
  if (backlogTickets.length === 0) {
@@ -1199,6 +1372,110 @@ export default class WorkStart extends PMOCommand {
1199
1372
  this.log(styles.muted('Cancelled.'));
1200
1373
  return;
1201
1374
  }
1375
+ // Prompt for permissions mode once for all tickets (TKT-513)
1376
+ let batchPermissionMode = flags['permission-mode'];
1377
+ if (!batchPermissionMode) {
1378
+ const { permissionMode } = await inquirer.prompt([
1379
+ {
1380
+ type: 'list',
1381
+ name: 'permissionMode',
1382
+ message: 'Permission mode for Claude Code:',
1383
+ choices: [
1384
+ { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
1385
+ { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe' },
1386
+ ],
1387
+ default: 'danger',
1388
+ },
1389
+ ]);
1390
+ batchPermissionMode = permissionMode;
1391
+ }
1392
+ // Check Docker credentials if any agents use devcontainers
1393
+ const anyUseDevcontainer = availableAgents.some(agent => {
1394
+ const agentDir = path.join(workspaceInfo.agentsPath, agent.name);
1395
+ return hasDevcontainerConfig(agentDir) && !flags['run-on-host'];
1396
+ });
1397
+ if (anyUseDevcontainer) {
1398
+ const hasCredentials = dockerCredentialsExist();
1399
+ if (!hasCredentials) {
1400
+ this.log('');
1401
+ this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1402
+ this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
1403
+ this.log('');
1404
+ const { authAction } = await inquirer.prompt([
1405
+ {
1406
+ type: 'list',
1407
+ name: 'authAction',
1408
+ message: 'What would you like to do?',
1409
+ choices: [
1410
+ { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
1411
+ { name: '💻 Run all agents on host instead (--run-on-host)', value: 'host' },
1412
+ { name: '✗ Cancel', value: 'cancel' },
1413
+ ],
1414
+ },
1415
+ ]);
1416
+ if (authAction === 'cancel') {
1417
+ db.close();
1418
+ this.log(styles.muted('Cancelled.'));
1419
+ return;
1420
+ }
1421
+ if (authAction === 'host') {
1422
+ flags['run-on-host'] = true;
1423
+ this.log(styles.muted('All agents will run on host.'));
1424
+ }
1425
+ else if (authAction === 'auth') {
1426
+ this.log('');
1427
+ this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1428
+ this.log('');
1429
+ // Open auth in a new terminal tab
1430
+ const authCmd = `${process.argv[1]} agent auth`;
1431
+ try {
1432
+ execSync(`osascript -e '
1433
+ tell application "iTerm"
1434
+ tell current window
1435
+ create tab with default profile
1436
+ tell current session
1437
+ write text "${authCmd}"
1438
+ end tell
1439
+ end tell
1440
+ end tell
1441
+ '`);
1442
+ }
1443
+ catch {
1444
+ // Fallback: try Terminal.app
1445
+ try {
1446
+ execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1447
+ }
1448
+ catch {
1449
+ this.log(styles.warning('Could not open new terminal tab.'));
1450
+ this.log(styles.muted(`Please run manually: ${authCmd}`));
1451
+ }
1452
+ }
1453
+ this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1454
+ this.log('');
1455
+ // Wait for user to complete auth
1456
+ await inquirer.prompt([{
1457
+ type: 'input',
1458
+ name: 'done',
1459
+ message: 'Press Enter when authentication is complete:',
1460
+ }]);
1461
+ // Check if credentials now exist
1462
+ if (!dockerCredentialsExist()) {
1463
+ this.log('');
1464
+ this.log(styles.warning('Authentication did not complete. No credentials found.'));
1465
+ db.close();
1466
+ return;
1467
+ }
1468
+ const info = getDockerCredentialInfo();
1469
+ this.log('');
1470
+ this.log(styles.success('✓ Credentials configured'));
1471
+ if (info) {
1472
+ this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1473
+ this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1474
+ }
1475
+ this.log('');
1476
+ }
1477
+ }
1478
+ }
1202
1479
  // Assign tickets to agents (round-robin)
1203
1480
  const assignments = [];
1204
1481
  for (let i = 0; i < backlogTickets.length; i++) {
@@ -1213,13 +1490,16 @@ export default class WorkStart extends PMOCommand {
1213
1490
  this.log(styles.muted(`Starting ${ticket.id} with ${agent.name}...`));
1214
1491
  // Use the work:start command for each ticket
1215
1492
  // Pass --project from ticket to avoid re-prompting for project selection
1493
+ // Pass --permission-mode to skip prompts in recursive calls (TKT-513)
1494
+ // eslint-disable-next-line no-await-in-loop -- Sequential spawning with user feedback
1216
1495
  await this.config.runCommand('work:start', [
1217
1496
  ticket.id,
1218
1497
  ...(ticket.projectId ? ['--project', ticket.projectId] : []),
1219
- '--mode', flags.mode || 'background',
1498
+ '--display', flags.display || 'background',
1220
1499
  ...(flags.executor ? ['--executor', flags.executor] : []),
1221
1500
  ...(flags['run-on-host'] ? ['--run-on-host'] : []),
1222
1501
  ...(flags.force ? ['--force'] : []),
1502
+ '--permission-mode', batchPermissionMode,
1223
1503
  ]);
1224
1504
  successCount++;
1225
1505
  }
@@ -1317,7 +1597,7 @@ export default class WorkStart extends PMOCommand {
1317
1597
  // Non-interactive defaults
1318
1598
  const environment = useDevcontainer ? 'devcontainer' : 'host';
1319
1599
  const displayMode = 'terminal';
1320
- const sandboxed = !flags['skip-permissions'];
1600
+ const sandboxed = flags['permission-mode'] === 'safe';
1321
1601
  const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
1322
1602
  const outputMode = 'interactive';
1323
1603
  // Handle git branch - only if action modifies code