@getjack/jack 0.1.28 → 0.1.30

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 (125) hide show
  1. package/package.json +1 -1
  2. package/src/commands/cd.ts +163 -0
  3. package/src/commands/clone.ts +112 -68
  4. package/src/commands/domain.ts +506 -0
  5. package/src/commands/domains.ts +215 -0
  6. package/src/commands/down.ts +18 -12
  7. package/src/commands/hack.ts +185 -8
  8. package/src/commands/init.ts +52 -1
  9. package/src/commands/link.ts +25 -43
  10. package/src/commands/logs.ts +2 -2
  11. package/src/commands/mcp.ts +74 -3
  12. package/src/commands/new.ts +48 -54
  13. package/src/commands/projects.ts +53 -10
  14. package/src/commands/secrets.ts +5 -1
  15. package/src/commands/services.ts +16 -4
  16. package/src/commands/shell-init.ts +43 -0
  17. package/src/commands/ship.ts +2 -11
  18. package/src/commands/skills.ts +335 -0
  19. package/src/commands/update.ts +31 -0
  20. package/src/commands/upgrade.ts +14 -0
  21. package/src/index.ts +116 -24
  22. package/src/lib/agent-integration.ts +1 -2
  23. package/src/lib/agents.ts +2 -2
  24. package/src/lib/auth/login-flow.ts +1 -1
  25. package/src/lib/clone-core.ts +252 -0
  26. package/src/lib/config.ts +22 -0
  27. package/src/lib/control-plane.ts +31 -5
  28. package/src/lib/fuzzy.ts +93 -0
  29. package/src/lib/managed-deploy.ts +4 -1
  30. package/src/lib/managed-down.ts +20 -5
  31. package/src/lib/output.ts +90 -9
  32. package/src/lib/picker.ts +406 -0
  33. package/src/lib/project-detection.ts +5 -2
  34. package/src/lib/project-list.ts +66 -5
  35. package/src/lib/project-operations.ts +68 -6
  36. package/src/lib/prompts.ts +1 -1
  37. package/src/lib/services/db-execute.ts +8 -1
  38. package/src/lib/services/db-list.ts +4 -1
  39. package/src/lib/services/domain-operations.ts +379 -0
  40. package/src/lib/services/storage-config.ts +1 -5
  41. package/src/lib/services/storage-delete.ts +1 -1
  42. package/src/lib/services/storage-info.ts +2 -4
  43. package/src/lib/services/vectorize-config.ts +1 -5
  44. package/src/lib/services/vectorize-create.ts +3 -1
  45. package/src/lib/shell-integration.ts +202 -0
  46. package/src/lib/telemetry-config.ts +50 -4
  47. package/src/lib/telemetry.ts +71 -2
  48. package/src/lib/version-check.ts +1 -3
  49. package/src/lib/wrangler-config.test.ts +2 -2
  50. package/src/lib/wrangler-config.ts +1 -1
  51. package/src/lib/zip-packager.ts +1 -3
  52. package/src/mcp/tools/index.ts +261 -7
  53. package/src/templates/index.ts +10 -1
  54. package/templates/ai-chat/.jack.json +1 -5
  55. package/templates/ai-chat/public/chat.js +130 -130
  56. package/templates/ai-chat/src/index.ts +9 -13
  57. package/templates/ai-chat/src/jack-ai.ts +6 -2
  58. package/templates/saas/.jack.json +6 -1
  59. package/templates/saas/src/auth.ts +8 -4
  60. package/templates/saas/src/client/App.tsx +22 -7
  61. package/templates/saas/src/client/components/ProtectedRoute.tsx +9 -2
  62. package/templates/saas/src/client/components/ThemeToggle.tsx +1 -6
  63. package/templates/saas/src/client/components/ui/accordion.tsx +1 -1
  64. package/templates/saas/src/client/components/ui/alert-dialog.tsx +2 -2
  65. package/templates/saas/src/client/components/ui/alert.tsx +2 -2
  66. package/templates/saas/src/client/components/ui/avatar.tsx +1 -1
  67. package/templates/saas/src/client/components/ui/badge.tsx +2 -2
  68. package/templates/saas/src/client/components/ui/breadcrumb.tsx +1 -1
  69. package/templates/saas/src/client/components/ui/button-group.tsx +2 -2
  70. package/templates/saas/src/client/components/ui/button.tsx +2 -2
  71. package/templates/saas/src/client/components/ui/card.tsx +1 -1
  72. package/templates/saas/src/client/components/ui/carousel.tsx +2 -2
  73. package/templates/saas/src/client/components/ui/checkbox.tsx +1 -1
  74. package/templates/saas/src/client/components/ui/command.tsx +2 -2
  75. package/templates/saas/src/client/components/ui/context-menu.tsx +1 -1
  76. package/templates/saas/src/client/components/ui/dialog.tsx +1 -1
  77. package/templates/saas/src/client/components/ui/drawer.tsx +1 -1
  78. package/templates/saas/src/client/components/ui/dropdown-menu.tsx +1 -1
  79. package/templates/saas/src/client/components/ui/empty.tsx +1 -1
  80. package/templates/saas/src/client/components/ui/field.tsx +2 -2
  81. package/templates/saas/src/client/components/ui/form.tsx +5 -5
  82. package/templates/saas/src/client/components/ui/hover-card.tsx +1 -1
  83. package/templates/saas/src/client/components/ui/input-group.tsx +3 -3
  84. package/templates/saas/src/client/components/ui/input-otp.tsx +1 -1
  85. package/templates/saas/src/client/components/ui/input.tsx +1 -1
  86. package/templates/saas/src/client/components/ui/item.tsx +3 -3
  87. package/templates/saas/src/client/components/ui/label.tsx +1 -1
  88. package/templates/saas/src/client/components/ui/menubar.tsx +1 -1
  89. package/templates/saas/src/client/components/ui/navigation-menu.tsx +1 -1
  90. package/templates/saas/src/client/components/ui/pagination.tsx +2 -2
  91. package/templates/saas/src/client/components/ui/popover.tsx +1 -1
  92. package/templates/saas/src/client/components/ui/progress.tsx +1 -1
  93. package/templates/saas/src/client/components/ui/radio-group.tsx +1 -1
  94. package/templates/saas/src/client/components/ui/resizable.tsx +1 -1
  95. package/templates/saas/src/client/components/ui/scroll-area.tsx +1 -1
  96. package/templates/saas/src/client/components/ui/select.tsx +1 -1
  97. package/templates/saas/src/client/components/ui/separator.tsx +1 -1
  98. package/templates/saas/src/client/components/ui/sheet.tsx +1 -1
  99. package/templates/saas/src/client/components/ui/sidebar.tsx +4 -4
  100. package/templates/saas/src/client/components/ui/slider.tsx +1 -1
  101. package/templates/saas/src/client/components/ui/switch.tsx +1 -1
  102. package/templates/saas/src/client/components/ui/table.tsx +1 -1
  103. package/templates/saas/src/client/components/ui/tabs.tsx +1 -1
  104. package/templates/saas/src/client/components/ui/textarea.tsx +1 -1
  105. package/templates/saas/src/client/components/ui/toggle-group.tsx +3 -3
  106. package/templates/saas/src/client/components/ui/toggle.tsx +2 -2
  107. package/templates/saas/src/client/components/ui/tooltip.tsx +1 -1
  108. package/templates/saas/src/client/hooks/useSubscription.ts +5 -4
  109. package/templates/saas/src/client/lib/auth-client.ts +1 -1
  110. package/templates/saas/src/client/lib/plans.ts +1 -6
  111. package/templates/saas/src/client/lib/utils.ts +1 -1
  112. package/templates/saas/src/client/main.tsx +1 -1
  113. package/templates/saas/src/client/pages/DashboardPage.tsx +41 -9
  114. package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +11 -2
  115. package/templates/saas/src/client/pages/HomePage.tsx +11 -2
  116. package/templates/saas/src/client/pages/LoginPage.tsx +11 -2
  117. package/templates/saas/src/client/pages/PricingPage.tsx +20 -10
  118. package/templates/saas/src/client/pages/ResetPasswordPage.tsx +14 -11
  119. package/templates/saas/src/client/pages/SignupPage.tsx +11 -2
  120. package/templates/saas/src/index.ts +28 -19
  121. package/templates/saas/vite.config.ts +1 -1
  122. package/templates/semantic-search/.jack.json +1 -5
  123. package/templates/semantic-search/src/index.ts +8 -4
  124. package/templates/semantic-search/src/jack-ai.ts +6 -2
  125. package/templates/semantic-search/src/jack-vectorize.ts +5 -1
@@ -2,11 +2,17 @@ import { getPreferredLaunchAgent, launchAgent, scanAgents, updateAgent } from ".
2
2
  import { debug } from "../lib/debug.ts";
3
3
  import { getErrorDetails } from "../lib/errors.ts";
4
4
  import { isIntentPhrase } from "../lib/intent.ts";
5
- import { output, spinner } from "../lib/output.ts";
5
+ import { createReporter, output } from "../lib/output.ts";
6
6
  import { createProject } from "../lib/project-operations.ts";
7
+ import {
8
+ detectShell,
9
+ getRcFilePath,
10
+ isInstalled as isShellIntegrationInstalled,
11
+ } from "../lib/shell-integration.ts";
7
12
 
8
13
  export default async function newProject(
9
14
  nameOrPhrase?: string,
15
+ pathArg?: string,
10
16
  options: {
11
17
  template?: string;
12
18
  intent?: string;
@@ -51,20 +57,11 @@ export default async function newProject(
51
57
  result = await createProject(projectName, {
52
58
  template: options.template,
53
59
  intent: intentPhrase,
54
- reporter: {
55
- start: output.start,
56
- stop: output.stop,
57
- spinner,
58
- info: output.info,
59
- warn: output.warn,
60
- error: output.error,
61
- success: output.success,
62
- box: output.box,
63
- celebrate: output.celebrate,
64
- },
60
+ reporter: createReporter(),
65
61
  interactive: !isCi,
66
62
  managed: options.managed,
67
63
  byo: options.byo,
64
+ targetDir: pathArg || undefined,
68
65
  });
69
66
  } catch (error) {
70
67
  const details = getErrorDetails(error);
@@ -96,63 +93,60 @@ export default async function newProject(
96
93
  return;
97
94
  }
98
95
 
99
- console.error("");
100
- output.info(`Project: ${result.targetDir}`);
101
-
102
- // Skip agent section entirely if --no-open or env var
96
+ // Skip next steps if --no-open or env var
103
97
  if (options.noOpen || process.env.JACK_NO_OPEN === "1") {
104
98
  return;
105
99
  }
106
100
 
107
- // Skip in CI mode
101
+ // Check if shell integration is installed
102
+ const shell = detectShell();
103
+ const rcFile = getRcFilePath(shell);
104
+ const hasShellIntegration = rcFile ? isShellIntegrationInstalled(rcFile) : false;
105
+
108
106
  if (!process.stdout.isTTY || isCi) {
107
+ console.error("");
108
+ console.error(`cd ${result.targetDir}`);
109
109
  return;
110
110
  }
111
111
 
112
- // Get preferred agent
113
- let preferred = await getPreferredLaunchAgent();
114
-
115
- // If no preferred agent, scan and auto-enable detected agents
116
- if (!preferred) {
117
- const detectionResult = await scanAgents();
118
-
119
- if (detectionResult.detected.length > 0) {
120
- // Auto-enable newly detected agents
121
- for (const { id, path, launch } of detectionResult.detected) {
122
- await updateAgent(id, {
123
- active: true,
124
- path,
125
- detectedAt: new Date().toISOString(),
126
- launch,
127
- });
112
+ // Auto-open if --open flag (requires agent detection)
113
+ if (options.open) {
114
+ let preferred = await getPreferredLaunchAgent();
115
+
116
+ if (!preferred) {
117
+ const detectionResult = await scanAgents();
118
+ if (detectionResult.detected.length > 0) {
119
+ for (const { id, path, launch } of detectionResult.detected) {
120
+ await updateAgent(id, {
121
+ active: true,
122
+ path,
123
+ detectedAt: new Date().toISOString(),
124
+ launch,
125
+ });
126
+ }
127
+ preferred = await getPreferredLaunchAgent();
128
128
  }
129
- // Use the first detected agent as preferred
130
- preferred = await getPreferredLaunchAgent();
131
129
  }
132
- }
133
130
 
134
- // Auto-open if --open flag
135
- if (options.open && preferred) {
136
- const launchResult = await launchAgent(preferred.launch, result.targetDir, {
137
- projectName: result.projectName,
138
- url: result.workerUrl,
139
- });
140
- if (!launchResult.success) {
141
- output.warn(`Failed to launch ${preferred.definition.name}`);
142
- if (launchResult.command?.length) {
143
- output.info(`Run manually: ${launchResult.command.join(" ")}`);
131
+ if (preferred) {
132
+ const launchResult = await launchAgent(preferred.launch, result.targetDir, {
133
+ projectName: result.projectName,
134
+ url: result.workerUrl,
135
+ });
136
+ if (!launchResult.success) {
137
+ output.warn(`Failed to launch ${preferred.definition.name}`);
138
+ if (launchResult.command?.length) {
139
+ output.info(`Run manually: ${launchResult.command.join(" ")}`);
140
+ }
144
141
  }
142
+ return;
145
143
  }
146
- return;
147
144
  }
148
145
 
149
- // Default: show next steps (no prompt)
150
- if (preferred) {
151
- console.error("");
152
- output.info(`Next: cd ${result.targetDir} && ${preferred.launch.command}`);
146
+ console.error("");
147
+ if (hasShellIntegration) {
148
+ output.success(`Ready in ${result.projectName}`);
153
149
  } else {
154
- console.error("");
155
- output.info("No AI agents detected");
156
- output.info("Install Claude Code or Codex, then run: jack agents scan");
150
+ console.error(`cd ${result.targetDir}`);
157
151
  }
158
152
  }
@@ -1,11 +1,13 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { resolve } from "node:path";
4
+ import { fuzzyFilter } from "../lib/fuzzy.ts";
4
5
  import { promptSelect } from "../lib/hooks.ts";
5
6
  import { error, info, item, output as outputSpinner, success, warn } from "../lib/output.ts";
6
7
  import {
7
8
  type ProjectListItem,
8
9
  STATUS_ICONS,
10
+ type SortOrder,
9
11
  buildTagColorMap,
10
12
  colors,
11
13
  filterByStatus,
@@ -16,6 +18,7 @@ import {
16
18
  formatTagsInline,
17
19
  groupProjects,
18
20
  sortByUpdated,
21
+ sortItems,
19
22
  toListItems,
20
23
  } from "../lib/project-list.ts";
21
24
  import { cleanupStaleProjects, scanStaleProjects } from "../lib/project-operations.ts";
@@ -63,9 +66,18 @@ export default async function projects(subcommand?: string, args: string[] = [])
63
66
  }
64
67
 
65
68
  /**
66
- * Extract a flag value from args (e.g., --status live -> "live")
69
+ * Extract a flag value from args (e.g., --status live -> "live" or --sort=name -> "name")
67
70
  */
68
71
  function extractFlagValue(args: string[], flag: string): string | null {
72
+ // Check for --flag=value syntax
73
+ const prefix = `${flag}=`;
74
+ for (const arg of args) {
75
+ if (arg.startsWith(prefix)) {
76
+ return arg.slice(prefix.length) || null;
77
+ }
78
+ }
79
+
80
+ // Check for --flag value syntax
69
81
  const idx = args.indexOf(flag);
70
82
  if (idx !== -1 && idx + 1 < args.length) {
71
83
  return args[idx + 1] ?? null;
@@ -99,6 +111,20 @@ async function listProjects(args: string[]): Promise<void> {
99
111
  const localOnly = args.includes("--local");
100
112
  const cloudOnly = args.includes("--cloud");
101
113
 
114
+ // Parse --sort flag (default: updated)
115
+ const sortFlag = extractFlagValue(args, "--sort");
116
+ const sortOrder: SortOrder = sortFlag === "name" || sortFlag === "created" ? sortFlag : "updated";
117
+
118
+ // Extract positional filter (first arg that's not a flag or flag value)
119
+ const flagsWithValues = ["--status", "--tag", "--sort"];
120
+ const nameFilter = args.find((arg, idx) => {
121
+ if (arg.startsWith("--") || arg.startsWith("-")) return false;
122
+ // Check if previous arg was a flag that takes a value
123
+ const prevArg = args[idx - 1];
124
+ if (prevArg && flagsWithValues.includes(prevArg)) return false;
125
+ return true;
126
+ });
127
+
102
128
  // Fetch all projects from registry and control plane
103
129
  outputSpinner.start("Loading projects...");
104
130
  const projects: ResolvedProject[] = await listAllProjects();
@@ -107,7 +133,18 @@ async function listProjects(args: string[]): Promise<void> {
107
133
  // Convert to list items
108
134
  let items = toListItems(projects);
109
135
 
110
- // Apply filters
136
+ // Apply fuzzy name filter first (uses updatedAt for tiebreaker via sort stability)
137
+ if (nameFilter) {
138
+ // Sort by updatedAt descending before fuzzy filter so tiebreaker favors recent
139
+ items.sort((a, b) => {
140
+ const aTime = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
141
+ const bTime = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
142
+ return bTime - aTime;
143
+ });
144
+ items = fuzzyFilter(nameFilter, items, (item) => item.name);
145
+ }
146
+
147
+ // Apply other filters
111
148
  if (statusFilter) items = filterByStatus(items, statusFilter);
112
149
  if (localOnly) items = items.filter((i) => i.isLocal);
113
150
  if (cloudOnly) items = items.filter((i) => i.isCloudOnly);
@@ -128,6 +165,9 @@ async function listProjects(args: string[]): Promise<void> {
128
165
  return;
129
166
  }
130
167
 
168
+ // Apply sorting
169
+ items = sortItems(items, sortOrder);
170
+
131
171
  // JSON output to stdout (pipeable)
132
172
  if (jsonOutput) {
133
173
  console.log(JSON.stringify(items, null, 2));
@@ -146,8 +186,10 @@ async function listProjects(args: string[]): Promise<void> {
146
186
 
147
187
  /**
148
188
  * Render the grouped view (default)
189
+ * Note: items are already sorted by the caller
149
190
  */
150
191
  function renderGroupedView(items: ProjectListItem[]): void {
192
+ // Group projects while preserving the sort order within each group
151
193
  const groups = groupProjects(items);
152
194
  const total = items.length;
153
195
  const CLOUD_LIMIT = 5;
@@ -173,13 +215,11 @@ function renderGroupedView(items: ProjectListItem[]): void {
173
215
  console.error(formatLocalSection(groups.local, { tagColorMap }));
174
216
  }
175
217
 
176
- // Section 3: Cloud-only (show last N by updatedAt)
218
+ // Section 3: Cloud-only (show first N from already-sorted list)
177
219
  if (groups.cloudOnly.length > 0) {
178
- const sorted = sortByUpdated(groups.cloudOnly);
179
-
180
220
  console.error("");
181
221
  console.error(
182
- formatCloudSection(sorted, {
222
+ formatCloudSection(groups.cloudOnly, {
183
223
  limit: CLOUD_LIMIT,
184
224
  total: groups.cloudOnly.length,
185
225
  tagColorMap,
@@ -200,13 +240,16 @@ function renderGroupedView(items: ProjectListItem[]): void {
200
240
 
201
241
  /**
202
242
  * Render flat table (for --all mode)
243
+ * Note: items are already sorted by the caller
203
244
  */
204
245
  function renderFlatTable(items: ProjectListItem[]): void {
205
- // Sort: errors first, then by name
246
+ // Keep errors at the top while preserving relative order within each group
206
247
  const sorted = [...items].sort((a, b) => {
207
- if (a.status === "error" && b.status !== "error") return -1;
208
- if (a.status !== "error" && b.status === "error") return 1;
209
- return a.name.localeCompare(b.name);
248
+ const aIsError = a.status === "error" || a.status === "auth-expired";
249
+ const bIsError = b.status === "error" || b.status === "auth-expired";
250
+ if (aIsError && !bIsError) return -1;
251
+ if (!aIsError && bIsError) return 1;
252
+ return 0; // Preserve existing sort order
210
253
  });
211
254
 
212
255
  // Build consistent tag color map
@@ -7,10 +7,10 @@
7
7
  */
8
8
 
9
9
  import { password as passwordPrompt } from "@clack/prompts";
10
- import { isCancel } from "../lib/hooks.ts";
11
10
  import { $ } from "bun";
12
11
  import { getControlApiUrl } from "../lib/control-plane.ts";
13
12
  import { JackError, JackErrorCode } from "../lib/errors.ts";
13
+ import { isCancel } from "../lib/hooks.ts";
14
14
  import { error, info, output, success, warn } from "../lib/output.ts";
15
15
  import { type LocalProjectLink, readProjectLink } from "../lib/project-link.ts";
16
16
  import { getProjectNameFromDir } from "../lib/storage/index.ts";
@@ -25,6 +25,10 @@ export default async function secrets(
25
25
  options: SecretsOptions = {},
26
26
  ): Promise<void> {
27
27
  if (!subcommand) {
28
+ return await listSecrets(options);
29
+ }
30
+
31
+ if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
28
32
  return showHelp();
29
33
  }
30
34
 
@@ -438,7 +438,10 @@ async function dbDelete(options: ServiceOptions): Promise<void> {
438
438
 
439
439
  // Confirm deletion
440
440
  const { promptSelect } = await import("../lib/hooks.ts");
441
- const choice = await promptSelect(["Yes, delete", "No, cancel"], `Delete database '${dbInfo.name}'?`);
441
+ const choice = await promptSelect(
442
+ ["Yes, delete", "No, cancel"],
443
+ `Delete database '${dbInfo.name}'?`,
444
+ );
442
445
 
443
446
  if (choice !== 0) {
444
447
  info("Cancelled");
@@ -989,7 +992,11 @@ async function storageInfo(args: string[], options: ServiceOptions): Promise<voi
989
992
 
990
993
  if (!bucketInfo) {
991
994
  console.error("");
992
- error(bucketName ? `Bucket "${bucketName}" not found` : "No storage buckets found for this project");
995
+ error(
996
+ bucketName
997
+ ? `Bucket "${bucketName}" not found`
998
+ : "No storage buckets found for this project",
999
+ );
993
1000
  info("Create one with: jack services storage create");
994
1001
  console.error("");
995
1002
  return;
@@ -999,7 +1006,9 @@ async function storageInfo(args: string[], options: ServiceOptions): Promise<voi
999
1006
  success(`Bucket: ${bucketInfo.name}`);
1000
1007
  console.error("");
1001
1008
  item(`Binding: ${bucketInfo.binding}`);
1002
- item(`Source: ${bucketInfo.source === "control-plane" ? "managed (jack cloud)" : "BYO (wrangler)"}`);
1009
+ item(
1010
+ `Source: ${bucketInfo.source === "control-plane" ? "managed (jack cloud)" : "BYO (wrangler)"}`,
1011
+ );
1003
1012
  console.error("");
1004
1013
  } catch (err) {
1005
1014
  outputSpinner.stop();
@@ -1137,7 +1146,10 @@ async function storageDelete(args: string[], options: ServiceOptions): Promise<v
1137
1146
 
1138
1147
  // Confirm deletion
1139
1148
  const { promptSelect } = await import("../lib/hooks.ts");
1140
- const choice = await promptSelect(["Yes, delete", "No, cancel"], `Delete bucket '${bucketName}'?`);
1149
+ const choice = await promptSelect(
1150
+ ["Yes, delete", "No, cancel"],
1151
+ `Delete bucket '${bucketName}'?`,
1152
+ );
1141
1153
 
1142
1154
  if (choice !== 0) {
1143
1155
  info("Cancelled");
@@ -0,0 +1,43 @@
1
+ /**
2
+ * shell-init command - output shell integration code
3
+ *
4
+ * Usage: jack shell-init
5
+ *
6
+ * Outputs shell code to stdout. Users can:
7
+ * - eval "$(jack shell-init)" for temporary use
8
+ * - Run `jack init` to install permanently (recommended)
9
+ */
10
+
11
+ import {
12
+ detectShell,
13
+ getRcFileName,
14
+ getRcFilePath,
15
+ getShellCode,
16
+ getShellFileDisplayPath,
17
+ getSourceLine,
18
+ } from "../lib/shell-integration.ts";
19
+
20
+ export default async function shellInit(): Promise<void> {
21
+ const code = getShellCode();
22
+
23
+ // If piped (eval), output raw code only
24
+ if (!process.stdout.isTTY) {
25
+ console.log(code);
26
+ return;
27
+ }
28
+
29
+ // Interactive: show instructions
30
+ const shell = detectShell();
31
+ const rcFile = getRcFilePath(shell);
32
+ const rcName = rcFile ? getRcFileName(rcFile) : ".bashrc or .zshrc";
33
+
34
+ console.log("# Shell integration for jack");
35
+ console.log("#");
36
+ console.log("# Option 1 (recommended): Run 'jack init' to install automatically");
37
+ console.log("#");
38
+ console.log(`# Option 2: Add this line to your ~/${rcName}:`);
39
+ console.log(`# ${getSourceLine().split("\n")[1]}`);
40
+ console.log(`# Then create ${getShellFileDisplayPath()} with:`);
41
+ console.log("#");
42
+ console.log(code);
43
+ }
@@ -1,5 +1,5 @@
1
1
  import { getErrorDetails } from "../lib/errors.ts";
2
- import { output, spinner } from "../lib/output.ts";
2
+ import { createReporter, output } from "../lib/output.ts";
3
3
  import { deployProject } from "../lib/project-operations.ts";
4
4
 
5
5
  export default async function ship(
@@ -9,16 +9,7 @@ export default async function ship(
9
9
  try {
10
10
  const result = await deployProject({
11
11
  projectPath: process.cwd(),
12
- reporter: {
13
- start: output.start,
14
- stop: output.stop,
15
- spinner,
16
- info: output.info,
17
- warn: output.warn,
18
- error: output.error,
19
- success: output.success,
20
- box: output.box,
21
- },
12
+ reporter: createReporter(),
22
13
  interactive: !isCi,
23
14
  includeSecrets: !options.dryRun,
24
15
  includeSync: !options.dryRun,