@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
@@ -0,0 +1,335 @@
1
+ /**
2
+ * jack skills - Manage agent skills for project-specific knowledge
3
+ *
4
+ * Skills are capability chips that teach AI agents Jack Cloud-specific patterns.
5
+ * Uses skills.sh (npx skills add) for installation.
6
+ */
7
+
8
+ import { existsSync } from "node:fs";
9
+ import { readdir, rm } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { type AgentLaunchConfig, getPreferredLaunchAgent, launchAgent } from "../lib/agents.ts";
12
+ import { error, info, item, success } from "../lib/output.ts";
13
+
14
+ const SKILLS_REPO = "getjack-org/skills";
15
+ const SKILLS_API_URL = `https://api.github.com/repos/${SKILLS_REPO}/contents/skills`;
16
+ const SUPPORTED_AGENTS = ["claude-code", "codex"];
17
+
18
+ // Cache for fetched skills (per process)
19
+ let cachedSkills: { name: string; description: string }[] | null = null;
20
+ let fetchError: string | null = null;
21
+
22
+ const FETCH_TIMEOUT_MS = 5000;
23
+
24
+ interface GitHubContent {
25
+ name: string;
26
+ type: string;
27
+ download_url: string | null;
28
+ }
29
+
30
+ async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
31
+ const controller = new AbortController();
32
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
33
+ try {
34
+ return await fetch(url, {
35
+ headers: { "User-Agent": "jack-cli" },
36
+ signal: controller.signal,
37
+ });
38
+ } finally {
39
+ clearTimeout(timeout);
40
+ }
41
+ }
42
+
43
+ async function fetchAvailableSkills(): Promise<{ name: string; description: string }[]> {
44
+ if (cachedSkills) return cachedSkills;
45
+ if (fetchError) return []; // Don't retry on same process
46
+
47
+ try {
48
+ const res = await fetchWithTimeout(SKILLS_API_URL, FETCH_TIMEOUT_MS);
49
+
50
+ if (res.status === 403) {
51
+ fetchError = "GitHub API rate limit exceeded. Try again later.";
52
+ return [];
53
+ }
54
+
55
+ if (!res.ok) {
56
+ fetchError = `GitHub API error: ${res.status}`;
57
+ return [];
58
+ }
59
+
60
+ const contents: GitHubContent[] = await res.json();
61
+ const skillDirs = contents.filter((c) => c.type === "dir");
62
+
63
+ // Fetch descriptions from SKILL.md in parallel (with timeout each)
64
+ const skills = await Promise.all(
65
+ skillDirs.map(async (dir) => {
66
+ const description = await fetchSkillDescription(dir.name);
67
+ return { name: dir.name, description };
68
+ }),
69
+ );
70
+
71
+ cachedSkills = skills;
72
+ return skills;
73
+ } catch (err) {
74
+ if (err instanceof Error && err.name === "AbortError") {
75
+ fetchError = "Request timed out. Check your internet connection.";
76
+ } else {
77
+ fetchError = "Could not fetch skills catalog.";
78
+ }
79
+ return [];
80
+ }
81
+ }
82
+
83
+ function getFetchError(): string | null {
84
+ return fetchError;
85
+ }
86
+
87
+ async function fetchSkillDescription(skillName: string): Promise<string> {
88
+ try {
89
+ const url = `https://raw.githubusercontent.com/${SKILLS_REPO}/main/skills/${skillName}/SKILL.md`;
90
+ const res = await fetch(url, {
91
+ headers: { "User-Agent": "jack-cli" },
92
+ });
93
+
94
+ if (!res.ok) return "";
95
+
96
+ const content = await res.text();
97
+ // Parse YAML frontmatter for description
98
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
99
+ if (match) {
100
+ const frontmatter = match[1];
101
+ const descMatch = frontmatter.match(/description:\s*>?\s*\n?\s*(.+?)(?:\n\s{2,}|$)/s);
102
+ if (descMatch) {
103
+ // Get first line of description
104
+ return descMatch[1].split("\n")[0].trim();
105
+ }
106
+ }
107
+ return "";
108
+ } catch {
109
+ return "";
110
+ }
111
+ }
112
+
113
+ export default async function skills(subcommand?: string, args: string[] = []): Promise<void> {
114
+ if (!subcommand) {
115
+ return showHelp();
116
+ }
117
+
118
+ switch (subcommand) {
119
+ case "run":
120
+ return await runSkill(args[0]);
121
+ case "list":
122
+ case "ls":
123
+ return await listSkills();
124
+ case "remove":
125
+ case "rm":
126
+ return await removeSkill(args[0]);
127
+ default:
128
+ error(`Unknown subcommand: ${subcommand}`);
129
+ info("Available: run, list, remove");
130
+ process.exit(1);
131
+ }
132
+ }
133
+
134
+ async function showHelp(): Promise<void> {
135
+ console.log("");
136
+ info("jack skills - Manage agent skills");
137
+ console.log("");
138
+ console.log("Commands:");
139
+ console.log(" run <name> Install (if needed) and launch agent with skill");
140
+ console.log(" list List installed skills in current project");
141
+ console.log(" remove <name> Remove a skill from project");
142
+ console.log("");
143
+ const skills = await fetchAvailableSkills();
144
+ if (skills.length > 0) {
145
+ console.log("Available skills:");
146
+ for (const skill of skills) {
147
+ console.log(` ${skill.name.padEnd(16)} ${skill.description}`);
148
+ }
149
+ console.log("");
150
+ } else {
151
+ const err = getFetchError();
152
+ if (err) {
153
+ info(`Could not load skills catalog: ${err}`);
154
+ info("You can still run: jack skills run <skill-name>");
155
+ console.log("");
156
+ }
157
+ }
158
+ }
159
+
160
+ async function runSkill(skillName?: string): Promise<void> {
161
+ const availableSkills = await fetchAvailableSkills();
162
+
163
+ if (!skillName) {
164
+ error("Missing skill name");
165
+ info("Usage: jack skills run <name>");
166
+ if (availableSkills.length > 0) {
167
+ console.log("");
168
+ console.log("Available skills:");
169
+ for (const skill of availableSkills) {
170
+ console.log(` ${skill.name.padEnd(16)} ${skill.description}`);
171
+ }
172
+ } else {
173
+ const err = getFetchError();
174
+ if (err) {
175
+ console.log("");
176
+ info(`Could not load skills catalog: ${err}`);
177
+ }
178
+ }
179
+ process.exit(1);
180
+ }
181
+
182
+ const projectDir = process.cwd();
183
+
184
+ // 1. Validate skill exists in catalog (if we could fetch it)
185
+ if (availableSkills.length > 0) {
186
+ const skill = availableSkills.find((s) => s.name === skillName);
187
+ if (!skill) {
188
+ error(`Skill not found: ${skillName}`);
189
+ console.log("");
190
+ console.log("Available skills:");
191
+ for (const s of availableSkills) {
192
+ console.log(` ${s.name.padEnd(16)} ${s.description}`);
193
+ }
194
+ process.exit(1);
195
+ }
196
+ }
197
+ // If fetch failed, let skills.sh handle validation
198
+
199
+ // 2. Check if already installed
200
+ const skillPath = join(projectDir, ".claude/skills", skillName);
201
+ if (!existsSync(skillPath)) {
202
+ info(`Installing ${skillName}...`);
203
+
204
+ const agentFlags = SUPPORTED_AGENTS.flatMap((a) => ["--agent", a]);
205
+ const proc = Bun.spawn(
206
+ ["npx", "skills", "add", SKILLS_REPO, "--skill", skillName, ...agentFlags, "--yes"],
207
+ {
208
+ cwd: projectDir,
209
+ stdout: "pipe",
210
+ stderr: "pipe",
211
+ },
212
+ );
213
+
214
+ // Collect output for error reporting
215
+ const stdout = await new Response(proc.stdout).text();
216
+ const stderr = await new Response(proc.stderr).text();
217
+
218
+ await proc.exited;
219
+
220
+ if (proc.exitCode !== 0) {
221
+ error(`Failed to install skill: ${skillName}`);
222
+ // Show stderr on failure for debugging
223
+ if (stderr.trim()) {
224
+ console.error(stderr);
225
+ }
226
+ process.exit(1);
227
+ }
228
+
229
+ success(`Installed ${skillName}`);
230
+ } else {
231
+ info(`Skill ${skillName} already installed`);
232
+ }
233
+
234
+ // 3. Get preferred agent
235
+ const preferred = await getPreferredLaunchAgent();
236
+ if (!preferred) {
237
+ error("No agent configured");
238
+ info("Run: jack init");
239
+ process.exit(1);
240
+ }
241
+
242
+ // 4. Launch agent with skill command
243
+ const agentName = preferred.definition.name;
244
+ console.log("");
245
+ info(`Launching ${agentName} with /${skillName}...`);
246
+
247
+ const launchWithSkill: AgentLaunchConfig = {
248
+ ...preferred.launch,
249
+ args: [...(preferred.launch.args || []), `/${skillName}`],
250
+ };
251
+
252
+ const result = await launchAgent(launchWithSkill, projectDir);
253
+
254
+ if (!result.success) {
255
+ error(`Failed to launch ${agentName}`);
256
+ if (result.error) {
257
+ info(result.error);
258
+ }
259
+ process.exit(1);
260
+ }
261
+ }
262
+
263
+ async function listSkills(): Promise<void> {
264
+ const projectDir = process.cwd();
265
+ const skillsDir = join(projectDir, ".claude/skills");
266
+ const availableSkills = await fetchAvailableSkills();
267
+
268
+ if (!existsSync(skillsDir)) {
269
+ info("No skills installed in this project.");
270
+ console.log("");
271
+ info("Run 'jack skills run <name>' to install and use a skill.");
272
+ if (availableSkills.length > 0) {
273
+ console.log("");
274
+ console.log("Available skills:");
275
+ for (const skill of availableSkills) {
276
+ console.log(` ${skill.name.padEnd(16)} ${skill.description}`);
277
+ }
278
+ } else {
279
+ const err = getFetchError();
280
+ if (err) {
281
+ console.log("");
282
+ info(`Could not load skills catalog: ${err}`);
283
+ }
284
+ }
285
+ return;
286
+ }
287
+
288
+ // List directories in .claude/skills/
289
+ const entries = await readdir(skillsDir, { withFileTypes: true });
290
+ const installedSkills = entries.filter((e) => e.isSymbolicLink() || e.isDirectory());
291
+
292
+ if (installedSkills.length === 0) {
293
+ info("No skills installed in this project.");
294
+ console.log("");
295
+ info("Run 'jack skills run <name>' to install and use a skill.");
296
+ return;
297
+ }
298
+
299
+ console.log("");
300
+ info("Installed skills (project):");
301
+ for (const skill of installedSkills) {
302
+ const desc = availableSkills.find((s) => s.name === skill.name)?.description ?? "";
303
+ item(`${skill.name.padEnd(16)} ${desc}`);
304
+ }
305
+ console.log("");
306
+ info("Run 'jack skills run <name>' to use a skill.");
307
+ }
308
+
309
+ async function removeSkill(skillName?: string): Promise<void> {
310
+ if (!skillName) {
311
+ error("Missing skill name");
312
+ info("Usage: jack skills remove <name>");
313
+ process.exit(1);
314
+ }
315
+
316
+ const projectDir = process.cwd();
317
+
318
+ // Remove from all agent directories
319
+ const dirs = [".agents/skills", ".claude/skills", ".codex/skills", ".cursor/skills"];
320
+ let removed = false;
321
+
322
+ for (const dir of dirs) {
323
+ const path = join(projectDir, dir, skillName);
324
+ if (existsSync(path)) {
325
+ await rm(path, { recursive: true, force: true });
326
+ removed = true;
327
+ }
328
+ }
329
+
330
+ if (removed) {
331
+ success(`Removed ${skillName}`);
332
+ } else {
333
+ info(`Skill ${skillName} not found in project`);
334
+ }
335
+ }
@@ -3,7 +3,14 @@
3
3
  */
4
4
 
5
5
  import { debug } from "../lib/debug.ts";
6
+ import { installMcpConfigsToAllApps } from "../lib/mcp-config.ts";
6
7
  import { error, info, success, warn } from "../lib/output.ts";
8
+ import {
9
+ detectShell,
10
+ getRcFilePath,
11
+ isInstalled as isShellIntegrationInstalled,
12
+ update as updateShellIntegration,
13
+ } from "../lib/shell-integration.ts";
7
14
  import {
8
15
  checkForUpdate,
9
16
  getCurrentVersion,
@@ -52,6 +59,30 @@ export default async function update(): Promise<void> {
52
59
 
53
60
  if (result.success) {
54
61
  success(`Updated to v${result.version ?? latestVersion}`);
62
+
63
+ // Repair MCP config on successful update (ensures new features work)
64
+ try {
65
+ const installedApps = await installMcpConfigsToAllApps();
66
+ if (installedApps.length > 0) {
67
+ info(`MCP config updated for: ${installedApps.join(", ")}`);
68
+ }
69
+ } catch {
70
+ // Non-critical - don't fail update if MCP repair fails
71
+ debug("MCP config repair failed (non-critical)");
72
+ }
73
+
74
+ try {
75
+ const shell = detectShell();
76
+ const rcFile = getRcFilePath(shell);
77
+ if (rcFile && isShellIntegrationInstalled(rcFile)) {
78
+ updateShellIntegration();
79
+ info("Shell integration updated");
80
+ } else if (rcFile && shell !== "unknown") {
81
+ info("Tip: 'jack init' enables auto-cd for jack new/cd");
82
+ }
83
+ } catch {
84
+ debug("Shell integration update failed");
85
+ }
55
86
  } else {
56
87
  error("Update failed");
57
88
  if (result.error) {
@@ -0,0 +1,14 @@
1
+ /**
2
+ * jack upgrade - Show billing dashboard URL to upgrade plan
3
+ */
4
+
5
+ import { info } from "../lib/output.ts";
6
+
7
+ const BILLING_URL = "https://dash.getjack.org";
8
+
9
+ export default async function upgrade(): Promise<void> {
10
+ console.error("");
11
+ info("Upgrade your plan at:");
12
+ console.error(` ${BILLING_URL}`);
13
+ console.error("");
14
+ }
package/src/index.ts CHANGED
@@ -4,7 +4,7 @@ import pkg from "../package.json";
4
4
  import { enableDebug } from "./lib/debug.ts";
5
5
  import { isJackError } from "./lib/errors.ts";
6
6
  import { info, error as printError } from "./lib/output.ts";
7
- import { getEnvironmentProps, identify, shutdown, withTelemetry } from "./lib/telemetry.ts";
7
+ import { identify, shutdown, withTelemetry } from "./lib/telemetry.ts";
8
8
 
9
9
  const cli = meow(
10
10
  `
@@ -15,7 +15,7 @@ const cli = meow(
15
15
 
16
16
  Getting Started
17
17
  init Set up jack (run once)
18
- new [name] Create and deploy a project
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
21
 
@@ -45,6 +45,8 @@ const cli = meow(
45
45
  Advanced
46
46
  agents Manage AI agent configs
47
47
  secrets Manage project secrets
48
+ domain Manage custom domains
49
+ skills Install and run agent skills
48
50
  services Manage databases
49
51
  mcp MCP server for AI agents
50
52
  telemetry Usage data settings
@@ -176,6 +178,9 @@ const cli = meow(
176
178
  db: {
177
179
  type: "string",
178
180
  },
181
+ sort: {
182
+ type: "string",
183
+ },
179
184
  },
180
185
  },
181
186
  );
@@ -187,15 +192,37 @@ if (cli.flags.debug) {
187
192
 
188
193
  const [command, ...args] = cli.input;
189
194
 
190
- // Identify user properties for telemetry
191
- identify({
192
- jack_version: pkg.version,
193
- os: process.platform,
194
- arch: process.arch,
195
- node_version: process.version,
196
- is_ci: !!process.env.CI,
197
- ...getEnvironmentProps(),
198
- });
195
+ // Identify user properties for telemetry (dedupe to once per day)
196
+ (async () => {
197
+ try {
198
+ const { getTelemetryConfig, saveTelemetryConfig } = await import("./lib/telemetry-config.ts");
199
+ const config = await getTelemetryConfig();
200
+ const today = new Date().toISOString().split("T")[0]; // "YYYY-MM-DD"
201
+
202
+ if (config.lastIdentifyDate === today) {
203
+ return; // Already identified today
204
+ }
205
+
206
+ identify({
207
+ jack_version: pkg.version,
208
+ os: process.platform,
209
+ arch: process.arch,
210
+ node_version: process.version,
211
+ });
212
+
213
+ // Update lastIdentifyDate
214
+ config.lastIdentifyDate = today;
215
+ await saveTelemetryConfig(config);
216
+ } catch {
217
+ // Fallback: just call identify without deduping
218
+ identify({
219
+ jack_version: pkg.version,
220
+ os: process.platform,
221
+ arch: process.arch,
222
+ node_version: process.version,
223
+ });
224
+ }
225
+ })();
199
226
 
200
227
  // Start non-blocking version check (skip for update command, CI, and help)
201
228
  const skipVersionCheck =
@@ -221,7 +248,7 @@ try {
221
248
  case "new":
222
249
  case "in": {
223
250
  const { default: newProject } = await import("./commands/new.ts");
224
- await withTelemetry("new", newProject)(args[0], {
251
+ await withTelemetry("new", newProject)(args[0], args[1], {
225
252
  template: cli.flags.template,
226
253
  intent: cli.flags.message,
227
254
  managed: cli.flags.managed,
@@ -235,7 +262,7 @@ try {
235
262
  case "vibe": {
236
263
  const { default: newProject } = await import("./commands/new.ts");
237
264
  // vibe always treats first arg as intent phrase
238
- await withTelemetry("vibe", newProject)(undefined, {
265
+ await withTelemetry("vibe", newProject)(undefined, undefined, {
239
266
  template: cli.flags.template,
240
267
  intent: args[0],
241
268
  });
@@ -269,14 +296,14 @@ try {
269
296
  }
270
297
  case "agents": {
271
298
  const { default: agents } = await import("./commands/agents.ts");
272
- await withTelemetry("agents", agents)(args[0], args.slice(1), {
299
+ await withTelemetry("agents", agents, { subcommand: args[0] })(args[0], args.slice(1), {
273
300
  project: cli.flags.project,
274
301
  });
275
302
  break;
276
303
  }
277
304
  case "tag": {
278
305
  const { default: tag } = await import("./commands/tag.ts");
279
- await withTelemetry("tag", tag)(args[0], args.slice(1));
306
+ await withTelemetry("tag", tag, { subcommand: args[0] })(args[0], args.slice(1));
280
307
  break;
281
308
  }
282
309
  case "sync": {
@@ -296,6 +323,16 @@ try {
296
323
  await withTelemetry("clone", clone)(args[0], { as: cli.flags.as });
297
324
  break;
298
325
  }
326
+ case "cd": {
327
+ const { default: cd } = await import("./commands/cd.ts");
328
+ await withTelemetry("cd", cd)(args[0]);
329
+ break;
330
+ }
331
+ case "shell-init": {
332
+ const { default: shellInit } = await import("./commands/shell-init.ts");
333
+ await shellInit();
334
+ break;
335
+ }
299
336
  case "telemetry": {
300
337
  const { default: telemetry } = await import("./commands/telemetry.ts");
301
338
  await telemetry(args[0]);
@@ -337,7 +374,7 @@ try {
337
374
  if (cli.flags.cloud) projectArgs.push("--cloud");
338
375
  if (cli.flags.yes) projectArgs.push("--yes");
339
376
  if (cli.flags.force) projectArgs.push("--force");
340
- await withTelemetry("projects", projects)(args[0], projectArgs);
377
+ await withTelemetry("projects", projects, { subcommand: args[0] })(args[0], projectArgs);
341
378
  break;
342
379
  }
343
380
  case "services": {
@@ -346,18 +383,32 @@ try {
346
383
  if (cli.flags.write) serviceArgs.push("--write");
347
384
  if (cli.flags.file) serviceArgs.push("--file", cli.flags.file);
348
385
  if (cli.flags.db) serviceArgs.push("--db", cli.flags.db);
349
- await withTelemetry("services", services)(args[0], serviceArgs, {
386
+
387
+ // Build subcommand: "db create", "storage list", or just "db"
388
+ const subcommand = args[0] ? (args[1] ? `${args[0]} ${args[1]}` : args[0]) : undefined;
389
+
390
+ await withTelemetry("services", services, { subcommand })(args[0], serviceArgs, {
350
391
  project: cli.flags.project,
351
392
  });
352
393
  break;
353
394
  }
354
395
  case "secrets": {
355
396
  const { default: secrets } = await import("./commands/secrets.ts");
356
- await withTelemetry("secrets", secrets)(args[0], args.slice(1), {
397
+ await withTelemetry("secrets", secrets, { subcommand: args[0] })(args[0], args.slice(1), {
357
398
  project: cli.flags.project,
358
399
  });
359
400
  break;
360
401
  }
402
+ case "domain": {
403
+ const { default: domain } = await import("./commands/domain.ts");
404
+ await withTelemetry("domain", domain, { subcommand: args[0] })(args[0], args.slice(1));
405
+ break;
406
+ }
407
+ case "domains": {
408
+ const { default: domains } = await import("./commands/domains.ts");
409
+ await withTelemetry("domains", domains)({ json: cli.flags.json });
410
+ break;
411
+ }
361
412
  case "mcp": {
362
413
  const { default: mcp } = await import("./commands/mcp.ts");
363
414
  // Note: Don't use withTelemetry wrapper for MCP serve - it runs indefinitely
@@ -367,23 +418,28 @@ try {
367
418
  case "ls": {
368
419
  const { default: projects } = await import("./commands/projects.ts");
369
420
  const lsArgs: string[] = [];
421
+ // First positional arg after 'ls' is the optional filter
422
+ if (args[0] && !args[0].startsWith("--")) {
423
+ lsArgs.push(args[0]);
424
+ }
370
425
  if (cli.flags.local) lsArgs.push("--local");
371
426
  if (cli.flags.deployed) lsArgs.push("--deployed");
372
427
  if (cli.flags.cloud) lsArgs.push("--cloud");
373
428
  if (cli.flags.all) lsArgs.push("--all");
374
429
  if (cli.flags.json) lsArgs.push("--json");
375
430
  if (cli.flags.status) lsArgs.push("--status", cli.flags.status);
431
+ if (cli.flags.sort) lsArgs.push("--sort", cli.flags.sort);
376
432
  if (cli.flags.tag) {
377
433
  for (const t of cli.flags.tag) {
378
434
  lsArgs.push("--tag", t);
379
435
  }
380
436
  }
381
- await withTelemetry("projects", projects)("list", lsArgs);
437
+ await withTelemetry("projects", projects, { subcommand: "list" })("list", lsArgs);
382
438
  break;
383
439
  }
384
440
  case "info": {
385
441
  const { default: projects } = await import("./commands/projects.ts");
386
- await withTelemetry("projects", projects)("info", args);
442
+ await withTelemetry("projects", projects, { subcommand: "info" })("info", args);
387
443
  break;
388
444
  }
389
445
  case "login": {
@@ -401,12 +457,16 @@ try {
401
457
  await withTelemetry("whoami", whoami)();
402
458
  break;
403
459
  }
404
- case "update":
405
- case "upgrade": {
460
+ case "update": {
406
461
  const { default: update } = await import("./commands/update.ts");
407
462
  await withTelemetry("update", update)();
408
463
  break;
409
464
  }
465
+ case "upgrade": {
466
+ const { default: upgrade } = await import("./commands/upgrade.ts");
467
+ await withTelemetry("upgrade", upgrade)();
468
+ break;
469
+ }
410
470
  case "feedback": {
411
471
  const { default: feedback } = await import("./commands/feedback.ts");
412
472
  await withTelemetry("feedback", feedback)();
@@ -428,8 +488,40 @@ try {
428
488
  await withTelemetry("unlink", unlink)();
429
489
  break;
430
490
  }
431
- default:
432
- cli.showHelp(command ? 1 : 0);
491
+ case "skillz":
492
+ case "skills": {
493
+ const { default: skills } = await import("./commands/skills.ts");
494
+ await withTelemetry("skills", skills, { subcommand: args[0] })(args[0], args.slice(1));
495
+ break;
496
+ }
497
+ default: {
498
+ // No command provided - show interactive picker if TTY, else help
499
+ if (!command) {
500
+ const { isTTY, requireTTY, pickProject } = await import("./lib/picker.ts");
501
+
502
+ if (!isTTY()) {
503
+ // Non-interactive: show help
504
+ cli.showHelp(0);
505
+ break;
506
+ }
507
+
508
+ requireTTY();
509
+
510
+ const result = await pickProject();
511
+
512
+ if (result.action === "cancel") {
513
+ break;
514
+ }
515
+
516
+ // User selected a project - run cd command
517
+ const { default: cd } = await import("./commands/cd.ts");
518
+ await withTelemetry("cd", cd)(result.project.name);
519
+ } else {
520
+ // Unknown command
521
+ cli.showHelp(1);
522
+ }
523
+ break;
524
+ }
433
525
  }
434
526
 
435
527
  // Show update notification if available (non-blocking check completed)
@@ -147,8 +147,7 @@ async function appendJackMdReferences(projectPath: string): Promise<string[]> {
147
147
 
148
148
  if (headingMatch && headingMatch.index !== undefined) {
149
149
  const insertPos = headingMatch.index + headingMatch[0].length;
150
- newContent =
151
- content.slice(0, insertPos) + "\n" + referenceBlock + content.slice(insertPos);
150
+ newContent = content.slice(0, insertPos) + "\n" + referenceBlock + content.slice(insertPos);
152
151
  } else {
153
152
  newContent = referenceBlock + content;
154
153
  }
package/src/lib/agents.ts CHANGED
@@ -6,8 +6,8 @@ import { type AgentConfig, type AgentLaunchConfig, readConfig, writeConfig } fro
6
6
  import { debug, isDebug } from "./debug.ts";
7
7
  import { restoreTty } from "./tty";
8
8
 
9
- // Re-export AgentConfig for consumers
10
- export type { AgentConfig } from "./config.ts";
9
+ // Re-export types for consumers
10
+ export type { AgentConfig, AgentLaunchConfig } from "./config.ts";
11
11
 
12
12
  /**
13
13
  * Project file to generate for an agent
@@ -2,13 +2,13 @@
2
2
  * Shared login flow for CLI and programmatic use
3
3
  */
4
4
  import { text } from "@clack/prompts";
5
- import { isCancel } from "../hooks.ts";
6
5
  import {
7
6
  checkUsernameAvailable,
8
7
  getCurrentUserProfile,
9
8
  registerUser,
10
9
  setUsername,
11
10
  } from "../control-plane.ts";
11
+ import { isCancel } from "../hooks.ts";
12
12
  import { promptSelect } from "../hooks.ts";
13
13
  import { celebrate, error, info, spinner, success, warn } from "../output.ts";
14
14
  import { identifyUser } from "../telemetry.ts";