@getjack/jack 0.1.31 → 0.1.33

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 (197) hide show
  1. package/package.json +1 -1
  2. package/src/commands/deploys.ts +95 -0
  3. package/src/commands/link.ts +8 -0
  4. package/src/commands/mcp.ts +179 -4
  5. package/src/commands/rollback.ts +53 -0
  6. package/src/commands/services.ts +100 -20
  7. package/src/commands/ship.ts +30 -3
  8. package/src/commands/tokens.ts +134 -0
  9. package/src/commands/whoami.ts +51 -12
  10. package/src/index.ts +33 -0
  11. package/src/lib/agent-files.ts +54 -4
  12. package/src/lib/agent-integration.ts +4 -166
  13. package/src/lib/auth/client.ts +11 -1
  14. package/src/lib/auth/guard.ts +1 -1
  15. package/src/lib/auth/store.ts +3 -0
  16. package/src/lib/claude-hooks-installer.ts +55 -0
  17. package/src/lib/control-plane.ts +78 -40
  18. package/src/lib/debug.ts +2 -1
  19. package/src/lib/deploy-upload.ts +6 -0
  20. package/src/lib/hooks.ts +3 -1
  21. package/src/lib/managed-deploy.ts +12 -9
  22. package/src/lib/project-link.ts +6 -0
  23. package/src/lib/project-operations.ts +68 -22
  24. package/src/lib/services/token-operations.ts +84 -0
  25. package/src/lib/telemetry.ts +6 -0
  26. package/src/mcp/README.md +1 -1
  27. package/src/mcp/resources/index.ts +174 -16
  28. package/src/mcp/server.ts +23 -0
  29. package/src/mcp/tools/index.ts +133 -17
  30. package/src/mcp/types.ts +1 -0
  31. package/src/mcp/utils.ts +2 -1
  32. package/src/templates/index.ts +25 -73
  33. package/templates/CLAUDE.md +41 -0
  34. package/templates/ai-chat/.jack.json +10 -5
  35. package/templates/ai-chat/bun.lock +50 -1
  36. package/templates/ai-chat/package.json +5 -0
  37. package/templates/ai-chat/public/app.js +73 -0
  38. package/templates/ai-chat/public/index.html +14 -197
  39. package/templates/ai-chat/schema.sql +14 -0
  40. package/templates/ai-chat/src/index.ts +86 -102
  41. package/templates/ai-chat/wrangler.jsonc +8 -1
  42. package/templates/cron/.jack.json +66 -0
  43. package/templates/cron/bun.lock +23 -0
  44. package/templates/cron/package.json +16 -0
  45. package/templates/cron/schema.sql +24 -0
  46. package/templates/cron/src/index.ts +117 -0
  47. package/templates/cron/src/jobs.ts +139 -0
  48. package/templates/cron/src/webhooks.ts +95 -0
  49. package/templates/cron/tsconfig.json +17 -0
  50. package/templates/cron/wrangler.jsonc +11 -0
  51. package/templates/miniapp/.jack.json +1 -1
  52. package/templates/nextjs/.jack.json +1 -1
  53. package/templates/nextjs-auth/.jack.json +44 -0
  54. package/templates/nextjs-auth/app/api/auth/[...all]/route.ts +11 -0
  55. package/templates/nextjs-auth/app/dashboard/loading.tsx +53 -0
  56. package/templates/nextjs-auth/app/dashboard/page.tsx +73 -0
  57. package/templates/nextjs-auth/app/error.tsx +44 -0
  58. package/templates/nextjs-auth/app/globals.css +1 -0
  59. package/templates/nextjs-auth/app/health/route.ts +3 -0
  60. package/templates/nextjs-auth/app/layout.tsx +24 -0
  61. package/templates/nextjs-auth/app/login/page.tsx +10 -0
  62. package/templates/nextjs-auth/app/page.tsx +86 -0
  63. package/templates/nextjs-auth/app/signup/page.tsx +10 -0
  64. package/templates/nextjs-auth/bun.lock +1065 -0
  65. package/templates/nextjs-auth/cloudflare-env.d.ts +8 -0
  66. package/templates/nextjs-auth/components/auth-form.tsx +191 -0
  67. package/templates/nextjs-auth/components/header.tsx +50 -0
  68. package/templates/nextjs-auth/components/user-menu.tsx +23 -0
  69. package/templates/nextjs-auth/lib/auth-client.ts +3 -0
  70. package/templates/nextjs-auth/lib/auth.ts +43 -0
  71. package/templates/nextjs-auth/lib/utils.ts +6 -0
  72. package/templates/nextjs-auth/middleware.ts +33 -0
  73. package/templates/nextjs-auth/next.config.ts +8 -0
  74. package/templates/nextjs-auth/open-next.config.ts +6 -0
  75. package/templates/nextjs-auth/package.json +33 -0
  76. package/templates/nextjs-auth/postcss.config.mjs +8 -0
  77. package/templates/nextjs-auth/schema.sql +49 -0
  78. package/templates/nextjs-auth/tsconfig.json +28 -0
  79. package/templates/nextjs-auth/wrangler.jsonc +23 -0
  80. package/templates/nextjs-clerk/.jack.json +54 -0
  81. package/templates/nextjs-clerk/app/dashboard/page.tsx +69 -0
  82. package/templates/nextjs-clerk/app/globals.css +1 -0
  83. package/templates/nextjs-clerk/app/health/route.ts +3 -0
  84. package/templates/nextjs-clerk/app/layout.tsx +26 -0
  85. package/templates/nextjs-clerk/app/page.tsx +86 -0
  86. package/templates/nextjs-clerk/app/sign-in/[[...sign-in]]/page.tsx +9 -0
  87. package/templates/nextjs-clerk/app/sign-up/[[...sign-up]]/page.tsx +9 -0
  88. package/templates/nextjs-clerk/bun.lock +1055 -0
  89. package/templates/nextjs-clerk/cloudflare-env.d.ts +3 -0
  90. package/templates/nextjs-clerk/components/header.tsx +40 -0
  91. package/templates/nextjs-clerk/lib/utils.ts +6 -0
  92. package/templates/nextjs-clerk/middleware.ts +18 -0
  93. package/templates/nextjs-clerk/next.config.ts +8 -0
  94. package/templates/nextjs-clerk/open-next.config.ts +6 -0
  95. package/templates/nextjs-clerk/package.json +31 -0
  96. package/templates/nextjs-clerk/postcss.config.mjs +8 -0
  97. package/templates/nextjs-clerk/tsconfig.json +28 -0
  98. package/templates/nextjs-clerk/wrangler.jsonc +17 -0
  99. package/templates/nextjs-shadcn/.jack.json +34 -0
  100. package/templates/nextjs-shadcn/app/dashboard/data.json +614 -0
  101. package/templates/nextjs-shadcn/app/dashboard/page.tsx +55 -0
  102. package/templates/nextjs-shadcn/app/globals.css +126 -0
  103. package/templates/nextjs-shadcn/app/health/route.ts +3 -0
  104. package/templates/nextjs-shadcn/app/layout.tsx +24 -0
  105. package/templates/nextjs-shadcn/app/login/page.tsx +19 -0
  106. package/templates/nextjs-shadcn/app/page.tsx +180 -0
  107. package/templates/nextjs-shadcn/app/showcase.tsx +1262 -0
  108. package/templates/nextjs-shadcn/bun.lock +1789 -0
  109. package/templates/nextjs-shadcn/cloudflare-env.d.ts +4 -0
  110. package/templates/nextjs-shadcn/components/app-sidebar.tsx +175 -0
  111. package/templates/nextjs-shadcn/components/chart-area-interactive.tsx +291 -0
  112. package/templates/nextjs-shadcn/components/data-table.tsx +807 -0
  113. package/templates/nextjs-shadcn/components/login-form.tsx +95 -0
  114. package/templates/nextjs-shadcn/components/nav-documents.tsx +92 -0
  115. package/templates/nextjs-shadcn/components/nav-main.tsx +73 -0
  116. package/templates/nextjs-shadcn/components/nav-projects.tsx +89 -0
  117. package/templates/nextjs-shadcn/components/nav-secondary.tsx +42 -0
  118. package/templates/nextjs-shadcn/components/nav-user.tsx +114 -0
  119. package/templates/nextjs-shadcn/components/section-cards.tsx +102 -0
  120. package/templates/nextjs-shadcn/components/site-header.tsx +30 -0
  121. package/templates/nextjs-shadcn/components/team-switcher.tsx +91 -0
  122. package/templates/nextjs-shadcn/components/ui/accordion.tsx +66 -0
  123. package/templates/nextjs-shadcn/components/ui/alert-dialog.tsx +196 -0
  124. package/templates/nextjs-shadcn/components/ui/alert.tsx +66 -0
  125. package/templates/nextjs-shadcn/components/ui/aspect-ratio.tsx +11 -0
  126. package/templates/nextjs-shadcn/components/ui/avatar.tsx +109 -0
  127. package/templates/nextjs-shadcn/components/ui/badge.tsx +48 -0
  128. package/templates/nextjs-shadcn/components/ui/breadcrumb.tsx +109 -0
  129. package/templates/nextjs-shadcn/components/ui/button-group.tsx +83 -0
  130. package/templates/nextjs-shadcn/components/ui/button.tsx +64 -0
  131. package/templates/nextjs-shadcn/components/ui/calendar.tsx +220 -0
  132. package/templates/nextjs-shadcn/components/ui/card.tsx +92 -0
  133. package/templates/nextjs-shadcn/components/ui/carousel.tsx +241 -0
  134. package/templates/nextjs-shadcn/components/ui/chart.tsx +357 -0
  135. package/templates/nextjs-shadcn/components/ui/checkbox.tsx +32 -0
  136. package/templates/nextjs-shadcn/components/ui/collapsible.tsx +33 -0
  137. package/templates/nextjs-shadcn/components/ui/combobox.tsx +310 -0
  138. package/templates/nextjs-shadcn/components/ui/command.tsx +184 -0
  139. package/templates/nextjs-shadcn/components/ui/context-menu.tsx +252 -0
  140. package/templates/nextjs-shadcn/components/ui/dialog.tsx +158 -0
  141. package/templates/nextjs-shadcn/components/ui/direction.tsx +22 -0
  142. package/templates/nextjs-shadcn/components/ui/drawer.tsx +135 -0
  143. package/templates/nextjs-shadcn/components/ui/dropdown-menu.tsx +257 -0
  144. package/templates/nextjs-shadcn/components/ui/empty.tsx +104 -0
  145. package/templates/nextjs-shadcn/components/ui/field.tsx +248 -0
  146. package/templates/nextjs-shadcn/components/ui/form.tsx +167 -0
  147. package/templates/nextjs-shadcn/components/ui/hover-card.tsx +44 -0
  148. package/templates/nextjs-shadcn/components/ui/input-group.tsx +170 -0
  149. package/templates/nextjs-shadcn/components/ui/input-otp.tsx +77 -0
  150. package/templates/nextjs-shadcn/components/ui/input.tsx +21 -0
  151. package/templates/nextjs-shadcn/components/ui/item.tsx +193 -0
  152. package/templates/nextjs-shadcn/components/ui/kbd.tsx +28 -0
  153. package/templates/nextjs-shadcn/components/ui/label.tsx +24 -0
  154. package/templates/nextjs-shadcn/components/ui/menubar.tsx +276 -0
  155. package/templates/nextjs-shadcn/components/ui/native-select.tsx +53 -0
  156. package/templates/nextjs-shadcn/components/ui/navigation-menu.tsx +168 -0
  157. package/templates/nextjs-shadcn/components/ui/pagination.tsx +127 -0
  158. package/templates/nextjs-shadcn/components/ui/popover.tsx +89 -0
  159. package/templates/nextjs-shadcn/components/ui/progress.tsx +31 -0
  160. package/templates/nextjs-shadcn/components/ui/radio-group.tsx +45 -0
  161. package/templates/nextjs-shadcn/components/ui/resizable.tsx +53 -0
  162. package/templates/nextjs-shadcn/components/ui/scroll-area.tsx +58 -0
  163. package/templates/nextjs-shadcn/components/ui/select.tsx +190 -0
  164. package/templates/nextjs-shadcn/components/ui/separator.tsx +28 -0
  165. package/templates/nextjs-shadcn/components/ui/sheet.tsx +143 -0
  166. package/templates/nextjs-shadcn/components/ui/sidebar.tsx +726 -0
  167. package/templates/nextjs-shadcn/components/ui/skeleton.tsx +13 -0
  168. package/templates/nextjs-shadcn/components/ui/slider.tsx +63 -0
  169. package/templates/nextjs-shadcn/components/ui/sonner.tsx +40 -0
  170. package/templates/nextjs-shadcn/components/ui/spinner.tsx +16 -0
  171. package/templates/nextjs-shadcn/components/ui/switch.tsx +35 -0
  172. package/templates/nextjs-shadcn/components/ui/table.tsx +116 -0
  173. package/templates/nextjs-shadcn/components/ui/tabs.tsx +91 -0
  174. package/templates/nextjs-shadcn/components/ui/textarea.tsx +18 -0
  175. package/templates/nextjs-shadcn/components/ui/toggle-group.tsx +83 -0
  176. package/templates/nextjs-shadcn/components/ui/toggle.tsx +47 -0
  177. package/templates/nextjs-shadcn/components/ui/tooltip.tsx +57 -0
  178. package/templates/nextjs-shadcn/components.json +23 -0
  179. package/templates/nextjs-shadcn/hooks/use-mobile.ts +19 -0
  180. package/templates/nextjs-shadcn/lib/utils.ts +6 -0
  181. package/templates/nextjs-shadcn/next-env.d.ts +6 -0
  182. package/templates/nextjs-shadcn/next.config.ts +8 -0
  183. package/templates/nextjs-shadcn/open-next.config.ts +6 -0
  184. package/templates/nextjs-shadcn/package.json +55 -0
  185. package/templates/nextjs-shadcn/postcss.config.mjs +8 -0
  186. package/templates/nextjs-shadcn/tsconfig.json +28 -0
  187. package/templates/nextjs-shadcn/wrangler.jsonc +23 -0
  188. package/templates/resend/.jack.json +64 -0
  189. package/templates/resend/bun.lock +23 -0
  190. package/templates/resend/package.json +16 -0
  191. package/templates/resend/schema.sql +13 -0
  192. package/templates/resend/src/email.ts +165 -0
  193. package/templates/resend/src/index.ts +108 -0
  194. package/templates/resend/tsconfig.json +17 -0
  195. package/templates/resend/wrangler.jsonc +11 -0
  196. package/templates/saas/.jack.json +1 -1
  197. package/templates/ai-chat/public/chat.js +0 -149
@@ -3,26 +3,53 @@ 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(
6
- options: { managed?: boolean; byo?: boolean; dryRun?: boolean } = {},
6
+ options: { managed?: boolean; byo?: boolean; dryRun?: boolean; json?: boolean; message?: string } = {},
7
7
  ): Promise<void> {
8
8
  const isCi = process.env.CI === "true" || process.env.CI === "1";
9
+ const jsonOutput = options.json ?? false;
9
10
  try {
10
11
  const result = await deployProject({
11
12
  projectPath: process.cwd(),
12
- reporter: createReporter(),
13
- interactive: !isCi,
13
+ reporter: jsonOutput ? undefined : createReporter(),
14
+ interactive: !isCi && !jsonOutput,
14
15
  includeSecrets: !options.dryRun,
15
16
  includeSync: !options.dryRun,
16
17
  managed: options.managed,
17
18
  byo: options.byo,
18
19
  dryRun: options.dryRun,
20
+ message: options.message,
19
21
  });
20
22
 
23
+ if (jsonOutput) {
24
+ console.log(
25
+ JSON.stringify({
26
+ success: true,
27
+ projectName: result.projectName,
28
+ url: result.workerUrl,
29
+ deployMode: result.deployMode,
30
+ ...(options.message && { message: options.message }),
31
+ }),
32
+ );
33
+ return;
34
+ }
35
+
21
36
  if (!result.workerUrl && result.deployOutput) {
22
37
  console.error(result.deployOutput);
23
38
  }
24
39
  } catch (error) {
25
40
  const details = getErrorDetails(error);
41
+
42
+ if (jsonOutput) {
43
+ console.log(
44
+ JSON.stringify({
45
+ success: false,
46
+ error: details.message,
47
+ suggestion: details.suggestion,
48
+ }),
49
+ );
50
+ process.exit(details.meta?.exitCode ?? 1);
51
+ }
52
+
26
53
  if (!details.meta?.reported) {
27
54
  output.error(details.message);
28
55
  }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * jack tokens - Manage API tokens for headless authentication
3
+ *
4
+ * Tokens are account-level (not project-scoped).
5
+ * Set JACK_API_TOKEN in your environment for CI/CD and automated pipelines.
6
+ */
7
+
8
+ import { error, info, success } from "../lib/output.ts";
9
+ import {
10
+ type TokenInfo,
11
+ createApiToken,
12
+ listApiTokens,
13
+ revokeApiToken,
14
+ } from "../lib/services/token-operations.ts";
15
+ import { Events, track } from "../lib/telemetry.ts";
16
+
17
+ export default async function tokens(
18
+ subcommand?: string,
19
+ args: string[] = [],
20
+ flags: Record<string, unknown> = {},
21
+ ): Promise<void> {
22
+ if (!subcommand) {
23
+ return showHelp();
24
+ }
25
+
26
+ if (subcommand === "help" || subcommand === "--help" || subcommand === "-h") {
27
+ return showHelp();
28
+ }
29
+
30
+ switch (subcommand) {
31
+ case "create":
32
+ case "new":
33
+ return await createToken(args, flags);
34
+ case "list":
35
+ case "ls":
36
+ return await listTokens();
37
+ case "revoke":
38
+ case "rm":
39
+ case "delete":
40
+ return await revokeToken(args);
41
+ default:
42
+ error(`Unknown subcommand: ${subcommand}`);
43
+ info("Available: create, list, revoke");
44
+ process.exit(1);
45
+ }
46
+ }
47
+
48
+ function showHelp(): void {
49
+ console.error("");
50
+ info("jack tokens - Manage API tokens for headless authentication");
51
+ console.error("");
52
+ console.error("Commands:");
53
+ console.error(" create [name] Create a new API token");
54
+ console.error(" --expires <days> Token expires after N days");
55
+ console.error(" list List active tokens");
56
+ console.error(" revoke <id> Revoke a token");
57
+ console.error("");
58
+ console.error("Usage:");
59
+ console.error(" Set JACK_API_TOKEN in your environment for headless auth.");
60
+ console.error(" Tokens work in CI/CD, Docker, and automated pipelines.");
61
+ console.error("");
62
+ }
63
+
64
+ async function createToken(args: string[], flags: Record<string, unknown> = {}): Promise<void> {
65
+ // Accept name from --name flag or first positional arg
66
+ let name = "CLI Token";
67
+ if (flags.name && typeof flags.name === "string") {
68
+ name = flags.name;
69
+ } else if (args[0] && !args[0].startsWith("-")) {
70
+ name = args[0];
71
+ }
72
+
73
+ // Parse --expires <days> flag
74
+ let expiresInDays: number | undefined;
75
+ if (flags.expires !== undefined) {
76
+ const parsed = Number(flags.expires);
77
+ if (!Number.isInteger(parsed) || parsed <= 0) {
78
+ error("--expires must be a positive integer (number of days)");
79
+ process.exit(1);
80
+ }
81
+ expiresInDays = parsed;
82
+ }
83
+
84
+ const data = await createApiToken(name, expiresInDays);
85
+
86
+ track(Events.TOKEN_CREATED);
87
+
88
+ success("Token created");
89
+ console.error("");
90
+ console.error(` ${data.token}`);
91
+ console.error("");
92
+ console.error(" Save this token -- it will not be shown again.");
93
+ if (data.expires_at) {
94
+ console.error(` Expires: ${data.expires_at}`);
95
+ }
96
+ console.error("");
97
+ console.error(" Usage:");
98
+ console.error(" export JACK_API_TOKEN=<token>");
99
+ console.error(" jack ship");
100
+ console.error("");
101
+ }
102
+
103
+ async function listTokens(): Promise<void> {
104
+ const tokenList = await listApiTokens();
105
+
106
+ if (tokenList.length === 0) {
107
+ info("No active tokens");
108
+ return;
109
+ }
110
+
111
+ console.error("");
112
+ for (const t of tokenList) {
113
+ const lastUsed = t.last_used_at ? `last used ${t.last_used_at}` : "never used";
114
+ console.error(` ${t.id} ${t.name} (${lastUsed})`);
115
+ }
116
+ console.error("");
117
+ }
118
+
119
+ async function revokeToken(args: string[]): Promise<void> {
120
+ const tokenId = args[0];
121
+
122
+ if (!tokenId) {
123
+ error("Missing token ID");
124
+ info("Usage: jack tokens revoke <token-id>");
125
+ info("Run 'jack tokens list' to see token IDs");
126
+ process.exit(1);
127
+ }
128
+
129
+ await revokeApiToken(tokenId);
130
+
131
+ track(Events.TOKEN_REVOKED);
132
+
133
+ success(`Token revoked: ${tokenId}`);
134
+ }
@@ -1,31 +1,70 @@
1
+ import { authFetch } from "../lib/auth/index.ts";
1
2
  import { getCredentials } from "../lib/auth/store.ts";
2
- import { info, item, success } from "../lib/output.ts";
3
+ import { getControlApiUrl } from "../lib/control-plane.ts";
4
+ import { error, info, item, success } from "../lib/output.ts";
3
5
 
4
6
  export default async function whoami(): Promise<void> {
7
+ const apiToken = process.env.JACK_API_TOKEN;
5
8
  const creds = await getCredentials();
6
9
 
7
- if (!creds) {
10
+ if (!apiToken && !creds) {
8
11
  info("Not logged in");
9
12
  info("Run 'jack login' to sign in");
10
13
  return;
11
14
  }
12
15
 
13
16
  console.error("");
17
+
18
+ if (apiToken && !creds) {
19
+ // Token-only: fetch user info from control plane
20
+ try {
21
+ const res = await authFetch(`${getControlApiUrl()}/v1/me`);
22
+ if (!res.ok) {
23
+ error("API token is invalid or expired");
24
+ return;
25
+ }
26
+ const data = (await res.json()) as {
27
+ user?: { email?: string; id?: string; first_name?: string; last_name?: string };
28
+ };
29
+ if (data.user) {
30
+ success("Logged in");
31
+ if (data.user.email) item(`Email: ${data.user.email}`);
32
+ if (data.user.id) item(`ID: ${data.user.id}`);
33
+ if (data.user.first_name) {
34
+ item(`Name: ${data.user.first_name}${data.user.last_name ? ` ${data.user.last_name}` : ""}`);
35
+ }
36
+ } else {
37
+ success("Authenticated");
38
+ }
39
+ item(`Auth: API token (${apiToken.slice(4, 12)}...)`);
40
+ } catch {
41
+ error("Failed to reach control plane");
42
+ item(`Auth: API token (${apiToken.slice(4, 12)}...)`);
43
+ }
44
+ console.error("");
45
+ return;
46
+ }
47
+
48
+ // Has stored creds (with or without API token)
14
49
  success("Logged in");
15
- item(`Email: ${creds.user.email}`);
16
- item(`ID: ${creds.user.id}`);
50
+ item(`Email: ${creds!.user.email}`);
51
+ item(`ID: ${creds!.user.id}`);
17
52
 
18
- if (creds.user.first_name) {
19
- item(`Name: ${creds.user.first_name}${creds.user.last_name ? ` ${creds.user.last_name}` : ""}`);
53
+ if (creds!.user.first_name) {
54
+ item(`Name: ${creds!.user.first_name}${creds!.user.last_name ? ` ${creds!.user.last_name}` : ""}`);
20
55
  }
21
56
 
22
- const expiresIn = creds.expires_at - Math.floor(Date.now() / 1000);
23
- if (expiresIn > 0) {
24
- const hours = Math.floor(expiresIn / 3600);
25
- const minutes = Math.floor((expiresIn % 3600) / 60);
26
- item(`Token expires: ${hours}h ${minutes}m`);
57
+ if (apiToken) {
58
+ item("Auth: API token");
27
59
  } else {
28
- item("Token: expired (will refresh on next request)");
60
+ const expiresIn = creds!.expires_at - Math.floor(Date.now() / 1000);
61
+ if (expiresIn > 0) {
62
+ const hours = Math.floor(expiresIn / 3600);
63
+ const minutes = Math.floor((expiresIn % 3600) / 60);
64
+ item(`Token expires: ${hours}h ${minutes}m`);
65
+ } else {
66
+ item("Token: expired (will refresh on next request)");
67
+ }
29
68
  }
30
69
  console.error("");
31
70
  }
package/src/index.ts CHANGED
@@ -18,10 +18,12 @@ const cli = meow(
18
18
  new <name> [path] Create and deploy a project
19
19
  vibe "<phrase>" Create from an idea
20
20
  ship Push changes to production
21
+ rollback Roll back to previous deploy
21
22
 
22
23
  Projects
23
24
  open [name] Open in browser
24
25
  logs Stream live logs
26
+ deploys List recent deployments
25
27
  down [name] Undeploy from cloud
26
28
  ls List all projects
27
29
  info [name] Show project details
@@ -35,6 +37,7 @@ const cli = meow(
35
37
  login Sign in
36
38
  logout Sign out
37
39
  whoami Show current user
40
+ tokens Manage API tokens
38
41
  update Update jack to latest version
39
42
 
40
43
  Project Management
@@ -181,6 +184,12 @@ const cli = meow(
181
184
  sort: {
182
185
  type: "string",
183
186
  },
187
+ name: {
188
+ type: "string",
189
+ },
190
+ to: {
191
+ type: "string",
192
+ },
184
193
  },
185
194
  },
186
195
  );
@@ -208,6 +217,7 @@ const [command, ...args] = cli.input;
208
217
  os: process.platform,
209
218
  arch: process.arch,
210
219
  node_version: process.version,
220
+ auth_method: process.env.JACK_API_TOKEN ? "token" : "oauth",
211
221
  });
212
222
 
213
223
  // Update lastIdentifyDate
@@ -220,6 +230,7 @@ const [command, ...args] = cli.input;
220
230
  os: process.platform,
221
231
  arch: process.arch,
222
232
  node_version: process.version,
233
+ auth_method: process.env.JACK_API_TOKEN ? "token" : "oauth",
223
234
  });
224
235
  }
225
236
  })();
@@ -285,6 +296,8 @@ try {
285
296
  managed: cli.flags.managed,
286
297
  byo: cli.flags.byo,
287
298
  dryRun: cli.flags.dryRun,
299
+ json: cli.flags.json,
300
+ message: cli.flags.message,
288
301
  });
289
302
  break;
290
303
  }
@@ -294,6 +307,16 @@ try {
294
307
  await withTelemetry("logs", logs)({ label: cli.flags.label });
295
308
  break;
296
309
  }
310
+ case "deploys": {
311
+ const { default: deploys } = await import("./commands/deploys.ts");
312
+ await withTelemetry("deploys", deploys)({ all: cli.flags.all });
313
+ break;
314
+ }
315
+ case "rollback": {
316
+ const { default: rollback } = await import("./commands/rollback.ts");
317
+ await withTelemetry("rollback", rollback)({ to: cli.flags.to });
318
+ break;
319
+ }
297
320
  case "agents": {
298
321
  const { default: agents } = await import("./commands/agents.ts");
299
322
  await withTelemetry("agents", agents, { subcommand: args[0] })(args[0], args.slice(1), {
@@ -389,6 +412,7 @@ try {
389
412
 
390
413
  await withTelemetry("services", services, { subcommand })(args[0], serviceArgs, {
391
414
  project: cli.flags.project,
415
+ json: cli.flags.json,
392
416
  });
393
417
  break;
394
418
  }
@@ -399,6 +423,15 @@ try {
399
423
  });
400
424
  break;
401
425
  }
426
+ case "tokens": {
427
+ const { default: tokens } = await import("./commands/tokens.ts");
428
+ await withTelemetry("tokens", tokens, { subcommand: args[0] })(
429
+ args[0],
430
+ args.slice(1),
431
+ cli.flags,
432
+ );
433
+ break;
434
+ }
402
435
  case "domain": {
403
436
  const { default: domain } = await import("./commands/domain.ts");
404
437
  await withTelemetry("domain", domain, { subcommand: args[0] })(args[0], args.slice(1));
@@ -7,7 +7,7 @@ import type { AgentConfig, AgentDefinition } from "./agents.ts";
7
7
  * Template for AGENTS.md
8
8
  */
9
9
  function generateAgentsMd(projectName: string, template: Template): string {
10
- const summary = template.agentContext?.summary || "A Cloudflare Workers project";
10
+ const summary = template.agentContext?.summary || "A jack project";
11
11
  const fullText = template.agentContext?.full_text || "";
12
12
 
13
13
  return `# ${projectName}
@@ -16,16 +16,66 @@ function generateAgentsMd(projectName: string, template: Template): string {
16
16
 
17
17
  ## Deployment
18
18
 
19
- This project is deployed to Cloudflare Workers using jack:
19
+ This project is deployed and managed via jack:
20
20
 
21
21
  \`\`\`bash
22
- jack ship # Deploy to Cloudflare Workers
22
+ jack ship # Deploy to production
23
23
  jack logs # Stream production logs
24
24
  \`\`\`
25
25
 
26
26
  All deployment is handled by jack. Never run \`wrangler\` commands directly.
27
27
 
28
+ ## Quick Commands
29
+
30
+ | Command | What it does |
31
+ |---------|--------------|
32
+ | \`jack ship\` | Deploy to production |
33
+ | \`jack logs\` | Stream live logs |
34
+ | \`jack services\` | Manage databases, KV, and other bindings |
35
+ | \`jack secrets\` | Manage environment secrets |
36
+
37
+ ## Services & Bindings
38
+
39
+ Jack manages your project's services. To add a database:
40
+
41
+ \`\`\`bash
42
+ jack services db create
43
+ \`\`\`
44
+
45
+ To query it:
46
+
47
+ \`\`\`bash
48
+ jack services db query "SELECT * FROM users"
49
+ \`\`\`
50
+
28
51
  ${fullText}
52
+
53
+ ## For AI Agents
54
+
55
+ ### MCP Tools
56
+
57
+ If jack MCP is connected, **always prefer these tools over CLI commands or wrangler**:
58
+
59
+ | Tool | Use for |
60
+ |------|---------|
61
+ | \`mcp__jack__create_project\` | Create a new project (supports forking via \`template: "username/slug"\`) |
62
+ | \`mcp__jack__deploy_project\` | Deploy changes |
63
+ | \`mcp__jack__get_project_status\` | Check deployment status and URL |
64
+ | \`mcp__jack__list_projects\` | List all projects |
65
+ | \`mcp__jack__create_database\` | Create a database |
66
+ | \`mcp__jack__execute_sql\` | Query the database (read-only by default) |
67
+ | \`mcp__jack__list_databases\` | List project databases |
68
+ | \`mcp__jack__create_storage_bucket\` | Create object storage |
69
+ | \`mcp__jack__create_vectorize_index\` | Create vector search index |
70
+ | \`mcp__jack__tail_logs\` | Debug with live log samples |
71
+ | \`mcp__jack__start_log_session\` | Start real-time log stream |
72
+ | \`mcp__jack__create_cron\` | Create scheduled tasks |
73
+ | \`mcp__jack__list_domains\` | List custom domains |
74
+ | \`mcp__jack__connect_domain\` | Add a custom domain |
75
+
76
+ ### Documentation
77
+
78
+ Full jack documentation: https://docs.getjack.org/llms-full.txt
29
79
  `;
30
80
  }
31
81
 
@@ -39,7 +89,7 @@ See [AGENTS.md](./AGENTS.md) for complete project context and deployment instruc
39
89
 
40
90
  ## Quick Commands
41
91
 
42
- - **Deploy**: \`jack ship\` - Deploy to Cloudflare Workers
92
+ - **Deploy**: \`jack ship\` - Deploy to production
43
93
  - **Logs**: \`jack logs\` - Stream production logs
44
94
 
45
95
  ## Important
@@ -1,165 +1,18 @@
1
1
  /**
2
2
  * Agent integration module
3
3
  *
4
- * Ensures AI agents have proper context for jack projects.
4
+ * Ensures AI agents have MCP configured for jack projects.
5
5
  * Called during both project creation and first BYO deploy.
6
6
  */
7
7
 
8
- import { existsSync } from "node:fs";
9
- import { join } from "node:path";
10
- import type { Template } from "../templates/types.ts";
11
8
  import { installMcpConfigsToAllApps, isAppInstalled } from "./mcp-config.ts";
12
9
 
13
10
  export interface EnsureAgentResult {
14
11
  mcpInstalled: string[];
15
- jackMdCreated: boolean;
16
- referencesAdded: string[];
17
12
  }
18
13
 
19
14
  export interface EnsureAgentOptions {
20
- template?: Template;
21
15
  silent?: boolean;
22
- projectName?: string;
23
- }
24
-
25
- /**
26
- * Generate JACK.md content
27
- * Uses template agentContext if available, otherwise generic jack instructions
28
- */
29
- export function generateJackMd(projectName?: string, template?: Template): string {
30
- const header = projectName ? `# ${projectName}\n\n` : "# Jack\n\n";
31
-
32
- const templateSummary = template?.agentContext?.summary;
33
- const templateFullText = template?.agentContext?.full_text;
34
-
35
- const summarySection = templateSummary ? `> ${templateSummary}\n\n` : "";
36
-
37
- const templateSection = templateFullText ? `${templateFullText}\n\n` : "";
38
-
39
- return `${header}${summarySection}This project is deployed and managed via jack.
40
-
41
- ## Quick Commands
42
-
43
- | Command | What it does |
44
- |---------|--------------|
45
- | \`jack ship\` | Deploy to production |
46
- | \`jack logs\` | Stream live logs |
47
- | \`jack services\` | Manage databases, KV, and other bindings |
48
- | \`jack secrets\` | Manage environment secrets |
49
-
50
- ## Important
51
-
52
- - **Never run \`wrangler\` commands directly** - jack handles all infrastructure
53
- - Use \`jack services db\` to create and query databases
54
- - Secrets sync automatically across deploys
55
-
56
- ## Services & Bindings
57
-
58
- Jack manages your project's services. To add a database:
59
-
60
- \`\`\`bash
61
- jack services db create
62
- \`\`\`
63
-
64
- To query it:
65
-
66
- \`\`\`bash
67
- jack services db query "SELECT * FROM users"
68
- \`\`\`
69
-
70
- More bindings (KV, R2, queues) coming soon.
71
-
72
- ${templateSection}## For AI Agents
73
-
74
- ### MCP Tools
75
-
76
- If jack MCP is connected, prefer these tools over CLI commands:
77
-
78
- | Tool | Use for |
79
- |------|---------|
80
- | \`mcp__jack__deploy_project\` | Deploy changes |
81
- | \`mcp__jack__create_database\` | Create a new database |
82
- | \`mcp__jack__execute_sql\` | Query the database |
83
- | \`mcp__jack__list_projects\` | List all projects |
84
- | \`mcp__jack__get_project_status\` | Check deployment status |
85
-
86
- ### Documentation
87
-
88
- Full jack documentation: https://docs.getjack.org/llms-full.txt
89
- `;
90
- }
91
-
92
- /**
93
- * Create JACK.md if it doesn't exist
94
- */
95
- async function ensureJackMd(
96
- projectPath: string,
97
- projectName?: string,
98
- template?: Template,
99
- ): Promise<boolean> {
100
- const jackMdPath = join(projectPath, "JACK.md");
101
-
102
- if (existsSync(jackMdPath)) {
103
- return false;
104
- }
105
-
106
- const content = generateJackMd(projectName, template);
107
- await Bun.write(jackMdPath, content);
108
- return true;
109
- }
110
-
111
- /**
112
- * Append JACK.md reference to existing agent files (CLAUDE.md, AGENTS.md)
113
- */
114
- async function appendJackMdReferences(projectPath: string): Promise<string[]> {
115
- const filesToCheck = ["CLAUDE.md", "AGENTS.md"];
116
- const referencesAdded: string[] = [];
117
- const jackMdPath = join(projectPath, "JACK.md");
118
-
119
- // Only add references if JACK.md exists
120
- if (!existsSync(jackMdPath)) {
121
- return referencesAdded;
122
- }
123
-
124
- const referenceBlock = `<!-- Added by jack -->
125
- > **Jack project** - See [JACK.md](./JACK.md) for deployment, services, and bindings.
126
-
127
- `;
128
-
129
- for (const filename of filesToCheck) {
130
- const filePath = join(projectPath, filename);
131
-
132
- if (!existsSync(filePath)) {
133
- continue;
134
- }
135
-
136
- try {
137
- const content = await Bun.file(filePath).text();
138
-
139
- // Skip if reference already exists
140
- if (content.includes("JACK.md") || content.includes("<!-- Added by jack -->")) {
141
- continue;
142
- }
143
-
144
- // Find position after first heading, or prepend if no heading
145
- const headingMatch = content.match(/^#[^\n]*\n/m);
146
- let newContent: string;
147
-
148
- if (headingMatch && headingMatch.index !== undefined) {
149
- const insertPos = headingMatch.index + headingMatch[0].length;
150
- newContent = content.slice(0, insertPos) + "\n" + referenceBlock + content.slice(insertPos);
151
- } else {
152
- newContent = referenceBlock + content;
153
- }
154
-
155
- await Bun.write(filePath, newContent);
156
- referencesAdded.push(filename);
157
- } catch {
158
- // Ignore errors reading/writing individual files
159
- }
160
- }
161
-
162
- return referencesAdded;
163
16
  }
164
17
 
165
18
  /**
@@ -186,31 +39,16 @@ async function ensureMcpConfigured(): Promise<string[]> {
186
39
  /**
187
40
  * Ensure agent integration is set up for a project
188
41
  *
189
- * This function:
190
- * 1. Creates JACK.md if not exists (with template context if available)
191
- * 2. Appends JACK.md reference to existing CLAUDE.md/AGENTS.md
192
- * 3. Installs MCP config to detected AI apps
193
- *
42
+ * Installs MCP config to detected AI apps.
194
43
  * Safe to call multiple times - all operations are idempotent.
195
44
  */
196
45
  export async function ensureAgentIntegration(
197
- projectPath: string,
198
- options: EnsureAgentOptions = {},
46
+ _projectPath: string,
47
+ _options: EnsureAgentOptions = {},
199
48
  ): Promise<EnsureAgentResult> {
200
- const { template, projectName } = options;
201
-
202
- // 1. Create JACK.md if not exists
203
- const jackMdCreated = await ensureJackMd(projectPath, projectName, template);
204
-
205
- // 2. Append references to existing agent files
206
- const referencesAdded = await appendJackMdReferences(projectPath);
207
-
208
- // 3. Ensure MCP is configured
209
49
  const mcpInstalled = await ensureMcpConfigured();
210
50
 
211
51
  return {
212
52
  mcpInstalled,
213
- jackMdCreated,
214
- referencesAdded,
215
53
  };
216
54
  }
@@ -73,6 +73,16 @@ export async function refreshToken(refreshTokenValue: string): Promise<TokenResp
73
73
  }
74
74
 
75
75
  export async function getValidAccessToken(): Promise<string | null> {
76
+ // Priority 1: API token from environment
77
+ const apiToken = process.env.JACK_API_TOKEN;
78
+ if (apiToken) {
79
+ if (!apiToken.startsWith("jkt_")) {
80
+ console.error("Warning: JACK_API_TOKEN should start with 'jkt_'");
81
+ }
82
+ return apiToken;
83
+ }
84
+
85
+ // Priority 2: Stored OAuth credentials
76
86
  const creds = await getCredentials();
77
87
  if (!creds) {
78
88
  return null;
@@ -102,7 +112,7 @@ export async function getValidAccessToken(): Promise<string | null> {
102
112
  export async function authFetch(url: string, options: RequestInit = {}): Promise<Response> {
103
113
  const token = await getValidAccessToken();
104
114
  if (!token) {
105
- throw new Error("Not authenticated. Run 'jack login' first.");
115
+ throw new Error("Not authenticated. Run 'jack login' or set JACK_API_TOKEN.");
106
116
  }
107
117
 
108
118
  return fetch(url, {
@@ -13,7 +13,7 @@ export async function requireAuth(): Promise<string> {
13
13
  throw new JackError(
14
14
  JackErrorCode.AUTH_FAILED,
15
15
  "Not logged in",
16
- "Run 'jack login' to sign in to jack cloud",
16
+ "Run 'jack login' to sign in, or set JACK_API_TOKEN for headless use",
17
17
  );
18
18
  }
19
19