contentrain 0.3.2 → 0.3.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.
@@ -0,0 +1,128 @@
1
+ import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
2
+ import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
3
+ import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { intro, log, outro, spinner } from "@clack/prompts";
6
+ //#region src/studio/commands/status.ts
7
+ var status_default = defineCommand({
8
+ meta: {
9
+ name: "status",
10
+ description: "Show project overview from Studio"
11
+ },
12
+ args: {
13
+ workspace: {
14
+ type: "string",
15
+ description: "Workspace ID",
16
+ required: false
17
+ },
18
+ project: {
19
+ type: "string",
20
+ description: "Project ID",
21
+ required: false
22
+ },
23
+ json: {
24
+ type: "boolean",
25
+ description: "JSON output",
26
+ required: false
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ if (!args.json) intro(pc.bold("contentrain studio status"));
31
+ try {
32
+ const client = await resolveStudioClient();
33
+ const ctx = await resolveStudioContext(client, args);
34
+ if (!ctx) {
35
+ log.warning("No workspace or project found.");
36
+ outro("");
37
+ return;
38
+ }
39
+ const { workspaceId, projectId } = ctx;
40
+ const s = args.json ? null : spinner();
41
+ s?.start("Fetching project status...");
42
+ const [workspaces, projects, branches] = await Promise.all([
43
+ client.listWorkspaces(),
44
+ client.listProjects(workspaceId),
45
+ client.listBranches(workspaceId, projectId).catch(() => [])
46
+ ]);
47
+ const workspace = workspaces.find((w) => w.id === workspaceId);
48
+ const project = projects.find((p) => p.id === projectId);
49
+ let cdnBuilds;
50
+ try {
51
+ cdnBuilds = await client.listCdnBuilds(workspaceId, projectId, { limit: "1" });
52
+ } catch {}
53
+ s?.stop("Status loaded");
54
+ if (args.json) {
55
+ process.stdout.write(JSON.stringify({
56
+ workspace: workspace ? {
57
+ id: workspace.id,
58
+ name: workspace.name,
59
+ plan: workspace.plan
60
+ } : null,
61
+ project: project ? {
62
+ id: project.id,
63
+ name: project.name,
64
+ stack: project.stack
65
+ } : null,
66
+ branches: {
67
+ total: branches.length,
68
+ items: branches
69
+ },
70
+ cdn: cdnBuilds?.data[0] ? { lastBuild: {
71
+ status: cdnBuilds.data[0].status,
72
+ fileCount: cdnBuilds.data[0].fileCount,
73
+ createdAt: cdnBuilds.data[0].createdAt
74
+ } } : null
75
+ }, null, 2));
76
+ return;
77
+ }
78
+ log.info(pc.bold("Project"));
79
+ if (project) {
80
+ log.message(` Name: ${pc.cyan(project.name)}`);
81
+ log.message(` Stack: ${project.stack}`);
82
+ log.message(` Members: ${project.memberCount}`);
83
+ }
84
+ if (workspace) {
85
+ log.info(pc.bold("\nWorkspace"));
86
+ log.message(` Name: ${pc.cyan(workspace.name)}`);
87
+ log.message(` Plan: ${workspace.plan}`);
88
+ }
89
+ if (branches.length > 0) {
90
+ log.info(pc.bold(`\nPending branches (${branches.length})`));
91
+ const rows = branches.slice(0, 10).map((b) => [
92
+ b.name,
93
+ `+${b.ahead}`,
94
+ b.author ?? "—",
95
+ timeAgo(b.lastCommitDate)
96
+ ]);
97
+ log.message(formatTable([
98
+ "Branch",
99
+ "Ahead",
100
+ "Author",
101
+ "Updated"
102
+ ], rows));
103
+ if (branches.length > 10) log.message(pc.dim(` ... and ${formatCount(branches.length - 10, "more branch", "more branches")}`));
104
+ } else log.message("\nNo pending branches.");
105
+ const lastBuild = cdnBuilds?.data[0];
106
+ if (lastBuild) {
107
+ log.info(pc.bold("\nCDN"));
108
+ const statusColor = lastBuild.status === "success" ? pc.green : lastBuild.status === "failed" ? pc.red : pc.yellow;
109
+ log.message(` Last build: ${statusColor(lastBuild.status)} (${formatCount(lastBuild.fileCount, "file")}, ${timeAgo(lastBuild.createdAt)})`);
110
+ }
111
+ } catch (error) {
112
+ log.error(error instanceof Error ? error.message : String(error));
113
+ process.exitCode = 1;
114
+ }
115
+ if (!args.json) outro("");
116
+ }
117
+ });
118
+ function timeAgo(dateStr) {
119
+ const diff = Date.now() - new Date(dateStr).getTime();
120
+ const minutes = Math.floor(diff / 6e4);
121
+ if (minutes < 1) return "just now";
122
+ if (minutes < 60) return `${minutes}m ago`;
123
+ const hours = Math.floor(minutes / 60);
124
+ if (hours < 24) return `${hours}h ago`;
125
+ return `${Math.floor(hours / 24)}d ago`;
126
+ }
127
+ //#endregion
128
+ export { status_default as default };
@@ -0,0 +1,23 @@
1
+ import { defineCommand } from "citty";
2
+ //#region src/studio/index.ts
3
+ var studio_default = defineCommand({
4
+ meta: {
5
+ name: "studio",
6
+ description: "Interact with Contentrain Studio"
7
+ },
8
+ subCommands: {
9
+ login: () => import("./login-CO0hxICh.mjs").then((m) => m.default),
10
+ logout: () => import("./logout-CRM4fCSH.mjs").then((m) => m.default),
11
+ whoami: () => import("./whoami-D8NyFKs_.mjs").then((m) => m.default),
12
+ status: () => import("./status-ts5FvDEZ.mjs").then((m) => m.default),
13
+ activity: () => import("./activity-CtOcAA4X.mjs").then((m) => m.default),
14
+ usage: () => import("./usage-BAb4q7Pz.mjs").then((m) => m.default),
15
+ branches: () => import("./branches-C_kgqSv9.mjs").then((m) => m.default),
16
+ "cdn-init": () => import("./cdn-init-DXQ-3V_c.mjs").then((m) => m.default),
17
+ "cdn-build": () => import("./cdn-build-CDv_yM15.mjs").then((m) => m.default),
18
+ webhooks: () => import("./webhooks-C39IItU1.mjs").then((m) => m.default),
19
+ submissions: () => import("./submissions-6_OCrUNF.mjs").then((m) => m.default)
20
+ }
21
+ });
22
+ //#endregion
23
+ export { studio_default as default };
@@ -0,0 +1,169 @@
1
+ import { i as pc, r as formatTable, t as formatCount } from "./ui-B5mXontH.mjs";
2
+ import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
3
+ import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { confirm, intro, isCancel, log, outro, select, spinner } from "@clack/prompts";
6
+ //#region src/studio/commands/submissions.ts
7
+ var submissions_default = defineCommand({
8
+ meta: {
9
+ name: "submissions",
10
+ description: "Manage form submissions on Studio"
11
+ },
12
+ args: {
13
+ workspace: {
14
+ type: "string",
15
+ description: "Workspace ID",
16
+ required: false
17
+ },
18
+ project: {
19
+ type: "string",
20
+ description: "Project ID",
21
+ required: false
22
+ },
23
+ form: {
24
+ type: "string",
25
+ description: "Form model ID",
26
+ required: false
27
+ },
28
+ status: {
29
+ type: "string",
30
+ description: "Filter by status (pending, approved, rejected)",
31
+ required: false
32
+ },
33
+ json: {
34
+ type: "boolean",
35
+ description: "JSON output",
36
+ required: false
37
+ }
38
+ },
39
+ async run({ args }) {
40
+ if (!args.json) intro(pc.bold("contentrain studio submissions"));
41
+ try {
42
+ const client = await resolveStudioClient();
43
+ const ctx = await resolveStudioContext(client, args);
44
+ if (!ctx) {
45
+ log.warning("No workspace or project found.");
46
+ outro("");
47
+ return;
48
+ }
49
+ const { workspaceId, projectId } = ctx;
50
+ const formId = args.form ?? await resolveFormId(args.json);
51
+ if (!formId) {
52
+ if (!args.json) outro("");
53
+ return;
54
+ }
55
+ const s = args.json ? null : spinner();
56
+ s?.start("Fetching submissions...");
57
+ const query = {};
58
+ if (args.status) query["status"] = args.status;
59
+ const result = await client.listSubmissions(workspaceId, projectId, formId, query);
60
+ s?.stop(`${formatCount(result.data.length, "submission")}`);
61
+ if (args.json) {
62
+ process.stdout.write(JSON.stringify(result, null, 2));
63
+ return;
64
+ }
65
+ if (result.data.length === 0) {
66
+ log.info("No submissions found.");
67
+ outro("");
68
+ return;
69
+ }
70
+ const rows = result.data.map((sub) => {
71
+ const preview = summarizeData(sub.data);
72
+ const statusColor = sub.status === "approved" ? pc.green : sub.status === "rejected" ? pc.red : sub.status === "spam" ? pc.dim : pc.yellow;
73
+ return [
74
+ sub.id.slice(0, 8),
75
+ preview,
76
+ statusColor(sub.status),
77
+ timeAgo(sub.submittedAt)
78
+ ];
79
+ });
80
+ log.message(formatTable([
81
+ "ID",
82
+ "Preview",
83
+ "Status",
84
+ "Submitted"
85
+ ], rows));
86
+ const pendingSubs = result.data.filter((sub) => sub.status === "pending");
87
+ if (pendingSubs.length === 0) {
88
+ outro("");
89
+ return;
90
+ }
91
+ const action = await select({
92
+ message: `${formatCount(pendingSubs.length, "pending submission")}. What would you like to do?`,
93
+ options: [
94
+ {
95
+ value: "approve",
96
+ label: "Approve a submission"
97
+ },
98
+ {
99
+ value: "reject",
100
+ label: "Reject a submission"
101
+ },
102
+ {
103
+ value: "cancel",
104
+ label: pc.dim("Done")
105
+ }
106
+ ]
107
+ });
108
+ if (isCancel(action) || action === "cancel") {
109
+ outro("");
110
+ return;
111
+ }
112
+ const subChoice = await select({
113
+ message: "Select submission",
114
+ options: pendingSubs.map((sub) => ({
115
+ value: sub.id,
116
+ label: `${sub.id.slice(0, 8)} — ${summarizeData(sub.data)}`
117
+ }))
118
+ });
119
+ if (isCancel(subChoice)) {
120
+ outro("");
121
+ return;
122
+ }
123
+ const targetStatus = action === "approve" ? "approved" : "rejected";
124
+ const confirmed = await confirm({ message: `${action === "approve" ? "Approve" : "Reject"} submission ${subChoice.slice(0, 8)}?` });
125
+ if (isCancel(confirmed) || !confirmed) {
126
+ outro(pc.dim("Cancelled"));
127
+ return;
128
+ }
129
+ const ms = spinner();
130
+ ms.start(`${action === "approve" ? "Approving" : "Rejecting"}...`);
131
+ await client.updateSubmissionStatus(workspaceId, projectId, formId, subChoice, targetStatus);
132
+ ms.stop(`Submission ${targetStatus}`);
133
+ log.success(`Submission ${pc.cyan(subChoice.slice(0, 8))} ${targetStatus}.`);
134
+ } catch (error) {
135
+ log.error(error instanceof Error ? error.message : String(error));
136
+ process.exitCode = 1;
137
+ }
138
+ if (!args.json) outro("");
139
+ }
140
+ });
141
+ async function resolveFormId(isJson) {
142
+ if (isJson) {
143
+ const { log: l } = await import("@clack/prompts");
144
+ l.error("--form is required for JSON output.");
145
+ process.exitCode = 1;
146
+ return null;
147
+ }
148
+ const { text: textPrompt, isCancel: isCancelled } = await import("@clack/prompts");
149
+ const input = await textPrompt({ message: "Form model ID" });
150
+ if (isCancelled(input)) return null;
151
+ return input;
152
+ }
153
+ function summarizeData(data) {
154
+ return Object.entries(data).slice(0, 2).map(([k, v]) => {
155
+ const val = typeof v === "string" ? v : JSON.stringify(v);
156
+ return `${k}: ${val.length > 20 ? val.slice(0, 17) + "..." : val}`;
157
+ }).join(", ") || "(empty)";
158
+ }
159
+ function timeAgo(dateStr) {
160
+ const diff = Date.now() - new Date(dateStr).getTime();
161
+ const minutes = Math.floor(diff / 6e4);
162
+ if (minutes < 1) return "just now";
163
+ if (minutes < 60) return `${minutes}m ago`;
164
+ const hours = Math.floor(minutes / 60);
165
+ if (hours < 24) return `${hours}h ago`;
166
+ return `${Math.floor(hours / 24)}d ago`;
167
+ }
168
+ //#endregion
169
+ export { submissions_default as default };
@@ -0,0 +1,81 @@
1
+ import { i as pc, n as formatPercent, r as formatTable } from "./ui-B5mXontH.mjs";
2
+ import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
3
+ import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { intro, log, outro, spinner } from "@clack/prompts";
6
+ //#region src/studio/commands/usage.ts
7
+ var usage_default = defineCommand({
8
+ meta: {
9
+ name: "usage",
10
+ description: "Show workspace usage metrics"
11
+ },
12
+ args: {
13
+ workspace: {
14
+ type: "string",
15
+ description: "Workspace ID",
16
+ required: false
17
+ },
18
+ project: {
19
+ type: "string",
20
+ description: "Project ID (for context resolution)",
21
+ required: false
22
+ },
23
+ json: {
24
+ type: "boolean",
25
+ description: "JSON output",
26
+ required: false
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ if (!args.json) intro(pc.bold("contentrain studio usage"));
31
+ try {
32
+ const client = await resolveStudioClient();
33
+ const ctx = await resolveStudioContext(client, args);
34
+ if (!ctx) {
35
+ log.warning("No workspace found.");
36
+ outro("");
37
+ return;
38
+ }
39
+ const s = args.json ? null : spinner();
40
+ s?.start("Fetching usage metrics...");
41
+ const usage = await client.getWorkspaceUsage(ctx.workspaceId);
42
+ s?.stop("Usage loaded");
43
+ if (args.json) {
44
+ process.stdout.write(JSON.stringify(usage, null, 2));
45
+ return;
46
+ }
47
+ const rows = [
48
+ metricRow("AI messages", usage.aiMessages),
49
+ metricRow("Form submissions", usage.formSubmissions),
50
+ metricRow("CDN bandwidth", usage.cdnBandwidthGb, "GB"),
51
+ metricRow("Media storage", usage.mediaStorageGb, "GB")
52
+ ];
53
+ log.message(formatTable([
54
+ "Metric",
55
+ "Used",
56
+ "Limit",
57
+ "%"
58
+ ], rows));
59
+ } catch (error) {
60
+ log.error(error instanceof Error ? error.message : String(error));
61
+ process.exitCode = 1;
62
+ }
63
+ if (!args.json) outro("");
64
+ }
65
+ });
66
+ function metricRow(name, metric, unit) {
67
+ const suffix = unit ? ` ${unit}` : "";
68
+ return [
69
+ name,
70
+ formatNumber(metric.current) + suffix,
71
+ metric.limit < 0 ? "unlimited" : formatNumber(metric.limit) + suffix,
72
+ metric.limit < 0 ? pc.dim("—") : formatPercent(metric.current, metric.limit)
73
+ ];
74
+ }
75
+ function formatNumber(n) {
76
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
77
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
78
+ return String(n);
79
+ }
80
+ //#endregion
81
+ export { usage_default as default };
@@ -0,0 +1,235 @@
1
+ import { i as pc, o as statusIcon, r as formatTable } from "./ui-B5mXontH.mjs";
2
+ import { n as resolveStudioClient } from "./client-D4xW8aLz.mjs";
3
+ import { t as resolveStudioContext } from "./resolve-context-BMV7JwCw.mjs";
4
+ import { defineCommand } from "citty";
5
+ import { confirm, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
6
+ //#region src/studio/commands/webhooks.ts
7
+ const ALL_EVENTS = [
8
+ {
9
+ value: "content.saved",
10
+ label: "Content saved"
11
+ },
12
+ {
13
+ value: "content.deleted",
14
+ label: "Content deleted"
15
+ },
16
+ {
17
+ value: "model.saved",
18
+ label: "Model updated"
19
+ },
20
+ {
21
+ value: "branch.merged",
22
+ label: "Branch merged"
23
+ },
24
+ {
25
+ value: "branch.rejected",
26
+ label: "Branch rejected"
27
+ },
28
+ {
29
+ value: "cdn.build_complete",
30
+ label: "CDN build complete"
31
+ },
32
+ {
33
+ value: "media.uploaded",
34
+ label: "Media uploaded"
35
+ },
36
+ {
37
+ value: "form.submitted",
38
+ label: "Form submitted"
39
+ }
40
+ ];
41
+ var webhooks_default = defineCommand({
42
+ meta: {
43
+ name: "webhooks",
44
+ description: "Manage webhooks on Studio"
45
+ },
46
+ args: {
47
+ workspace: {
48
+ type: "string",
49
+ description: "Workspace ID",
50
+ required: false
51
+ },
52
+ project: {
53
+ type: "string",
54
+ description: "Project ID",
55
+ required: false
56
+ },
57
+ json: {
58
+ type: "boolean",
59
+ description: "JSON output",
60
+ required: false
61
+ }
62
+ },
63
+ async run({ args }) {
64
+ if (!args.json) intro(pc.bold("contentrain studio webhooks"));
65
+ try {
66
+ const client = await resolveStudioClient();
67
+ const ctx = await resolveStudioContext(client, args);
68
+ if (!ctx) {
69
+ log.warning("No workspace or project found.");
70
+ outro("");
71
+ return;
72
+ }
73
+ const { workspaceId, projectId } = ctx;
74
+ const s = args.json ? null : spinner();
75
+ s?.start("Fetching webhooks...");
76
+ const webhooks = await client.listWebhooks(workspaceId, projectId);
77
+ s?.stop(`${webhooks.length} webhook(s)`);
78
+ if (args.json) {
79
+ process.stdout.write(JSON.stringify(webhooks, null, 2));
80
+ return;
81
+ }
82
+ if (webhooks.length > 0) {
83
+ const rows = webhooks.map((w) => [
84
+ w.name,
85
+ w.url.length > 40 ? w.url.slice(0, 37) + "..." : w.url,
86
+ w.events.join(", "),
87
+ statusIcon(w.active)
88
+ ]);
89
+ log.message(formatTable([
90
+ "Name",
91
+ "URL",
92
+ "Events",
93
+ "Active"
94
+ ], rows));
95
+ } else log.info("No webhooks configured.");
96
+ const action = await select({
97
+ message: "What would you like to do?",
98
+ options: [
99
+ {
100
+ value: "create",
101
+ label: "Create new webhook"
102
+ },
103
+ ...webhooks.length > 0 ? [
104
+ {
105
+ value: "test",
106
+ label: "Test a webhook"
107
+ },
108
+ {
109
+ value: "delete",
110
+ label: "Delete a webhook"
111
+ },
112
+ {
113
+ value: "deliveries",
114
+ label: "View delivery history"
115
+ }
116
+ ] : [],
117
+ {
118
+ value: "cancel",
119
+ label: pc.dim("Done")
120
+ }
121
+ ]
122
+ });
123
+ if (isCancel(action) || action === "cancel") {
124
+ outro("");
125
+ return;
126
+ }
127
+ if (action === "create") await createWebhook(client, workspaceId, projectId);
128
+ else if (action === "test") await testWebhook(client, workspaceId, projectId, webhooks);
129
+ else if (action === "delete") await deleteWebhook(client, workspaceId, projectId, webhooks);
130
+ else if (action === "deliveries") await viewDeliveries(client, workspaceId, projectId, webhooks);
131
+ } catch (error) {
132
+ log.error(error instanceof Error ? error.message : String(error));
133
+ process.exitCode = 1;
134
+ }
135
+ if (!args.json) outro("");
136
+ }
137
+ });
138
+ async function createWebhook(client, wid, pid) {
139
+ const name = await text({ message: "Webhook name" });
140
+ if (isCancel(name)) return;
141
+ const url = await text({
142
+ message: "Endpoint URL",
143
+ validate: (v) => {
144
+ if (!v.startsWith("https://")) return "URL must start with https://";
145
+ }
146
+ });
147
+ if (isCancel(url)) return;
148
+ const events = await multiselect({
149
+ message: "Events to subscribe to",
150
+ options: ALL_EVENTS,
151
+ required: true
152
+ });
153
+ if (isCancel(events)) return;
154
+ const s = spinner();
155
+ s.start("Creating webhook...");
156
+ const webhook = await client.createWebhook(wid, pid, {
157
+ name,
158
+ url,
159
+ events
160
+ });
161
+ s.stop("Webhook created");
162
+ log.success(`${pc.cyan(webhook.name)} → ${webhook.url}`);
163
+ }
164
+ async function testWebhook(client, wid, pid, webhooks) {
165
+ const choice = await select({
166
+ message: "Select webhook to test",
167
+ options: webhooks.map((w) => ({
168
+ value: w.id,
169
+ label: w.name
170
+ }))
171
+ });
172
+ if (isCancel(choice)) return;
173
+ const s = spinner();
174
+ s.start("Sending test event...");
175
+ const result = await client.testWebhook(wid, pid, choice);
176
+ s.stop(result.success ? "Test succeeded" : "Test failed");
177
+ if (result.httpStatus) log.message(` HTTP status: ${result.httpStatus}`);
178
+ }
179
+ async function deleteWebhook(client, wid, pid, webhooks) {
180
+ const choice = await select({
181
+ message: "Select webhook to delete",
182
+ options: webhooks.map((w) => ({
183
+ value: w.id,
184
+ label: w.name
185
+ }))
186
+ });
187
+ if (isCancel(choice)) return;
188
+ const confirmed = await confirm({ message: `Delete webhook "${webhooks.find((w) => w.id === choice)?.name}"? This cannot be undone.` });
189
+ if (isCancel(confirmed) || !confirmed) return;
190
+ const s = spinner();
191
+ s.start("Deleting...");
192
+ await client.deleteWebhook(wid, pid, choice);
193
+ s.stop("Webhook deleted");
194
+ }
195
+ async function viewDeliveries(client, wid, pid, webhooks) {
196
+ const choice = await select({
197
+ message: "Select webhook",
198
+ options: webhooks.map((w) => ({
199
+ value: w.id,
200
+ label: w.name
201
+ }))
202
+ });
203
+ if (isCancel(choice)) return;
204
+ const s = spinner();
205
+ s.start("Fetching deliveries...");
206
+ const result = await client.listWebhookDeliveries(wid, pid, choice);
207
+ s.stop(`${result.data.length} deliveries`);
208
+ if (result.data.length === 0) {
209
+ log.info("No deliveries yet.");
210
+ return;
211
+ }
212
+ const rows = result.data.map((d) => [
213
+ d.event,
214
+ d.status === "success" ? pc.green(d.status) : d.status === "failed" ? pc.red(d.status) : pc.yellow(d.status),
215
+ d.httpStatus ? String(d.httpStatus) : "—",
216
+ timeAgo(d.createdAt)
217
+ ]);
218
+ log.message(formatTable([
219
+ "Event",
220
+ "Status",
221
+ "HTTP",
222
+ "Time"
223
+ ], rows));
224
+ }
225
+ function timeAgo(dateStr) {
226
+ const diff = Date.now() - new Date(dateStr).getTime();
227
+ const minutes = Math.floor(diff / 6e4);
228
+ if (minutes < 1) return "just now";
229
+ if (minutes < 60) return `${minutes}m ago`;
230
+ const hours = Math.floor(minutes / 60);
231
+ if (hours < 24) return `${hours}h ago`;
232
+ return `${Math.floor(hours / 24)}d ago`;
233
+ }
234
+ //#endregion
235
+ export { webhooks_default as default };
@@ -0,0 +1,65 @@
1
+ import { i as pc } from "./ui-B5mXontH.mjs";
2
+ import { i as checkPermissions, n as resolveStudioClient, r as AuthExpiredError } from "./client-D4xW8aLz.mjs";
3
+ import { defineCommand } from "citty";
4
+ import { intro, log, outro } from "@clack/prompts";
5
+ //#region src/studio/commands/whoami.ts
6
+ var whoami_default = defineCommand({
7
+ meta: {
8
+ name: "whoami",
9
+ description: "Show current Studio authentication status"
10
+ },
11
+ args: { json: {
12
+ type: "boolean",
13
+ description: "JSON output",
14
+ required: false
15
+ } },
16
+ async run({ args }) {
17
+ if (!args.json) intro(pc.bold("contentrain studio whoami"));
18
+ try {
19
+ const client = await resolveStudioClient();
20
+ const user = await client.me();
21
+ if (args.json) {
22
+ const workspaces = await client.listWorkspaces();
23
+ process.stdout.write(JSON.stringify({
24
+ user: {
25
+ id: user.id,
26
+ email: user.email,
27
+ name: user.name,
28
+ provider: user.provider
29
+ },
30
+ workspaces: workspaces.map((w) => ({
31
+ id: w.id,
32
+ name: w.name,
33
+ slug: w.slug,
34
+ plan: w.plan,
35
+ role: w.role
36
+ }))
37
+ }, null, 2));
38
+ return;
39
+ }
40
+ log.info(pc.bold("Profile"));
41
+ log.message(` Email: ${pc.cyan(user.email)}`);
42
+ if (user.name) log.message(` Name: ${user.name}`);
43
+ log.message(` Provider: ${user.provider}`);
44
+ const workspaces = await client.listWorkspaces();
45
+ if (workspaces.length > 0) {
46
+ log.info(pc.bold(`\nWorkspaces (${workspaces.length})`));
47
+ for (const ws of workspaces) log.message(` ${pc.cyan(ws.name)} (${ws.plan}) — ${pc.dim(ws.role)}`);
48
+ }
49
+ const permWarning = await checkPermissions();
50
+ if (permWarning) log.warning(`\n${permWarning}`);
51
+ } catch (error) {
52
+ if (error instanceof AuthExpiredError) {
53
+ if (args.json) process.stdout.write(JSON.stringify({ error: "not_authenticated" }));
54
+ else log.error(error.message);
55
+ process.exitCode = 1;
56
+ } else {
57
+ if (!args.json) log.error(error instanceof Error ? error.message : String(error));
58
+ process.exitCode = 1;
59
+ }
60
+ }
61
+ if (!args.json) outro("");
62
+ }
63
+ });
64
+ //#endregion
65
+ export { whoami_default as default };