@getjack/jack 0.1.2 → 0.1.4

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 (91) hide show
  1. package/README.md +77 -29
  2. package/package.json +54 -47
  3. package/src/commands/agents.ts +145 -10
  4. package/src/commands/down.ts +110 -102
  5. package/src/commands/feedback.ts +189 -0
  6. package/src/commands/init.ts +8 -12
  7. package/src/commands/login.ts +88 -0
  8. package/src/commands/logout.ts +14 -0
  9. package/src/commands/logs.ts +21 -0
  10. package/src/commands/mcp.ts +134 -7
  11. package/src/commands/new.ts +43 -17
  12. package/src/commands/open.ts +13 -6
  13. package/src/commands/projects.ts +269 -143
  14. package/src/commands/secrets.ts +413 -0
  15. package/src/commands/services.ts +96 -123
  16. package/src/commands/ship.ts +5 -1
  17. package/src/commands/whoami.ts +31 -0
  18. package/src/index.ts +218 -144
  19. package/src/lib/agent-files.ts +34 -0
  20. package/src/lib/agents.ts +390 -22
  21. package/src/lib/asset-hash.ts +50 -0
  22. package/src/lib/auth/client.ts +115 -0
  23. package/src/lib/auth/constants.ts +5 -0
  24. package/src/lib/auth/guard.ts +57 -0
  25. package/src/lib/auth/index.ts +18 -0
  26. package/src/lib/auth/store.ts +54 -0
  27. package/src/lib/binding-validator.ts +136 -0
  28. package/src/lib/build-helper.ts +211 -0
  29. package/src/lib/cloudflare-api.ts +24 -0
  30. package/src/lib/config.ts +5 -6
  31. package/src/lib/control-plane.ts +295 -0
  32. package/src/lib/debug.ts +3 -1
  33. package/src/lib/deploy-mode.ts +93 -0
  34. package/src/lib/deploy-upload.ts +92 -0
  35. package/src/lib/errors.ts +2 -0
  36. package/src/lib/github.ts +31 -1
  37. package/src/lib/hooks.ts +4 -12
  38. package/src/lib/intent.ts +88 -0
  39. package/src/lib/jsonc.ts +125 -0
  40. package/src/lib/local-paths.test.ts +902 -0
  41. package/src/lib/local-paths.ts +258 -0
  42. package/src/lib/managed-deploy.ts +175 -0
  43. package/src/lib/managed-down.ts +159 -0
  44. package/src/lib/mcp-config.ts +55 -34
  45. package/src/lib/names.ts +9 -29
  46. package/src/lib/project-operations.ts +676 -249
  47. package/src/lib/project-resolver.ts +476 -0
  48. package/src/lib/registry.ts +76 -37
  49. package/src/lib/resources.ts +196 -0
  50. package/src/lib/schema.ts +30 -1
  51. package/src/lib/storage/file-filter.ts +1 -0
  52. package/src/lib/storage/index.ts +5 -1
  53. package/src/lib/telemetry.ts +14 -0
  54. package/src/lib/tty.ts +15 -0
  55. package/src/lib/zip-packager.ts +255 -0
  56. package/src/mcp/resources/index.ts +8 -2
  57. package/src/mcp/server.ts +32 -4
  58. package/src/mcp/tools/index.ts +35 -13
  59. package/src/mcp/types.ts +6 -0
  60. package/src/mcp/utils.ts +1 -1
  61. package/src/templates/index.ts +42 -4
  62. package/src/templates/types.ts +13 -0
  63. package/templates/CLAUDE.md +166 -0
  64. package/templates/api/.jack.json +4 -0
  65. package/templates/api/bun.lock +1 -0
  66. package/templates/api/wrangler.jsonc +5 -0
  67. package/templates/hello/.jack.json +28 -0
  68. package/templates/hello/package.json +10 -0
  69. package/templates/hello/src/index.ts +11 -0
  70. package/templates/hello/tsconfig.json +11 -0
  71. package/templates/hello/wrangler.jsonc +5 -0
  72. package/templates/miniapp/.jack.json +15 -4
  73. package/templates/miniapp/bun.lock +135 -40
  74. package/templates/miniapp/index.html +1 -0
  75. package/templates/miniapp/package.json +3 -1
  76. package/templates/miniapp/public/.well-known/farcaster.json +7 -5
  77. package/templates/miniapp/public/icon.png +0 -0
  78. package/templates/miniapp/public/og.png +0 -0
  79. package/templates/miniapp/schema.sql +8 -0
  80. package/templates/miniapp/src/App.tsx +254 -3
  81. package/templates/miniapp/src/components/ShareSheet.tsx +147 -0
  82. package/templates/miniapp/src/hooks/useAI.ts +35 -0
  83. package/templates/miniapp/src/hooks/useGuestbook.ts +11 -1
  84. package/templates/miniapp/src/hooks/useShare.ts +76 -0
  85. package/templates/miniapp/src/index.css +15 -0
  86. package/templates/miniapp/src/lib/api.ts +2 -1
  87. package/templates/miniapp/src/worker.ts +515 -1
  88. package/templates/miniapp/wrangler.jsonc +15 -3
  89. package/LICENSE +0 -190
  90. package/src/commands/cloud.ts +0 -230
  91. package/templates/api/wrangler.toml +0 -3
@@ -1,18 +1,145 @@
1
- import { error, info } from "../lib/output.ts";
1
+ import { spawn } from "node:child_process";
2
+ import { error, info, success } from "../lib/output.ts";
2
3
  import { startMcpServer } from "../mcp/server.ts";
3
4
 
4
5
  interface McpOptions {
5
6
  project?: string;
7
+ debug?: boolean;
6
8
  }
7
9
 
8
10
  export default async function mcp(subcommand?: string, options: McpOptions = {}): Promise<void> {
9
- if (subcommand !== "serve") {
10
- error("Unknown subcommand. Use: jack mcp serve");
11
- info("Usage: jack mcp serve [--project /path/to/project]");
12
- process.exit(1);
11
+ if (subcommand === "serve") {
12
+ await startMcpServer({
13
+ projectPath: options.project,
14
+ debug: options.debug,
15
+ });
16
+ return;
17
+ }
18
+
19
+ if (subcommand === "test") {
20
+ await testMcpServer();
21
+ return;
13
22
  }
14
23
 
15
- await startMcpServer({
16
- projectPath: options.project,
24
+ error("Unknown subcommand. Use: jack mcp serve or jack mcp test");
25
+ info("Usage:");
26
+ info(" jack mcp serve [--project /path] [--debug] Start MCP server");
27
+ info(" jack mcp test Test MCP server connectivity");
28
+ process.exit(1);
29
+ }
30
+
31
+ /**
32
+ * Test MCP server by spawning it and sending test requests
33
+ */
34
+ async function testMcpServer(): Promise<void> {
35
+ info("Testing MCP server...\n");
36
+
37
+ const proc = spawn("./src/index.ts", ["mcp", "serve"], {
38
+ stdio: ["pipe", "pipe", "pipe"],
17
39
  });
40
+
41
+ const results: { test: string; passed: boolean; error?: string }[] = [];
42
+
43
+ const sendRequest = (id: number, method: string, params: object = {}): Promise<unknown> => {
44
+ return new Promise((resolve, reject) => {
45
+ const timeout = setTimeout(() => reject(new Error("Timeout")), 10000);
46
+
47
+ const handler = (data: Buffer) => {
48
+ const lines = data.toString().split("\n").filter(Boolean);
49
+ for (const line of lines) {
50
+ try {
51
+ const response = JSON.parse(line);
52
+ if (response.id === id) {
53
+ clearTimeout(timeout);
54
+ proc.stdout.off("data", handler);
55
+ if (response.error) {
56
+ reject(new Error(response.error.message));
57
+ } else {
58
+ resolve(response.result);
59
+ }
60
+ }
61
+ } catch {
62
+ // Not JSON, ignore
63
+ }
64
+ }
65
+ };
66
+
67
+ proc.stdout.on("data", handler);
68
+ proc.stdin.write(`${JSON.stringify({ jsonrpc: "2.0", method, params, id })}\n`);
69
+ });
70
+ };
71
+
72
+ try {
73
+ // Test 1: Initialize
74
+ info("1. Testing initialize...");
75
+ const initResult = (await sendRequest(1, "initialize", {
76
+ protocolVersion: "2024-11-05",
77
+ capabilities: {},
78
+ clientInfo: { name: "jack-mcp-test", version: "1.0" },
79
+ })) as { serverInfo?: { name: string; version: string } };
80
+ if (initResult?.serverInfo?.name === "jack") {
81
+ results.push({ test: "initialize", passed: true });
82
+ success(` ✓ Server info: ${initResult.serverInfo.name} v${initResult.serverInfo.version}`);
83
+ } else {
84
+ results.push({ test: "initialize", passed: false, error: "Invalid response" });
85
+ }
86
+
87
+ // Test 2: List tools
88
+ info("2. Testing tools/list...");
89
+ const toolsResult = (await sendRequest(2, "tools/list")) as { tools?: { name: string }[] };
90
+ const toolNames = toolsResult?.tools?.map((t) => t.name) ?? [];
91
+ if (toolNames.length > 0) {
92
+ results.push({ test: "tools/list", passed: true });
93
+ success(` ✓ Found ${toolNames.length} tools: ${toolNames.join(", ")}`);
94
+ } else {
95
+ results.push({ test: "tools/list", passed: false, error: "No tools found" });
96
+ }
97
+
98
+ // Test 3: List resources
99
+ info("3. Testing resources/list...");
100
+ const resourcesResult = (await sendRequest(3, "resources/list")) as {
101
+ resources?: { name: string }[];
102
+ };
103
+ const resourceCount = resourcesResult?.resources?.length ?? 0;
104
+ results.push({ test: "resources/list", passed: true });
105
+ success(` ✓ Found ${resourceCount} resource(s)`);
106
+
107
+ // Test 4: Call list_projects tool
108
+ info("4. Testing tools/call (list_projects)...");
109
+ const callResult = (await sendRequest(4, "tools/call", {
110
+ name: "list_projects",
111
+ arguments: {},
112
+ })) as { content?: { text: string }[] };
113
+ const responseText = callResult?.content?.[0]?.text;
114
+ if (responseText) {
115
+ const parsed = JSON.parse(responseText);
116
+ if (parsed.success) {
117
+ results.push({ test: "tools/call", passed: true });
118
+ success(` ✓ list_projects returned ${parsed.data?.length ?? 0} projects`);
119
+ } else {
120
+ results.push({ test: "tools/call", passed: false, error: parsed.error?.message });
121
+ }
122
+ }
123
+ } catch (err) {
124
+ const errorMsg = err instanceof Error ? err.message : String(err);
125
+ results.push({ test: "unknown", passed: false, error: errorMsg });
126
+ error(` ✗ Error: ${errorMsg}`);
127
+ } finally {
128
+ proc.kill();
129
+ }
130
+
131
+ // Summary
132
+ console.log("");
133
+ const passed = results.filter((r) => r.passed).length;
134
+ const failed = results.filter((r) => !r.passed).length;
135
+
136
+ if (failed === 0) {
137
+ success(`All ${passed} tests passed! MCP server is working correctly.`);
138
+ } else {
139
+ error(`${failed}/${results.length} tests failed.`);
140
+ for (const r of results.filter((r) => !r.passed)) {
141
+ error(` - ${r.test}: ${r.error}`);
142
+ }
143
+ process.exit(1);
144
+ }
18
145
  }
@@ -2,22 +2,48 @@ import { getPreferredLaunchAgent, launchAgent } from "../lib/agents.ts";
2
2
  import { debug } from "../lib/debug.ts";
3
3
  import { getErrorDetails } from "../lib/errors.ts";
4
4
  import { promptSelect } from "../lib/hooks.ts";
5
+ import { isIntentPhrase } from "../lib/intent.ts";
5
6
  import { output, spinner } from "../lib/output.ts";
6
7
  import { createProject } from "../lib/project-operations.ts";
7
8
 
8
9
  export default async function newProject(
9
- name?: string,
10
- options: { template?: string } = {},
10
+ nameOrPhrase?: string,
11
+ options: { template?: string; intent?: string; managed?: boolean; byo?: boolean; ci?: boolean } = {},
11
12
  ): Promise<void> {
12
13
  // Immediate feedback
13
14
  output.start("Starting...");
14
- debug("newProject called", { name, options });
15
- const isCi = process.env.CI === "true" || process.env.CI === "1";
15
+ debug("newProject called", { nameOrPhrase, options });
16
+ // CI mode: explicit --ci flag, JACK_CI env, or standard CI env
17
+ const isCi =
18
+ options.ci ||
19
+ process.env.JACK_CI === "1" ||
20
+ process.env.JACK_CI === "true" ||
21
+ process.env.CI === "true" ||
22
+ process.env.CI === "1";
23
+
24
+ // Determine if first arg is intent phrase or project name
25
+ let projectName: string | undefined;
26
+ let intentPhrase: string | undefined = options.intent;
27
+
28
+ if (nameOrPhrase) {
29
+ if (options.intent) {
30
+ // Explicit -m flag means first arg is definitely a name
31
+ projectName = nameOrPhrase;
32
+ } else if (isIntentPhrase(nameOrPhrase)) {
33
+ // Detected as intent phrase - name will be auto-generated
34
+ intentPhrase = nameOrPhrase;
35
+ projectName = undefined;
36
+ } else {
37
+ // Treat as project name
38
+ projectName = nameOrPhrase;
39
+ }
40
+ }
16
41
 
17
42
  let result: Awaited<ReturnType<typeof createProject>>;
18
43
  try {
19
- result = await createProject(name, {
44
+ result = await createProject(projectName, {
20
45
  template: options.template,
46
+ intent: intentPhrase,
21
47
  reporter: {
22
48
  start: output.start,
23
49
  stop: output.stop,
@@ -29,6 +55,8 @@ export default async function newProject(
29
55
  box: output.box,
30
56
  },
31
57
  interactive: !isCi,
58
+ managed: options.managed,
59
+ byo: options.byo,
32
60
  });
33
61
  } catch (error) {
34
62
  const details = getErrorDetails(error);
@@ -37,9 +65,9 @@ export default async function newProject(
37
65
  output.error(details.message);
38
66
  }
39
67
 
40
- const hasMissingSecrets = !!details.meta?.missingSecrets?.length;
41
- if (hasMissingSecrets) {
42
- for (const key of details.meta.missingSecrets) {
68
+ const missingSecrets = details.meta?.missingSecrets;
69
+ if (missingSecrets?.length) {
70
+ for (const key of missingSecrets) {
43
71
  output.info(` Run: jack secrets add ${key}`);
44
72
  }
45
73
  }
@@ -48,7 +76,7 @@ export default async function newProject(
48
76
  console.error(details.meta.stderr);
49
77
  }
50
78
 
51
- if (details.suggestion && !details.meta?.reported && !hasMissingSecrets) {
79
+ if (details.suggestion && !details.meta?.reported && !missingSecrets?.length) {
52
80
  output.info(details.suggestion);
53
81
  }
54
82
 
@@ -63,8 +91,8 @@ export default async function newProject(
63
91
  console.error("");
64
92
  output.info(`Project: ${result.targetDir}`);
65
93
 
66
- // Prompt to open preferred agent (only in interactive TTY)
67
- if (process.stdout.isTTY) {
94
+ // Prompt to open preferred agent (only in interactive TTY, skip in CI mode)
95
+ if (process.stdout.isTTY && !isCi) {
68
96
  const preferred = await getPreferredLaunchAgent();
69
97
  if (preferred) {
70
98
  console.error("");
@@ -73,12 +101,10 @@ export default async function newProject(
73
101
  const choice = await promptSelect(["Yes", "No"]);
74
102
 
75
103
  if (choice === 0) {
76
- // Ensure terminal is in normal state before handoff
77
- if (process.stdin.isTTY) {
78
- process.stdin.setRawMode(false);
79
- }
80
-
81
- const launchResult = await launchAgent(preferred.launch, result.targetDir);
104
+ const launchResult = await launchAgent(preferred.launch, result.targetDir, {
105
+ projectName: result.projectName,
106
+ url: result.workerUrl,
107
+ });
82
108
  if (!launchResult.success) {
83
109
  output.warn(`Failed to launch ${preferred.definition.name}`);
84
110
  if (launchResult.command?.length) {
@@ -1,6 +1,6 @@
1
1
  import { $ } from "bun";
2
2
  import { error, info } from "../lib/output.ts";
3
- import { getProject } from "../lib/registry.ts";
3
+ import { resolveProject } from "../lib/project-resolver.ts";
4
4
  import { getProjectNameFromDir } from "../lib/storage/index.ts";
5
5
 
6
6
  export interface OpenFlags {
@@ -22,19 +22,26 @@ export default async function open(projectName?: string, flags: OpenFlags = {}):
22
22
  }
23
23
  }
24
24
 
25
- // Get project from registry
26
- const project = await getProject(name);
25
+ // Resolve project from registry and control plane
26
+ const project = await resolveProject(name);
27
27
 
28
- // Determine URL based on flags
28
+ // Determine URL based on flags and deploy mode
29
29
  let url: string;
30
30
 
31
31
  if (flags.dash) {
32
+ // Dashboard URLs - use Cloudflare dash for now
32
33
  url = `https://dash.cloudflare.com/workers/services/view/${name}`;
33
34
  } else if (flags.logs) {
34
35
  url = `https://dash.cloudflare.com/workers/services/view/${name}/logs`;
35
36
  } else {
36
- // Default: use worker URL from registry or construct it
37
- url = project?.workerUrl || `https://${name}.workers.dev`;
37
+ // Default: use mode-appropriate URL
38
+ if (project?.sources.controlPlane && project.url) {
39
+ // Managed project: use runjack URL
40
+ url = project.url;
41
+ } else {
42
+ // BYO project: use workers.dev URL or stored URL
43
+ url = project?.url || `https://${name}.workers.dev`;
44
+ }
38
45
  }
39
46
 
40
47
  // Open browser using platform-specific command