@ascendkit/cli 0.1.10

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 (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/client.d.ts +34 -0
  3. package/dist/api/client.js +155 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1153 -0
  6. package/dist/commands/auth.d.ts +17 -0
  7. package/dist/commands/auth.js +29 -0
  8. package/dist/commands/content.d.ts +25 -0
  9. package/dist/commands/content.js +28 -0
  10. package/dist/commands/email.d.ts +37 -0
  11. package/dist/commands/email.js +39 -0
  12. package/dist/commands/journeys.d.ts +86 -0
  13. package/dist/commands/journeys.js +69 -0
  14. package/dist/commands/platform.d.ts +35 -0
  15. package/dist/commands/platform.js +517 -0
  16. package/dist/commands/surveys.d.ts +51 -0
  17. package/dist/commands/surveys.js +41 -0
  18. package/dist/commands/webhooks.d.ts +16 -0
  19. package/dist/commands/webhooks.js +28 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +29 -0
  22. package/dist/mcp.d.ts +2 -0
  23. package/dist/mcp.js +40 -0
  24. package/dist/tools/auth.d.ts +3 -0
  25. package/dist/tools/auth.js +75 -0
  26. package/dist/tools/content.d.ts +3 -0
  27. package/dist/tools/content.js +64 -0
  28. package/dist/tools/email.d.ts +3 -0
  29. package/dist/tools/email.js +57 -0
  30. package/dist/tools/journeys.d.ts +3 -0
  31. package/dist/tools/journeys.js +302 -0
  32. package/dist/tools/platform.d.ts +3 -0
  33. package/dist/tools/platform.js +63 -0
  34. package/dist/tools/surveys.d.ts +3 -0
  35. package/dist/tools/surveys.js +212 -0
  36. package/dist/tools/webhooks.d.ts +3 -0
  37. package/dist/tools/webhooks.js +56 -0
  38. package/dist/types.d.ts +96 -0
  39. package/dist/types.js +4 -0
  40. package/dist/utils/credentials.d.ts +27 -0
  41. package/dist/utils/credentials.js +90 -0
  42. package/dist/utils/duration.d.ts +16 -0
  43. package/dist/utils/duration.js +47 -0
  44. package/dist/utils/journey-format.d.ts +112 -0
  45. package/dist/utils/journey-format.js +200 -0
  46. package/dist/utils/survey-format.d.ts +60 -0
  47. package/dist/utils/survey-format.js +164 -0
  48. package/package.json +37 -0
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ import * as platform from "../commands/platform.js";
3
+ export function registerPlatformTools(server, client) {
4
+ server.tool("platform_login", "Log in with your AscendKit email and password. Required before using platform tools (project/environment management).", {
5
+ email: z.string().describe("Your AscendKit account email"),
6
+ password: z.string().describe("Your AscendKit account password"),
7
+ }, async (params) => {
8
+ const data = await platform.mcpLogin(client, params);
9
+ return {
10
+ content: [
11
+ {
12
+ type: "text",
13
+ text: `Logged in as ${data.user.email} (account: ${data.accountId})\n\n${JSON.stringify(data.user, null, 2)}`,
14
+ },
15
+ ],
16
+ };
17
+ });
18
+ server.tool("platform_list_projects", "List all projects in your organization", {}, async () => {
19
+ const data = await platform.mcpListProjects(client);
20
+ return {
21
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
22
+ };
23
+ });
24
+ server.tool("platform_create_project", "Create a new project in your organization", {
25
+ name: z.string().describe("Project name"),
26
+ description: z.string().optional().describe("Project description"),
27
+ enabledServices: z
28
+ .array(z.string())
29
+ .optional()
30
+ .describe('Services to enable, e.g. ["auth", "content", "surveys"]'),
31
+ }, async (params) => {
32
+ const data = await platform.mcpCreateProject(client, params);
33
+ return {
34
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
35
+ };
36
+ });
37
+ server.tool("platform_list_environments", "List environments for a project", {
38
+ projectId: z.string().describe("Project ID (prj_ prefixed)"),
39
+ }, async (params) => {
40
+ const data = await platform.mcpListEnvironments(client, params.projectId);
41
+ return {
42
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
43
+ };
44
+ });
45
+ server.tool("platform_create_environment", "Create a new environment for a project. Returns the public key needed for SDK/CLI configuration.", {
46
+ projectId: z.string().describe("Project ID (prj_ prefixed)"),
47
+ name: z
48
+ .string()
49
+ .describe('Environment name, e.g. "Development", "Production"'),
50
+ description: z
51
+ .string()
52
+ .optional()
53
+ .describe("Environment description"),
54
+ tier: z
55
+ .string()
56
+ .describe('Environment tier: "dev", "beta", or "prod"'),
57
+ }, async (params) => {
58
+ const data = await platform.mcpCreateEnvironment(client, params);
59
+ return {
60
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
61
+ };
62
+ });
63
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { AscendKitClient } from "../api/client.js";
3
+ export declare function registerSurveyTools(server: McpServer, client: AscendKitClient): void;
@@ -0,0 +1,212 @@
1
+ import { z } from "zod";
2
+ import * as surveys from "../commands/surveys.js";
3
+ import { formatDistributionResult, formatQuestionList, formatSingleQuestion, formatSurveyList, formatSurveyWithGuidance, } from "../utils/survey-format.js";
4
+ export function registerSurveyTools(server, client) {
5
+ server.tool("survey_create", "Create a new survey. Use type 'nps' or 'csat' for ready-to-use presets with standard questions, or 'custom' to build from scratch with survey_add_question. Survey lifecycle: create → add questions → activate → distribute → collect → analyze.", {
6
+ name: z.string().describe("Survey name, e.g. 'Q1 NPS Survey'"),
7
+ type: z
8
+ .enum(["nps", "csat", "custom"])
9
+ .default("custom")
10
+ .describe("Survey type: 'nps' and 'csat' auto-generate definitions"),
11
+ definition: z
12
+ .record(z.unknown())
13
+ .optional()
14
+ .describe("SurveyJS-compatible JSON definition (required for type 'custom')"),
15
+ }, async (params) => {
16
+ const data = await surveys.createSurvey(client, params);
17
+ const formatted = formatSurveyWithGuidance(data);
18
+ return { content: [{ type: "text", text: formatted }] };
19
+ });
20
+ server.tool("survey_list", "List all surveys for the current project with readiness status (needs questions / draft / active / collecting responses).", {}, async () => {
21
+ const data = await surveys.listSurveys(client);
22
+ const formatted = formatSurveyList(data);
23
+ return { content: [{ type: "text", text: formatted }] };
24
+ });
25
+ server.tool("survey_get", "Get a survey by ID with status, question count, response count, and next-step guidance.", {
26
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
27
+ }, async (params) => {
28
+ const data = await surveys.getSurvey(client, params.surveyId);
29
+ const formatted = formatSurveyWithGuidance(data);
30
+ return { content: [{ type: "text", text: formatted }] };
31
+ });
32
+ server.tool("survey_update", "Update a survey's name, definition, or status. Set status to 'active' to enable distribution, 'paused' to stop collection.", {
33
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
34
+ name: z.string().optional().describe("New survey name"),
35
+ definition: z
36
+ .record(z.unknown())
37
+ .optional()
38
+ .describe("Updated SurveyJS definition"),
39
+ status: z
40
+ .enum(["draft", "active", "paused"])
41
+ .optional()
42
+ .describe("Survey status"),
43
+ }, async (params) => {
44
+ const { surveyId, ...rest } = params;
45
+ const data = await surveys.updateSurvey(client, surveyId, rest);
46
+ const formatted = formatSurveyWithGuidance(data);
47
+ return { content: [{ type: "text", text: formatted }] };
48
+ });
49
+ server.tool("survey_delete", "Delete a survey and all its invitations and responses", {
50
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
51
+ }, async (params) => {
52
+ const data = await surveys.deleteSurvey(client, params.surveyId);
53
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
54
+ });
55
+ server.tool("survey_distribute", "Distribute a survey to registered project users. Creates a unique personalized link per user for tracking (sent → opened → submitted). The survey must be active before distributing.", {
56
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
57
+ userIds: z
58
+ .array(z.string())
59
+ .describe("List of project user IDs (usr_ prefixed) to send the survey to"),
60
+ }, async (params) => {
61
+ const data = await surveys.distributeSurvey(client, params.surveyId, params.userIds);
62
+ const formatted = formatDistributionResult(data);
63
+ return { content: [{ type: "text", text: formatted }] };
64
+ });
65
+ server.tool("survey_list_invitations", "List all invitations for a survey with their status (sent/opened/submitted)", {
66
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
67
+ }, async (params) => {
68
+ const data = await surveys.listInvitations(client, params.surveyId);
69
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
70
+ });
71
+ server.tool("survey_analytics", "Get survey analytics: funnel (sent -> opened -> submitted), conversion rates, NPS score (for NPS surveys)", {
72
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
73
+ }, async (params) => {
74
+ const data = await surveys.getAnalytics(client, params.surveyId);
75
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
76
+ });
77
+ // --- Question-level management tools ---
78
+ server.tool("survey_list_questions", "List all questions in a survey in human-readable format. Shows question number, type, title, required status, choices, and any conditional logic.", {
79
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
80
+ }, async (params) => {
81
+ const data = await surveys.listQuestions(client, params.surveyId);
82
+ const formatted = formatQuestionList(data);
83
+ return { content: [{ type: "text", text: formatted }] };
84
+ });
85
+ server.tool("survey_add_question", "Add a question to a survey. Supported types: text (short text), comment (long text/multi-line), radiogroup (single choice), checkbox (multiple choice), rating (scale — use rateMin:0 rateMax:10 for NPS), boolean (yes/no), dropdown, ranking. For date input, use type 'text' with inputType 'date'. Question name is auto-generated from title if not provided.", {
86
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
87
+ type: z
88
+ .enum([
89
+ "text",
90
+ "comment",
91
+ "radiogroup",
92
+ "checkbox",
93
+ "rating",
94
+ "boolean",
95
+ "dropdown",
96
+ "ranking",
97
+ ])
98
+ .describe("Question type"),
99
+ title: z.string().describe("Question text shown to the user"),
100
+ name: z
101
+ .string()
102
+ .optional()
103
+ .describe("Unique identifier for this question (auto-generated from title if omitted)"),
104
+ isRequired: z
105
+ .boolean()
106
+ .default(false)
107
+ .describe("Whether the question must be answered"),
108
+ choices: z
109
+ .array(z.string())
110
+ .optional()
111
+ .describe("Answer options (required for radiogroup, checkbox, dropdown, ranking)"),
112
+ rateMin: z
113
+ .number()
114
+ .optional()
115
+ .describe("Minimum rating value (for rating type)"),
116
+ rateMax: z
117
+ .number()
118
+ .optional()
119
+ .describe("Maximum rating value (for rating type)"),
120
+ minRateDescription: z
121
+ .string()
122
+ .optional()
123
+ .describe("Label for minimum rating"),
124
+ maxRateDescription: z
125
+ .string()
126
+ .optional()
127
+ .describe("Label for maximum rating"),
128
+ inputType: z
129
+ .string()
130
+ .optional()
131
+ .describe("HTML input type override (e.g., 'date' for date picker)"),
132
+ visibleIf: z
133
+ .string()
134
+ .optional()
135
+ .describe("SurveyJS expression for conditional visibility (e.g., '{ease_of_setup} <= 3')"),
136
+ requiredIf: z
137
+ .string()
138
+ .optional()
139
+ .describe("SurveyJS expression for conditional required"),
140
+ position: z
141
+ .number()
142
+ .optional()
143
+ .describe("0-based position to insert at (appends to end if omitted)"),
144
+ }, async (params) => {
145
+ const { surveyId, ...rest } = params;
146
+ const data = await surveys.addQuestion(client, surveyId, rest);
147
+ const formatted = formatSingleQuestion(data, "Added");
148
+ return { content: [{ type: "text", text: formatted }] };
149
+ });
150
+ server.tool("survey_edit_question", "Edit an existing question in a survey by its name. Only provided fields are updated; others remain unchanged. Pass empty string for visibleIf/requiredIf to clear conditional logic.", {
151
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
152
+ questionName: z.string().describe("Name of the question to edit"),
153
+ title: z.string().optional().describe("New question text"),
154
+ isRequired: z
155
+ .boolean()
156
+ .optional()
157
+ .describe("Whether the question must be answered"),
158
+ choices: z.array(z.string()).optional().describe("New answer options"),
159
+ rateMin: z.number().optional().describe("New minimum rating value"),
160
+ rateMax: z.number().optional().describe("New maximum rating value"),
161
+ minRateDescription: z
162
+ .string()
163
+ .optional()
164
+ .describe("New label for minimum rating"),
165
+ maxRateDescription: z
166
+ .string()
167
+ .optional()
168
+ .describe("New label for maximum rating"),
169
+ inputType: z.string().optional().describe("New HTML input type"),
170
+ visibleIf: z
171
+ .string()
172
+ .optional()
173
+ .describe("New visibility expression (empty string to clear)"),
174
+ requiredIf: z
175
+ .string()
176
+ .optional()
177
+ .describe("New required expression (empty string to clear)"),
178
+ }, async (params) => {
179
+ const { surveyId, questionName, ...rest } = params;
180
+ const data = await surveys.editQuestion(client, surveyId, questionName, rest);
181
+ const formatted = formatSingleQuestion(data, "Updated");
182
+ return { content: [{ type: "text", text: formatted }] };
183
+ });
184
+ server.tool("survey_remove_question", "Remove a question from a survey by its name. This is permanent — the question and its configuration are deleted from the survey definition.", {
185
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
186
+ questionName: z.string().describe("Name of the question to remove"),
187
+ }, async (params) => {
188
+ await surveys.removeQuestion(client, params.surveyId, params.questionName);
189
+ return {
190
+ content: [
191
+ {
192
+ type: "text",
193
+ text: `Removed question '${params.questionName}' from survey.`,
194
+ },
195
+ ],
196
+ };
197
+ });
198
+ server.tool("survey_reorder_questions", "Reorder questions in a survey by providing all question names in the desired order. All existing question names must be included.", {
199
+ surveyId: z.string().describe("Survey ID (srv_ prefixed)"),
200
+ order: z
201
+ .array(z.string())
202
+ .describe("List of all question names in the desired display order"),
203
+ }, async (params) => {
204
+ const data = await surveys.reorderQuestions(client, params.surveyId, params.order);
205
+ const formatted = formatQuestionList(data);
206
+ return {
207
+ content: [
208
+ { type: "text", text: `Questions reordered:\n\n${formatted}` },
209
+ ],
210
+ };
211
+ });
212
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { AscendKitClient } from "../api/client.js";
3
+ export declare function registerWebhookTools(server: McpServer, client: AscendKitClient): void;
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+ import * as webhooks from "../commands/webhooks.js";
3
+ export function registerWebhookTools(server, client) {
4
+ server.tool("webhook_create", "Create a new webhook endpoint for the current environment. Optionally filter which events are sent to this endpoint.", {
5
+ url: z.string().describe("The URL to receive webhook POST requests"),
6
+ events: z
7
+ .array(z.string())
8
+ .optional()
9
+ .describe("List of event types to subscribe to (e.g. ['user.created', 'survey.completed']). Receives all events if omitted."),
10
+ }, async (params) => {
11
+ const data = await webhooks.createWebhook(client, params);
12
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
13
+ });
14
+ server.tool("webhook_list", "List all webhook endpoints configured for the current environment.", {}, async () => {
15
+ const data = await webhooks.listWebhooks(client);
16
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
17
+ });
18
+ server.tool("webhook_get", "Get a webhook endpoint by its config ID, including URL, subscribed events, and status.", {
19
+ configId: z.string().describe("Webhook config ID"),
20
+ }, async (params) => {
21
+ const data = await webhooks.getWebhook(client, params.configId);
22
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
23
+ });
24
+ server.tool("webhook_update", "Update a webhook endpoint. Change the URL, subscribed events, or status (active/inactive). Only provided fields are changed.", {
25
+ configId: z.string().describe("Webhook config ID"),
26
+ url: z.string().optional().describe("New URL for the webhook endpoint"),
27
+ events: z
28
+ .array(z.string())
29
+ .optional()
30
+ .describe("Updated list of event types to subscribe to"),
31
+ status: z
32
+ .enum(["active", "inactive"])
33
+ .optional()
34
+ .describe("Set endpoint status: 'active' to receive events, 'inactive' to pause delivery"),
35
+ }, async (params) => {
36
+ const { configId, ...rest } = params;
37
+ const data = await webhooks.updateWebhook(client, configId, rest);
38
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
39
+ });
40
+ server.tool("webhook_delete", "Delete a webhook endpoint. This is permanent and cannot be undone.", {
41
+ configId: z.string().describe("Webhook config ID"),
42
+ }, async (params) => {
43
+ const data = await webhooks.deleteWebhook(client, params.configId);
44
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
45
+ });
46
+ server.tool("webhook_test", "Send a test webhook event to an endpoint. Useful for verifying your endpoint receives and processes events correctly.", {
47
+ configId: z.string().describe("Webhook config ID"),
48
+ eventType: z
49
+ .string()
50
+ .optional()
51
+ .describe("Event type to simulate (e.g. 'user.created'). Uses a default test event if omitted."),
52
+ }, async (params) => {
53
+ const data = await webhooks.testWebhook(client, params.configId, params.eventType);
54
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
55
+ });
56
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Shared type definitions for AscendKit MCP server.
3
+ */
4
+ export interface User {
5
+ id: string;
6
+ email: string;
7
+ name: string | null;
8
+ emailVerified: string | null;
9
+ image: string | null;
10
+ }
11
+ export interface Project {
12
+ id: string;
13
+ name: string;
14
+ publicKey: string;
15
+ createdAt: string;
16
+ }
17
+ export interface AuthSettings {
18
+ providers: string[];
19
+ features: {
20
+ emailVerification: boolean;
21
+ waitlist: boolean;
22
+ passwordReset: boolean;
23
+ };
24
+ sessionDuration: string;
25
+ }
26
+ export interface ContentTemplate {
27
+ id: string;
28
+ name: string;
29
+ slug?: string;
30
+ description?: string;
31
+ isSystem: boolean;
32
+ subject: string;
33
+ currentVersion: number;
34
+ createdAt: string;
35
+ updatedAt: string;
36
+ }
37
+ export interface Survey {
38
+ id: string;
39
+ name: string;
40
+ slug: string;
41
+ type: "nps" | "csat" | "custom";
42
+ status: "draft" | "active" | "paused";
43
+ stats: {
44
+ responseCount: number;
45
+ completionRate: number;
46
+ npsScore?: number;
47
+ csatAverage?: number;
48
+ };
49
+ createdAt: string;
50
+ }
51
+ export interface SurveyInvitation {
52
+ id: string;
53
+ surveyId: string;
54
+ userId: string;
55
+ token: string;
56
+ status: "sent" | "opened" | "submitted";
57
+ sentAt: string;
58
+ openedAt?: string;
59
+ submittedAt?: string;
60
+ }
61
+ export interface Journey {
62
+ id: string;
63
+ environmentId: string;
64
+ name: string;
65
+ slug: string;
66
+ description: string;
67
+ entryEvent: string;
68
+ entryNode: string;
69
+ entryConditions?: Record<string, unknown>;
70
+ reEntryPolicy: "skip" | "restart";
71
+ nodes: Record<string, {
72
+ action?: {
73
+ type?: string;
74
+ };
75
+ terminal?: boolean;
76
+ }>;
77
+ transitions: Array<{
78
+ from: string;
79
+ to: string;
80
+ trigger: {
81
+ type: "event" | "timer";
82
+ event?: string;
83
+ delay?: string;
84
+ };
85
+ priority?: number;
86
+ }>;
87
+ status: "draft" | "active" | "paused" | "archived";
88
+ stats: {
89
+ totalEnrolled: number;
90
+ currentlyActive: number;
91
+ completed: number;
92
+ };
93
+ version: number;
94
+ createdAt: string;
95
+ updatedAt: string;
96
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared type definitions for AscendKit MCP server.
3
+ */
4
+ export {};
@@ -0,0 +1,27 @@
1
+ export interface AuthCredentials {
2
+ token: string;
3
+ apiUrl: string;
4
+ }
5
+ export interface EnvContext {
6
+ publicKey: string;
7
+ projectId: string;
8
+ projectName: string;
9
+ environmentId: string;
10
+ environmentName: string;
11
+ tier: string;
12
+ }
13
+ /**
14
+ * Walk up from cwd looking for an existing .ascendkit/ directory
15
+ * that contains auth.json or env.json. Falls back to cwd.
16
+ */
17
+ export declare function findAscendKitRoot(): string;
18
+ export declare function loadAuth(): AuthCredentials | null;
19
+ /**
20
+ * Save auth credentials to .ascendkit/auth.json in the CURRENT directory.
21
+ * This is where `ascendkit init` was run.
22
+ */
23
+ export declare function saveAuth(auth: AuthCredentials): void;
24
+ export declare function deleteAuth(): void;
25
+ export declare function loadEnvContext(): EnvContext | null;
26
+ export declare function saveEnvContext(ctx: EnvContext): void;
27
+ export declare function ensureGitignore(): void;
@@ -0,0 +1,90 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync, appendFileSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ const LOCAL_DIR = ".ascendkit";
4
+ const AUTH_FILE = "auth.json";
5
+ const ENV_FILE = "env.json";
6
+ /**
7
+ * Walk up from cwd looking for an existing .ascendkit/ directory
8
+ * that contains auth.json or env.json. Falls back to cwd.
9
+ */
10
+ export function findAscendKitRoot() {
11
+ const cwd = process.cwd();
12
+ let search = cwd;
13
+ while (true) {
14
+ const dir = join(search, LOCAL_DIR);
15
+ if (existsSync(join(dir, AUTH_FILE)) || existsSync(join(dir, ENV_FILE))) {
16
+ return search;
17
+ }
18
+ const parent = dirname(search);
19
+ if (parent === search)
20
+ break;
21
+ search = parent;
22
+ }
23
+ return cwd;
24
+ }
25
+ function localAuthPath() {
26
+ return join(findAscendKitRoot(), LOCAL_DIR, AUTH_FILE);
27
+ }
28
+ function localEnvPath() {
29
+ return join(findAscendKitRoot(), LOCAL_DIR, ENV_FILE);
30
+ }
31
+ // --- Auth operations ---
32
+ export function loadAuth() {
33
+ try {
34
+ const raw = readFileSync(localAuthPath(), "utf-8");
35
+ return JSON.parse(raw);
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /**
42
+ * Save auth credentials to .ascendkit/auth.json in the CURRENT directory.
43
+ * This is where `ascendkit init` was run.
44
+ */
45
+ export function saveAuth(auth) {
46
+ const dir = join(process.cwd(), LOCAL_DIR);
47
+ mkdirSync(dir, { recursive: true });
48
+ writeFileSync(join(dir, AUTH_FILE), JSON.stringify(auth, null, 2));
49
+ }
50
+ export function deleteAuth() {
51
+ const authPath = localAuthPath();
52
+ if (existsSync(authPath))
53
+ unlinkSync(authPath);
54
+ }
55
+ // --- Env context operations ---
56
+ export function loadEnvContext() {
57
+ try {
58
+ const raw = readFileSync(localEnvPath(), "utf-8");
59
+ return JSON.parse(raw);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ export function saveEnvContext(ctx) {
66
+ const root = findAscendKitRoot();
67
+ const dir = join(root, LOCAL_DIR);
68
+ mkdirSync(dir, { recursive: true });
69
+ writeFileSync(join(dir, ENV_FILE), JSON.stringify(ctx, null, 2));
70
+ }
71
+ // --- .gitignore helper ---
72
+ export function ensureGitignore() {
73
+ const root = findAscendKitRoot();
74
+ const gitignorePath = join(root, ".gitignore");
75
+ const pattern = ".ascendkit/";
76
+ try {
77
+ if (existsSync(gitignorePath)) {
78
+ const content = readFileSync(gitignorePath, "utf-8");
79
+ if (content.includes(".ascendkit/") || content.includes(pattern)) {
80
+ return; // Already covered
81
+ }
82
+ }
83
+ const prefix = existsSync(gitignorePath) ? "\n" : "";
84
+ appendFileSync(gitignorePath, `${prefix}# AscendKit local files (contains auth/session context)\n${pattern}\n`);
85
+ console.log(" Added .ascendkit/ to .gitignore");
86
+ }
87
+ catch {
88
+ console.error(" Warning: Could not update .gitignore. Add '.ascendkit/' manually.");
89
+ }
90
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Parse a human-friendly duration string into the API's compact format.
3
+ *
4
+ * Accepted inputs:
5
+ * "3 day" | "3day" | "3d" → "3d"
6
+ * "12 hr" | "12hr" | "12h" → "12h"
7
+ * "30 m" | "30m" | "30" → "30m" (bare number = minutes)
8
+ * "2 week" | "2week" | "2w" → "14d" (weeks converted to days)
9
+ *
10
+ * Unit aliases:
11
+ * minutes: m, min, minute, minutes
12
+ * hours: h, hr, hour, hours
13
+ * days: d, day, days
14
+ * weeks: w, week, weeks
15
+ */
16
+ export declare function parseDelay(input: string): string;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Parse a human-friendly duration string into the API's compact format.
3
+ *
4
+ * Accepted inputs:
5
+ * "3 day" | "3day" | "3d" → "3d"
6
+ * "12 hr" | "12hr" | "12h" → "12h"
7
+ * "30 m" | "30m" | "30" → "30m" (bare number = minutes)
8
+ * "2 week" | "2week" | "2w" → "14d" (weeks converted to days)
9
+ *
10
+ * Unit aliases:
11
+ * minutes: m, min, minute, minutes
12
+ * hours: h, hr, hour, hours
13
+ * days: d, day, days
14
+ * weeks: w, week, weeks
15
+ */
16
+ export function parseDelay(input) {
17
+ const trimmed = input.trim();
18
+ // Already in compact format (e.g. "3d", "12h")
19
+ if (/^\d+[dhm]$/.test(trimmed)) {
20
+ return trimmed;
21
+ }
22
+ // Bare number → minutes
23
+ if (/^\d+$/.test(trimmed)) {
24
+ return `${trimmed}m`;
25
+ }
26
+ // "3 day", "12hr", "2 weeks", etc.
27
+ const match = trimmed.match(/^(\d+)\s*(m|min|minute|minutes|h|hr|hour|hours|d|day|days|w|week|weeks)$/i);
28
+ if (!match) {
29
+ throw new Error(`Invalid delay format: "${input}". Use a number with a unit (e.g. "3 day", "12 hr", "30 m", "2 week") or a bare number for minutes.`);
30
+ }
31
+ const value = parseInt(match[1], 10);
32
+ const unit = match[2].toLowerCase();
33
+ if (unit === "m" || unit === "min" || unit === "minute" || unit === "minutes") {
34
+ return `${value}m`;
35
+ }
36
+ if (unit === "h" || unit === "hr" || unit === "hour" || unit === "hours") {
37
+ return `${value}h`;
38
+ }
39
+ if (unit === "d" || unit === "day" || unit === "days") {
40
+ return `${value}d`;
41
+ }
42
+ if (unit === "w" || unit === "week" || unit === "weeks") {
43
+ return `${value * 7}d`;
44
+ }
45
+ // Should not reach here due to regex, but guard anyway
46
+ throw new Error(`Unrecognized unit: "${unit}"`);
47
+ }