@getjack/jack 0.1.30 → 0.1.32
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 +4 -5
- package/src/commands/domain.ts +148 -17
- package/src/commands/domains.ts +28 -2
- package/src/commands/services.ts +389 -20
- package/src/commands/ship.ts +28 -3
- package/src/commands/skills.ts +58 -1
- package/src/commands/tokens.ts +119 -0
- package/src/commands/whoami.ts +10 -6
- package/src/index.ts +17 -0
- package/src/lib/auth/client.ts +11 -1
- package/src/lib/auth/guard.ts +1 -1
- package/src/lib/auth/login-flow.ts +34 -0
- package/src/lib/auth/store.ts +3 -0
- package/src/lib/control-plane.ts +156 -0
- package/src/lib/mcp-config.ts +26 -4
- package/src/lib/output.ts +4 -2
- package/src/lib/picker.ts +3 -1
- package/src/lib/project-operations.ts +38 -4
- package/src/lib/services/cron-create.ts +73 -0
- package/src/lib/services/cron-delete.ts +66 -0
- package/src/lib/services/cron-list.ts +59 -0
- package/src/lib/services/cron-test.ts +93 -0
- package/src/lib/services/cron-utils.ts +78 -0
- package/src/lib/services/domain-operations.ts +89 -18
- package/src/lib/services/token-operations.ts +84 -0
- package/src/lib/telemetry.ts +4 -0
- package/src/mcp/resources/index.ts +173 -0
- package/src/mcp/server.ts +20 -0
- package/src/mcp/tools/index.ts +279 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron schedule creation logic for jack services cron create
|
|
3
|
+
*
|
|
4
|
+
* Validates cron expression, checks minimum interval, and calls control plane API.
|
|
5
|
+
* Only supported for managed (Jack Cloud) projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createCronSchedule as createCronScheduleApi } from "../control-plane.ts";
|
|
9
|
+
import { readProjectLink } from "../project-link.ts";
|
|
10
|
+
import { checkMinimumInterval, validateCronExpression } from "./cron-utils.ts";
|
|
11
|
+
|
|
12
|
+
// Minimum interval between cron runs (in minutes)
|
|
13
|
+
const MIN_INTERVAL_MINUTES = 15;
|
|
14
|
+
|
|
15
|
+
export interface CreateCronScheduleOptions {
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CreateCronScheduleResult {
|
|
20
|
+
id: string;
|
|
21
|
+
expression: string;
|
|
22
|
+
description: string;
|
|
23
|
+
nextRunAt: string;
|
|
24
|
+
created: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a cron schedule for the current project.
|
|
29
|
+
*
|
|
30
|
+
* For managed projects: calls control plane POST /v1/projects/:id/crons
|
|
31
|
+
* For BYO projects: throws error (not supported)
|
|
32
|
+
*/
|
|
33
|
+
export async function createCronSchedule(
|
|
34
|
+
projectDir: string,
|
|
35
|
+
expression: string,
|
|
36
|
+
options: CreateCronScheduleOptions = {},
|
|
37
|
+
): Promise<CreateCronScheduleResult> {
|
|
38
|
+
// Validate expression
|
|
39
|
+
const validation = validateCronExpression(expression);
|
|
40
|
+
if (!validation.valid) {
|
|
41
|
+
throw new Error(`Invalid cron expression: ${validation.error}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const normalizedExpression = validation.normalized!;
|
|
45
|
+
|
|
46
|
+
// Check minimum interval (15 minutes)
|
|
47
|
+
if (!checkMinimumInterval(normalizedExpression, MIN_INTERVAL_MINUTES)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Cron schedules must run at least ${MIN_INTERVAL_MINUTES} minutes apart. ` +
|
|
50
|
+
"This limit helps ensure reliable execution.",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Must be managed mode
|
|
55
|
+
const link = await readProjectLink(projectDir);
|
|
56
|
+
if (!link || link.deploy_mode !== "managed") {
|
|
57
|
+
throw new Error(
|
|
58
|
+
"Cron schedules are only supported for Jack Cloud (managed) projects.\n" +
|
|
59
|
+
"BYO projects can use native Cloudflare cron triggers in wrangler.toml.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Create via control plane
|
|
64
|
+
const result = await createCronScheduleApi(link.project_id, normalizedExpression);
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
id: result.id,
|
|
68
|
+
expression: result.expression,
|
|
69
|
+
description: result.description,
|
|
70
|
+
nextRunAt: result.next_run_at,
|
|
71
|
+
created: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron schedule deletion logic for jack services cron delete
|
|
3
|
+
*
|
|
4
|
+
* Finds schedule by expression and deletes via control plane API.
|
|
5
|
+
* Only supported for managed (Jack Cloud) projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
deleteCronSchedule as deleteCronScheduleApi,
|
|
10
|
+
listCronSchedules as listCronSchedulesApi,
|
|
11
|
+
} from "../control-plane.ts";
|
|
12
|
+
import { readProjectLink } from "../project-link.ts";
|
|
13
|
+
import { normalizeCronExpression } from "./cron-utils.ts";
|
|
14
|
+
|
|
15
|
+
export interface DeleteCronScheduleOptions {
|
|
16
|
+
interactive?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface DeleteCronScheduleResult {
|
|
20
|
+
expression: string;
|
|
21
|
+
deleted: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Delete a cron schedule by its expression.
|
|
26
|
+
*
|
|
27
|
+
* For managed projects: finds the schedule by expression and calls DELETE API
|
|
28
|
+
* For BYO projects: throws error (not supported)
|
|
29
|
+
*/
|
|
30
|
+
export async function deleteCronSchedule(
|
|
31
|
+
projectDir: string,
|
|
32
|
+
expression: string,
|
|
33
|
+
options: DeleteCronScheduleOptions = {},
|
|
34
|
+
): Promise<DeleteCronScheduleResult> {
|
|
35
|
+
const normalizedExpression = normalizeCronExpression(expression);
|
|
36
|
+
|
|
37
|
+
// Must be managed mode
|
|
38
|
+
const link = await readProjectLink(projectDir);
|
|
39
|
+
if (!link || link.deploy_mode !== "managed") {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"Cron schedules are only supported for Jack Cloud (managed) projects.\n" +
|
|
42
|
+
"BYO projects can use native Cloudflare cron triggers in wrangler.toml.",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Find the schedule by expression
|
|
47
|
+
const schedules = await listCronSchedulesApi(link.project_id);
|
|
48
|
+
const schedule = schedules.find(
|
|
49
|
+
(s) => normalizeCronExpression(s.expression) === normalizedExpression,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!schedule) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`No cron schedule found with expression "${normalizedExpression}".\n` +
|
|
55
|
+
"Use 'jack services cron list' to see all schedules.",
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Delete via control plane
|
|
60
|
+
await deleteCronScheduleApi(link.project_id, schedule.id);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
expression: normalizedExpression,
|
|
64
|
+
deleted: true,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron schedule listing logic for jack services cron list
|
|
3
|
+
*
|
|
4
|
+
* Fetches cron schedules from control plane API.
|
|
5
|
+
* Only supported for managed (Jack Cloud) projects.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type CronScheduleInfo,
|
|
10
|
+
listCronSchedules as listCronSchedulesApi,
|
|
11
|
+
} from "../control-plane.ts";
|
|
12
|
+
import { readProjectLink } from "../project-link.ts";
|
|
13
|
+
|
|
14
|
+
export interface CronScheduleListEntry {
|
|
15
|
+
id: string;
|
|
16
|
+
expression: string;
|
|
17
|
+
description: string;
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
nextRunAt: string;
|
|
20
|
+
lastRunAt: string | null;
|
|
21
|
+
lastRunStatus: string | null;
|
|
22
|
+
lastRunDurationMs: number | null;
|
|
23
|
+
consecutiveFailures: number;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* List all cron schedules for the current project.
|
|
29
|
+
*
|
|
30
|
+
* For managed projects: calls control plane GET /v1/projects/:id/crons
|
|
31
|
+
* For BYO projects: throws error (not supported)
|
|
32
|
+
*/
|
|
33
|
+
export async function listCronSchedules(projectDir: string): Promise<CronScheduleListEntry[]> {
|
|
34
|
+
// Must be managed mode
|
|
35
|
+
const link = await readProjectLink(projectDir);
|
|
36
|
+
if (!link || link.deploy_mode !== "managed") {
|
|
37
|
+
throw new Error(
|
|
38
|
+
"Cron schedules are only supported for Jack Cloud (managed) projects.\n" +
|
|
39
|
+
"BYO projects can use native Cloudflare cron triggers in wrangler.toml.",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Fetch from control plane
|
|
44
|
+
const schedules = await listCronSchedulesApi(link.project_id);
|
|
45
|
+
|
|
46
|
+
// Map to our format
|
|
47
|
+
return schedules.map((s: CronScheduleInfo) => ({
|
|
48
|
+
id: s.id,
|
|
49
|
+
expression: s.expression,
|
|
50
|
+
description: s.description,
|
|
51
|
+
enabled: s.enabled,
|
|
52
|
+
nextRunAt: s.next_run_at,
|
|
53
|
+
lastRunAt: s.last_run_at,
|
|
54
|
+
lastRunStatus: s.last_run_status,
|
|
55
|
+
lastRunDurationMs: s.last_run_duration_ms,
|
|
56
|
+
consecutiveFailures: s.consecutive_failures,
|
|
57
|
+
createdAt: s.created_at,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron schedule testing logic for jack services cron test
|
|
3
|
+
*
|
|
4
|
+
* Validates expression, shows human-readable description and next times.
|
|
5
|
+
* Optionally triggers the schedule on production (managed projects only).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { triggerCronSchedule as triggerCronScheduleApi } from "../control-plane.ts";
|
|
9
|
+
import { readProjectLink } from "../project-link.ts";
|
|
10
|
+
import {
|
|
11
|
+
describeCronExpression,
|
|
12
|
+
getNextScheduledTimes,
|
|
13
|
+
validateCronExpression,
|
|
14
|
+
} from "./cron-utils.ts";
|
|
15
|
+
|
|
16
|
+
export interface CronTestOptions {
|
|
17
|
+
triggerProduction?: boolean;
|
|
18
|
+
interactive?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CronTestResult {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
expression?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
nextTimes?: Date[];
|
|
27
|
+
triggerResult?: {
|
|
28
|
+
triggered: boolean;
|
|
29
|
+
status: string;
|
|
30
|
+
durationMs: number;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Test a cron expression: validate, describe, and show next run times.
|
|
36
|
+
* Optionally trigger the schedule handler on production.
|
|
37
|
+
*/
|
|
38
|
+
export async function testCronExpression(
|
|
39
|
+
projectDir: string,
|
|
40
|
+
expression: string,
|
|
41
|
+
options: CronTestOptions = {},
|
|
42
|
+
): Promise<CronTestResult> {
|
|
43
|
+
// Validate expression
|
|
44
|
+
const validation = validateCronExpression(expression);
|
|
45
|
+
if (!validation.valid) {
|
|
46
|
+
return {
|
|
47
|
+
valid: false,
|
|
48
|
+
error: validation.error,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const normalizedExpression = validation.normalized!;
|
|
53
|
+
|
|
54
|
+
// Get description and next times
|
|
55
|
+
const description = describeCronExpression(normalizedExpression);
|
|
56
|
+
const nextTimes = getNextScheduledTimes(normalizedExpression, 5);
|
|
57
|
+
|
|
58
|
+
const result: CronTestResult = {
|
|
59
|
+
valid: true,
|
|
60
|
+
expression: normalizedExpression,
|
|
61
|
+
description,
|
|
62
|
+
nextTimes,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Optionally trigger production
|
|
66
|
+
if (options.triggerProduction) {
|
|
67
|
+
const link = await readProjectLink(projectDir);
|
|
68
|
+
if (!link || link.deploy_mode !== "managed") {
|
|
69
|
+
throw new Error("Production trigger is only available for Jack Cloud (managed) projects.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const triggerResponse = await triggerCronScheduleApi(link.project_id, normalizedExpression);
|
|
73
|
+
result.triggerResult = {
|
|
74
|
+
triggered: triggerResponse.triggered,
|
|
75
|
+
status: triggerResponse.status,
|
|
76
|
+
durationMs: triggerResponse.duration_ms,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Common cron expression patterns for help output.
|
|
85
|
+
*/
|
|
86
|
+
export const COMMON_CRON_PATTERNS = [
|
|
87
|
+
{ expression: "*/15 * * * *", description: "Every 15 minutes" },
|
|
88
|
+
{ expression: "0 * * * *", description: "Every hour" },
|
|
89
|
+
{ expression: "0 0 * * *", description: "Daily at midnight" },
|
|
90
|
+
{ expression: "0 9 * * *", description: "Daily at 9am" },
|
|
91
|
+
{ expression: "0 9 * * 1", description: "Every Monday at 9am" },
|
|
92
|
+
{ expression: "0 0 1 * *", description: "First day of every month" },
|
|
93
|
+
];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron expression utilities for validation, parsing, and human-readable descriptions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import parser from "cron-parser";
|
|
6
|
+
import cronstrue from "cronstrue";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a cron expression by collapsing whitespace.
|
|
10
|
+
* "0 * * * *" -> "0 * * * *"
|
|
11
|
+
*/
|
|
12
|
+
export function normalizeCronExpression(expression: string): string {
|
|
13
|
+
return expression.trim().replace(/\s+/g, " ");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validate a cron expression.
|
|
18
|
+
* Returns normalized expression if valid, error message if invalid.
|
|
19
|
+
*/
|
|
20
|
+
export function validateCronExpression(expression: string): {
|
|
21
|
+
valid: boolean;
|
|
22
|
+
error?: string;
|
|
23
|
+
normalized?: string;
|
|
24
|
+
} {
|
|
25
|
+
try {
|
|
26
|
+
const normalized = normalizeCronExpression(expression);
|
|
27
|
+
parser.parseExpression(normalized);
|
|
28
|
+
return { valid: true, normalized };
|
|
29
|
+
} catch (e) {
|
|
30
|
+
return { valid: false, error: e instanceof Error ? e.message : String(e) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a cron expression has at least the minimum interval between runs.
|
|
36
|
+
* Returns true if the interval is >= minMinutes.
|
|
37
|
+
*/
|
|
38
|
+
export function checkMinimumInterval(expression: string, minMinutes: number): boolean {
|
|
39
|
+
try {
|
|
40
|
+
const interval = parser.parseExpression(expression);
|
|
41
|
+
const first = interval.next().toDate();
|
|
42
|
+
const second = interval.next().toDate();
|
|
43
|
+
return (second.getTime() - first.getTime()) / 1000 / 60 >= minMinutes;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get the next N scheduled times for a cron expression.
|
|
51
|
+
*/
|
|
52
|
+
export function getNextScheduledTimes(expression: string, count: number): Date[] {
|
|
53
|
+
const interval = parser.parseExpression(expression);
|
|
54
|
+
const times: Date[] = [];
|
|
55
|
+
for (let i = 0; i < count; i++) {
|
|
56
|
+
times.push(interval.next().toDate());
|
|
57
|
+
}
|
|
58
|
+
return times;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get a human-readable description of a cron expression.
|
|
63
|
+
* e.g., "0 * * * *" -> "At minute 0"
|
|
64
|
+
*/
|
|
65
|
+
export function describeCronExpression(expression: string): string {
|
|
66
|
+
try {
|
|
67
|
+
return cronstrue.toString(expression);
|
|
68
|
+
} catch {
|
|
69
|
+
return expression;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Compute the next run time for a cron expression as ISO string.
|
|
75
|
+
*/
|
|
76
|
+
export function computeNextRun(expression: string): string {
|
|
77
|
+
return parser.parseExpression(expression).next().toDate().toISOString();
|
|
78
|
+
}
|
|
@@ -15,14 +15,18 @@ import { JackError, JackErrorCode } from "../errors.ts";
|
|
|
15
15
|
|
|
16
16
|
export type DomainStatus =
|
|
17
17
|
| "claimed"
|
|
18
|
+
| "unassigned"
|
|
18
19
|
| "pending"
|
|
20
|
+
| "pending_dns"
|
|
19
21
|
| "pending_owner"
|
|
20
22
|
| "pending_ssl"
|
|
21
23
|
| "active"
|
|
22
24
|
| "blocked"
|
|
23
25
|
| "moved"
|
|
24
26
|
| "failed"
|
|
25
|
-
| "deleting"
|
|
27
|
+
| "deleting"
|
|
28
|
+
| "expired"
|
|
29
|
+
| "deleted";
|
|
26
30
|
|
|
27
31
|
export interface DomainVerification {
|
|
28
32
|
type: "cname";
|
|
@@ -36,6 +40,22 @@ export interface DomainOwnershipVerification {
|
|
|
36
40
|
value: string;
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
export interface DomainDns {
|
|
44
|
+
verified: boolean;
|
|
45
|
+
checked_at: string | null;
|
|
46
|
+
current_target: string | null;
|
|
47
|
+
expected_target: string | null;
|
|
48
|
+
error: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface DomainNextStep {
|
|
52
|
+
action: string;
|
|
53
|
+
record_type?: string;
|
|
54
|
+
record_name?: string;
|
|
55
|
+
record_value?: string;
|
|
56
|
+
message?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
export interface DomainInfo {
|
|
40
60
|
id: string;
|
|
41
61
|
hostname: string;
|
|
@@ -45,6 +65,8 @@ export interface DomainInfo {
|
|
|
45
65
|
project_slug: string | null;
|
|
46
66
|
verification?: DomainVerification;
|
|
47
67
|
ownership_verification?: DomainOwnershipVerification;
|
|
68
|
+
dns?: DomainDns;
|
|
69
|
+
next_step?: DomainNextStep;
|
|
48
70
|
created_at: string;
|
|
49
71
|
}
|
|
50
72
|
|
|
@@ -96,16 +118,20 @@ interface ListDomainsApiResponse {
|
|
|
96
118
|
}
|
|
97
119
|
|
|
98
120
|
interface ConnectDomainApiResponse {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
121
|
+
domain: {
|
|
122
|
+
id: string;
|
|
123
|
+
hostname: string;
|
|
124
|
+
status: string;
|
|
125
|
+
};
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
interface AssignDomainApiResponse {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
129
|
+
domain: {
|
|
130
|
+
id: string;
|
|
131
|
+
hostname: string;
|
|
132
|
+
status: string;
|
|
133
|
+
ssl_status: string | null;
|
|
134
|
+
};
|
|
109
135
|
verification?: DomainVerification;
|
|
110
136
|
ownership_verification?: DomainOwnershipVerification;
|
|
111
137
|
}
|
|
@@ -215,9 +241,9 @@ export async function connectDomain(hostname: string): Promise<ConnectDomainResu
|
|
|
215
241
|
|
|
216
242
|
const data = (await response.json()) as ConnectDomainApiResponse;
|
|
217
243
|
return {
|
|
218
|
-
id: data.id,
|
|
219
|
-
hostname: data.hostname,
|
|
220
|
-
status: data.status as DomainStatus,
|
|
244
|
+
id: data.domain.id,
|
|
245
|
+
hostname: data.domain.hostname,
|
|
246
|
+
status: data.domain.status as DomainStatus,
|
|
221
247
|
};
|
|
222
248
|
}
|
|
223
249
|
|
|
@@ -282,10 +308,10 @@ export async function assignDomain(
|
|
|
282
308
|
|
|
283
309
|
const data = (await response.json()) as AssignDomainApiResponse;
|
|
284
310
|
return {
|
|
285
|
-
id: data.id,
|
|
286
|
-
hostname: data.hostname,
|
|
287
|
-
status: data.status as DomainStatus,
|
|
288
|
-
ssl_status: data.ssl_status,
|
|
311
|
+
id: data.domain.id,
|
|
312
|
+
hostname: data.domain.hostname,
|
|
313
|
+
status: data.domain.status as DomainStatus,
|
|
314
|
+
ssl_status: data.domain.ssl_status,
|
|
289
315
|
project_id: project.id,
|
|
290
316
|
project_slug: projectSlug,
|
|
291
317
|
verification: data.verification,
|
|
@@ -334,10 +360,13 @@ export async function unassignDomain(hostname: string): Promise<UnassignDomainRe
|
|
|
334
360
|
);
|
|
335
361
|
}
|
|
336
362
|
|
|
363
|
+
const data = (await response.json()) as {
|
|
364
|
+
domain: { id: string; hostname: string; status: string };
|
|
365
|
+
};
|
|
337
366
|
return {
|
|
338
|
-
id: domain.id,
|
|
339
|
-
hostname: domain.hostname,
|
|
340
|
-
status:
|
|
367
|
+
id: data.domain.id,
|
|
368
|
+
hostname: data.domain.hostname,
|
|
369
|
+
status: data.domain.status as DomainStatus,
|
|
341
370
|
};
|
|
342
371
|
}
|
|
343
372
|
|
|
@@ -377,3 +406,45 @@ export async function disconnectDomain(hostname: string): Promise<DisconnectDoma
|
|
|
377
406
|
hostname: domain.hostname,
|
|
378
407
|
};
|
|
379
408
|
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Verify DNS configuration for a domain.
|
|
412
|
+
*
|
|
413
|
+
* @throws JackError with RESOURCE_NOT_FOUND if domain not found
|
|
414
|
+
*/
|
|
415
|
+
export interface VerifyDomainResult {
|
|
416
|
+
domain: DomainInfo;
|
|
417
|
+
dns_check?: {
|
|
418
|
+
verified: boolean;
|
|
419
|
+
target: string | null;
|
|
420
|
+
error: string | null;
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function verifyDomain(hostname: string): Promise<VerifyDomainResult> {
|
|
425
|
+
const domain = await getDomainByHostname(hostname);
|
|
426
|
+
if (!domain) {
|
|
427
|
+
throw new JackError(
|
|
428
|
+
JackErrorCode.PROJECT_NOT_FOUND,
|
|
429
|
+
`Domain not found: ${hostname}`,
|
|
430
|
+
"Run 'jack domain' to see all domains",
|
|
431
|
+
{ exitCode: 1 },
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/domains/${domain.id}/verify`, {
|
|
436
|
+
method: "POST",
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
if (!response.ok) {
|
|
440
|
+
const err = (await response
|
|
441
|
+
.json()
|
|
442
|
+
.catch(() => ({ message: "Unknown error" }))) as ApiErrorResponse;
|
|
443
|
+
throw new JackError(
|
|
444
|
+
JackErrorCode.INTERNAL_ERROR,
|
|
445
|
+
err.message || `Failed to verify domain: ${response.status}`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return (await response.json()) as VerifyDomainResult;
|
|
450
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token operations service layer for jack cloud
|
|
3
|
+
*
|
|
4
|
+
* Provides shared API token management functions for both CLI and MCP.
|
|
5
|
+
* Returns pure data - no console.log or process.exit.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { authFetch } from "../auth/index.ts";
|
|
9
|
+
import { getControlApiUrl } from "../control-plane.ts";
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Types
|
|
13
|
+
// ============================================================================
|
|
14
|
+
|
|
15
|
+
export interface CreateTokenResult {
|
|
16
|
+
token: string;
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
created_at: string;
|
|
20
|
+
expires_at: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TokenInfo {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
id_prefix: string;
|
|
27
|
+
created_at: string;
|
|
28
|
+
last_used_at: string | null;
|
|
29
|
+
expires_at: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Service Functions
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new API token for headless authentication.
|
|
38
|
+
*/
|
|
39
|
+
export async function createApiToken(
|
|
40
|
+
name: string,
|
|
41
|
+
expiresInDays?: number,
|
|
42
|
+
): Promise<CreateTokenResult> {
|
|
43
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify({ name, expires_in_days: expiresInDays }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
51
|
+
throw new Error(err.message || `Failed to create token: ${response.status}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return response.json() as Promise<CreateTokenResult>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List all active API tokens for the current user.
|
|
59
|
+
*/
|
|
60
|
+
export async function listApiTokens(): Promise<TokenInfo[]> {
|
|
61
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens`);
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
65
|
+
throw new Error(err.message || `Failed to list tokens: ${response.status}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = (await response.json()) as { tokens: TokenInfo[] };
|
|
69
|
+
return data.tokens;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Revoke an API token by ID.
|
|
74
|
+
*/
|
|
75
|
+
export async function revokeApiToken(tokenId: string): Promise<void> {
|
|
76
|
+
const response = await authFetch(`${getControlApiUrl()}/v1/tokens/${tokenId}`, {
|
|
77
|
+
method: "DELETE",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const err = (await response.json().catch(() => ({}))) as { message?: string };
|
|
82
|
+
throw new Error(err.message || `Failed to revoke token: ${response.status}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
package/src/lib/telemetry.ts
CHANGED
|
@@ -44,6 +44,9 @@ export const Events = {
|
|
|
44
44
|
BYO_DEPLOY_STARTED: "byo_deploy_started",
|
|
45
45
|
BYO_DEPLOY_COMPLETED: "byo_deploy_completed",
|
|
46
46
|
BYO_DEPLOY_FAILED: "byo_deploy_failed",
|
|
47
|
+
// Token management events
|
|
48
|
+
TOKEN_CREATED: "token_created",
|
|
49
|
+
TOKEN_REVOKED: "token_revoked",
|
|
47
50
|
} as const;
|
|
48
51
|
|
|
49
52
|
type EventName = (typeof Events)[keyof typeof Events];
|
|
@@ -127,6 +130,7 @@ export interface UserProperties {
|
|
|
127
130
|
is_tty?: boolean;
|
|
128
131
|
locale?: string;
|
|
129
132
|
config_style?: "byoc" | "jack-cloud";
|
|
133
|
+
auth_method?: "oauth" | "token";
|
|
130
134
|
}
|
|
131
135
|
|
|
132
136
|
// Detect environment properties (for user profile - stable properties)
|