@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.
- package/package.json +1 -1
- package/src/commands/cd.ts +163 -0
- package/src/commands/clone.ts +112 -68
- package/src/commands/domain.ts +506 -0
- package/src/commands/domains.ts +215 -0
- package/src/commands/down.ts +18 -12
- package/src/commands/hack.ts +185 -8
- package/src/commands/init.ts +52 -1
- package/src/commands/link.ts +25 -43
- package/src/commands/logs.ts +2 -2
- package/src/commands/mcp.ts +74 -3
- package/src/commands/new.ts +48 -54
- package/src/commands/projects.ts +53 -10
- package/src/commands/secrets.ts +5 -1
- package/src/commands/services.ts +16 -4
- package/src/commands/shell-init.ts +43 -0
- package/src/commands/ship.ts +2 -11
- package/src/commands/skills.ts +335 -0
- package/src/commands/update.ts +31 -0
- package/src/commands/upgrade.ts +14 -0
- package/src/index.ts +116 -24
- package/src/lib/agent-integration.ts +1 -2
- package/src/lib/agents.ts +2 -2
- package/src/lib/auth/login-flow.ts +1 -1
- package/src/lib/clone-core.ts +252 -0
- package/src/lib/config.ts +22 -0
- package/src/lib/control-plane.ts +31 -5
- package/src/lib/fuzzy.ts +93 -0
- package/src/lib/managed-deploy.ts +4 -1
- package/src/lib/managed-down.ts +20 -5
- package/src/lib/output.ts +90 -9
- package/src/lib/picker.ts +406 -0
- package/src/lib/project-detection.ts +5 -2
- package/src/lib/project-list.ts +66 -5
- package/src/lib/project-operations.ts +68 -6
- package/src/lib/prompts.ts +1 -1
- package/src/lib/services/db-execute.ts +8 -1
- package/src/lib/services/db-list.ts +4 -1
- package/src/lib/services/domain-operations.ts +379 -0
- package/src/lib/services/storage-config.ts +1 -5
- package/src/lib/services/storage-delete.ts +1 -1
- package/src/lib/services/storage-info.ts +2 -4
- package/src/lib/services/vectorize-config.ts +1 -5
- package/src/lib/services/vectorize-create.ts +3 -1
- package/src/lib/shell-integration.ts +202 -0
- package/src/lib/telemetry-config.ts +50 -4
- package/src/lib/telemetry.ts +71 -2
- package/src/lib/version-check.ts +1 -3
- package/src/lib/wrangler-config.test.ts +2 -2
- package/src/lib/wrangler-config.ts +1 -1
- package/src/lib/zip-packager.ts +1 -3
- package/src/mcp/tools/index.ts +261 -7
- package/src/templates/index.ts +10 -1
- package/templates/ai-chat/.jack.json +1 -5
- package/templates/ai-chat/public/chat.js +130 -130
- package/templates/ai-chat/src/index.ts +9 -13
- package/templates/ai-chat/src/jack-ai.ts +6 -2
- package/templates/saas/.jack.json +6 -1
- package/templates/saas/src/auth.ts +8 -4
- package/templates/saas/src/client/App.tsx +22 -7
- package/templates/saas/src/client/components/ProtectedRoute.tsx +9 -2
- package/templates/saas/src/client/components/ThemeToggle.tsx +1 -6
- package/templates/saas/src/client/components/ui/accordion.tsx +1 -1
- package/templates/saas/src/client/components/ui/alert-dialog.tsx +2 -2
- package/templates/saas/src/client/components/ui/alert.tsx +2 -2
- package/templates/saas/src/client/components/ui/avatar.tsx +1 -1
- package/templates/saas/src/client/components/ui/badge.tsx +2 -2
- package/templates/saas/src/client/components/ui/breadcrumb.tsx +1 -1
- package/templates/saas/src/client/components/ui/button-group.tsx +2 -2
- package/templates/saas/src/client/components/ui/button.tsx +2 -2
- package/templates/saas/src/client/components/ui/card.tsx +1 -1
- package/templates/saas/src/client/components/ui/carousel.tsx +2 -2
- package/templates/saas/src/client/components/ui/checkbox.tsx +1 -1
- package/templates/saas/src/client/components/ui/command.tsx +2 -2
- package/templates/saas/src/client/components/ui/context-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/dialog.tsx +1 -1
- package/templates/saas/src/client/components/ui/drawer.tsx +1 -1
- package/templates/saas/src/client/components/ui/dropdown-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/empty.tsx +1 -1
- package/templates/saas/src/client/components/ui/field.tsx +2 -2
- package/templates/saas/src/client/components/ui/form.tsx +5 -5
- package/templates/saas/src/client/components/ui/hover-card.tsx +1 -1
- package/templates/saas/src/client/components/ui/input-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/input-otp.tsx +1 -1
- package/templates/saas/src/client/components/ui/input.tsx +1 -1
- package/templates/saas/src/client/components/ui/item.tsx +3 -3
- package/templates/saas/src/client/components/ui/label.tsx +1 -1
- package/templates/saas/src/client/components/ui/menubar.tsx +1 -1
- package/templates/saas/src/client/components/ui/navigation-menu.tsx +1 -1
- package/templates/saas/src/client/components/ui/pagination.tsx +2 -2
- package/templates/saas/src/client/components/ui/popover.tsx +1 -1
- package/templates/saas/src/client/components/ui/progress.tsx +1 -1
- package/templates/saas/src/client/components/ui/radio-group.tsx +1 -1
- package/templates/saas/src/client/components/ui/resizable.tsx +1 -1
- package/templates/saas/src/client/components/ui/scroll-area.tsx +1 -1
- package/templates/saas/src/client/components/ui/select.tsx +1 -1
- package/templates/saas/src/client/components/ui/separator.tsx +1 -1
- package/templates/saas/src/client/components/ui/sheet.tsx +1 -1
- package/templates/saas/src/client/components/ui/sidebar.tsx +4 -4
- package/templates/saas/src/client/components/ui/slider.tsx +1 -1
- package/templates/saas/src/client/components/ui/switch.tsx +1 -1
- package/templates/saas/src/client/components/ui/table.tsx +1 -1
- package/templates/saas/src/client/components/ui/tabs.tsx +1 -1
- package/templates/saas/src/client/components/ui/textarea.tsx +1 -1
- package/templates/saas/src/client/components/ui/toggle-group.tsx +3 -3
- package/templates/saas/src/client/components/ui/toggle.tsx +2 -2
- package/templates/saas/src/client/components/ui/tooltip.tsx +1 -1
- package/templates/saas/src/client/hooks/useSubscription.ts +5 -4
- package/templates/saas/src/client/lib/auth-client.ts +1 -1
- package/templates/saas/src/client/lib/plans.ts +1 -6
- package/templates/saas/src/client/lib/utils.ts +1 -1
- package/templates/saas/src/client/main.tsx +1 -1
- package/templates/saas/src/client/pages/DashboardPage.tsx +41 -9
- package/templates/saas/src/client/pages/ForgotPasswordPage.tsx +11 -2
- package/templates/saas/src/client/pages/HomePage.tsx +11 -2
- package/templates/saas/src/client/pages/LoginPage.tsx +11 -2
- package/templates/saas/src/client/pages/PricingPage.tsx +20 -10
- package/templates/saas/src/client/pages/ResetPasswordPage.tsx +14 -11
- package/templates/saas/src/client/pages/SignupPage.tsx +11 -2
- package/templates/saas/src/index.ts +28 -19
- package/templates/saas/vite.config.ts +1 -1
- package/templates/semantic-search/.jack.json +1 -5
- package/templates/semantic-search/src/index.ts +8 -4
- package/templates/semantic-search/src/jack-ai.ts +6 -2
- 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
|
+
}
|
package/src/commands/update.ts
CHANGED
|
@@ -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 {
|
|
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 [
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
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
|
|
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";
|