@xano/cli 0.0.14 → 0.0.16

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 (66) hide show
  1. package/README.md +115 -14
  2. package/dist/commands/profile/create/index.d.ts +2 -0
  3. package/dist/commands/profile/create/index.js +15 -0
  4. package/dist/commands/profile/edit/index.d.ts +6 -0
  5. package/dist/commands/profile/edit/index.js +50 -1
  6. package/dist/commands/profile/list/index.js +5 -0
  7. package/dist/commands/profile/project/index.d.ts +6 -0
  8. package/dist/commands/profile/project/index.js +54 -0
  9. package/dist/commands/profile/token/index.d.ts +6 -0
  10. package/dist/commands/profile/token/index.js +54 -0
  11. package/dist/commands/profile/wizard/index.d.ts +2 -0
  12. package/dist/commands/profile/wizard/index.js +108 -0
  13. package/dist/commands/run/env/delete/index.d.ts +13 -0
  14. package/dist/commands/run/env/delete/index.js +65 -0
  15. package/dist/commands/run/env/get/index.d.ts +13 -0
  16. package/dist/commands/run/env/get/index.js +52 -0
  17. package/dist/commands/run/env/list/index.d.ts +11 -0
  18. package/dist/commands/run/env/list/index.js +58 -0
  19. package/dist/commands/run/env/set/index.d.ts +13 -0
  20. package/dist/commands/run/env/set/index.js +51 -0
  21. package/dist/commands/{ephemeral/run/job → run/exec}/index.d.ts +4 -3
  22. package/dist/commands/run/exec/index.js +353 -0
  23. package/dist/commands/{ephemeral/run/service → run/info}/index.d.ts +3 -5
  24. package/dist/commands/run/info/index.js +160 -0
  25. package/dist/commands/run/projects/create/index.d.ts +13 -0
  26. package/dist/commands/run/projects/create/index.js +75 -0
  27. package/dist/commands/run/projects/delete/index.d.ts +13 -0
  28. package/dist/commands/run/projects/delete/index.js +65 -0
  29. package/dist/commands/run/projects/list/index.d.ts +12 -0
  30. package/dist/commands/run/projects/list/index.js +66 -0
  31. package/dist/commands/run/projects/update/index.d.ts +15 -0
  32. package/dist/commands/run/projects/update/index.js +86 -0
  33. package/dist/commands/run/secrets/delete/index.d.ts +13 -0
  34. package/dist/commands/run/secrets/delete/index.js +65 -0
  35. package/dist/commands/run/secrets/get/index.d.ts +13 -0
  36. package/dist/commands/run/secrets/get/index.js +52 -0
  37. package/dist/commands/run/secrets/list/index.d.ts +11 -0
  38. package/dist/commands/run/secrets/list/index.js +62 -0
  39. package/dist/commands/run/secrets/set/index.d.ts +15 -0
  40. package/dist/commands/run/secrets/set/index.js +74 -0
  41. package/dist/commands/run/sessions/delete/index.d.ts +13 -0
  42. package/dist/commands/run/sessions/delete/index.js +65 -0
  43. package/dist/commands/run/sessions/get/index.d.ts +13 -0
  44. package/dist/commands/run/sessions/get/index.js +72 -0
  45. package/dist/commands/run/sessions/list/index.d.ts +12 -0
  46. package/dist/commands/run/sessions/list/index.js +64 -0
  47. package/dist/commands/run/sessions/start/index.d.ts +13 -0
  48. package/dist/commands/run/sessions/start/index.js +56 -0
  49. package/dist/commands/run/sessions/stop/index.d.ts +13 -0
  50. package/dist/commands/run/sessions/stop/index.js +56 -0
  51. package/dist/commands/run/sink/get/index.d.ts +13 -0
  52. package/dist/commands/run/sink/get/index.js +63 -0
  53. package/dist/commands/workspace/pull/index.d.ts +28 -0
  54. package/dist/commands/workspace/pull/index.js +238 -0
  55. package/dist/commands/workspace/push/index.d.ts +19 -0
  56. package/dist/commands/workspace/push/index.js +163 -0
  57. package/dist/lib/base-run-command.d.ts +42 -0
  58. package/dist/lib/base-run-command.js +75 -0
  59. package/dist/lib/run-http-client.d.ts +58 -0
  60. package/dist/lib/run-http-client.js +136 -0
  61. package/dist/lib/run-types.d.ts +226 -0
  62. package/dist/lib/run-types.js +5 -0
  63. package/oclif.manifest.json +1470 -218
  64. package/package.json +1 -1
  65. package/dist/commands/ephemeral/run/job/index.js +0 -311
  66. package/dist/commands/ephemeral/run/service/index.js +0 -287
@@ -0,0 +1,238 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import * as yaml from 'js-yaml';
6
+ import BaseCommand from '../../../base-command.js';
7
+ export default class Pull extends BaseCommand {
8
+ static args = {
9
+ directory: Args.string({
10
+ description: 'Output directory for pulled documents',
11
+ required: true,
12
+ }),
13
+ };
14
+ static flags = {
15
+ ...BaseCommand.baseFlags,
16
+ workspace: Flags.string({
17
+ char: 'w',
18
+ description: 'Workspace ID (optional if set in profile)',
19
+ required: false,
20
+ }),
21
+ env: Flags.boolean({
22
+ description: 'Include environment variables',
23
+ required: false,
24
+ default: false,
25
+ }),
26
+ records: Flags.boolean({
27
+ description: 'Include records',
28
+ required: false,
29
+ default: false,
30
+ }),
31
+ };
32
+ static description = 'Pull a workspace multidoc from the Xano Metadata API and split into individual files';
33
+ static examples = [
34
+ `$ xano workspace pull ./my-workspace
35
+ Pulled 42 documents to ./my-workspace
36
+ `,
37
+ `$ xano workspace pull ./output -w 40
38
+ Pulled 15 documents to ./output
39
+ `,
40
+ `$ xano workspace pull ./backup --profile production --env --records
41
+ Pulled 58 documents to ./backup
42
+ `,
43
+ ];
44
+ async run() {
45
+ const { args, flags } = await this.parse(Pull);
46
+ // Get profile name (default or from flag/env)
47
+ const profileName = flags.profile || this.getDefaultProfile();
48
+ // Load credentials
49
+ const credentials = this.loadCredentials();
50
+ // Get the profile configuration
51
+ if (!(profileName in credentials.profiles)) {
52
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
53
+ `Create a profile using 'xano profile:create'`);
54
+ }
55
+ const profile = credentials.profiles[profileName];
56
+ // Validate required fields
57
+ if (!profile.instance_origin) {
58
+ this.error(`Profile '${profileName}' is missing instance_origin`);
59
+ }
60
+ if (!profile.access_token) {
61
+ this.error(`Profile '${profileName}' is missing access_token`);
62
+ }
63
+ // Determine workspace_id from flag or profile
64
+ let workspaceId;
65
+ if (flags.workspace) {
66
+ workspaceId = flags.workspace;
67
+ }
68
+ else if (profile.workspace) {
69
+ workspaceId = profile.workspace;
70
+ }
71
+ else {
72
+ this.error(`Workspace ID is required. Either:\n` +
73
+ ` 1. Provide it as a flag: xano workspace pull <directory> -w <workspace_id>\n` +
74
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
75
+ }
76
+ // Build query parameters
77
+ const queryParams = new URLSearchParams({
78
+ env: flags.env.toString(),
79
+ records: flags.records.toString(),
80
+ });
81
+ // Construct the API URL
82
+ const apiUrl = `${profile.instance_origin}/api:meta/beta/workspace/${workspaceId}/multidoc?${queryParams.toString()}`;
83
+ // Fetch multidoc from the API
84
+ let responseText;
85
+ try {
86
+ const response = await fetch(apiUrl, {
87
+ method: 'GET',
88
+ headers: {
89
+ 'accept': 'application/json',
90
+ 'Authorization': `Bearer ${profile.access_token}`,
91
+ },
92
+ });
93
+ if (!response.ok) {
94
+ const errorText = await response.text();
95
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
96
+ }
97
+ responseText = await response.text();
98
+ }
99
+ catch (error) {
100
+ if (error instanceof Error) {
101
+ this.error(`Failed to fetch multidoc: ${error.message}`);
102
+ }
103
+ else {
104
+ this.error(`Failed to fetch multidoc: ${String(error)}`);
105
+ }
106
+ }
107
+ // Split the response into individual documents
108
+ const rawDocuments = responseText.split('\n---\n');
109
+ // Parse each document
110
+ const documents = [];
111
+ for (const raw of rawDocuments) {
112
+ const trimmed = raw.trim();
113
+ if (!trimmed) {
114
+ continue;
115
+ }
116
+ const parsed = this.parseDocument(trimmed);
117
+ if (parsed) {
118
+ documents.push(parsed);
119
+ }
120
+ }
121
+ if (documents.length === 0) {
122
+ this.log('No documents found in response');
123
+ return;
124
+ }
125
+ // Resolve the output directory
126
+ const outputDir = path.resolve(args.directory);
127
+ // Create the output directory if it doesn't exist
128
+ fs.mkdirSync(outputDir, { recursive: true });
129
+ // Track filenames per type to handle duplicates
130
+ const filenameCounters = new Map();
131
+ let writtenCount = 0;
132
+ for (const doc of documents) {
133
+ // Create the type subdirectory
134
+ const typeDir = path.join(outputDir, doc.type);
135
+ fs.mkdirSync(typeDir, { recursive: true });
136
+ // Build the base filename
137
+ let baseName = this.sanitizeFilename(doc.name);
138
+ if (doc.verb) {
139
+ baseName = `${baseName}_${doc.verb}`;
140
+ }
141
+ // Track duplicates per type directory
142
+ if (!filenameCounters.has(doc.type)) {
143
+ filenameCounters.set(doc.type, new Map());
144
+ }
145
+ const typeCounters = filenameCounters.get(doc.type);
146
+ const count = typeCounters.get(baseName) || 0;
147
+ typeCounters.set(baseName, count + 1);
148
+ // Append numeric suffix for duplicates
149
+ let filename;
150
+ if (count === 0) {
151
+ filename = `${baseName}.xs`;
152
+ }
153
+ else {
154
+ filename = `${baseName}_${count + 1}.xs`;
155
+ }
156
+ const filePath = path.join(typeDir, filename);
157
+ fs.writeFileSync(filePath, doc.content, 'utf8');
158
+ writtenCount++;
159
+ }
160
+ this.log(`Pulled ${writtenCount} documents to ${args.directory}`);
161
+ }
162
+ /**
163
+ * Parse a single document to extract its type, name, and optional verb.
164
+ * Skips leading comment lines (starting with //) to find the first
165
+ * meaningful line containing the type keyword and name.
166
+ */
167
+ parseDocument(content) {
168
+ const lines = content.split('\n');
169
+ // Find the first non-comment line
170
+ let firstLine = null;
171
+ for (const line of lines) {
172
+ const trimmedLine = line.trim();
173
+ if (trimmedLine && !trimmedLine.startsWith('//')) {
174
+ firstLine = trimmedLine;
175
+ break;
176
+ }
177
+ }
178
+ if (!firstLine) {
179
+ return null;
180
+ }
181
+ // Parse the type keyword and name from the first meaningful line
182
+ // Expected formats:
183
+ // type name {
184
+ // type name verb=GET {
185
+ // type "name with spaces" {
186
+ // type "name with spaces" verb=PATCH {
187
+ const match = firstLine.match(/^(\w+)\s+("(?:[^"\\]|\\.)*"|\S+)(?:\s+(.*))?/);
188
+ if (!match) {
189
+ return null;
190
+ }
191
+ const type = match[1];
192
+ let name = match[2];
193
+ const rest = match[3] || '';
194
+ // Strip surrounding quotes from the name
195
+ if (name.startsWith('"') && name.endsWith('"')) {
196
+ name = name.slice(1, -1);
197
+ }
198
+ // Extract verb if present (e.g., verb=GET)
199
+ let verb;
200
+ const verbMatch = rest.match(/verb=(\S+)/);
201
+ if (verbMatch) {
202
+ verb = verbMatch[1];
203
+ }
204
+ return { type, name, verb, content };
205
+ }
206
+ /**
207
+ * Sanitize a document name for use as a filename.
208
+ * Strips quotes, replaces spaces with underscores, and removes
209
+ * characters that are unsafe in filenames.
210
+ */
211
+ sanitizeFilename(name) {
212
+ return name
213
+ .replace(/"/g, '')
214
+ .replace(/\s+/g, '_')
215
+ .replace(/[<>:"/\\|?*]/g, '_');
216
+ }
217
+ loadCredentials() {
218
+ const configDir = path.join(os.homedir(), '.xano');
219
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
220
+ // Check if credentials file exists
221
+ if (!fs.existsSync(credentialsPath)) {
222
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
223
+ `Create a profile using 'xano profile:create'`);
224
+ }
225
+ // Read credentials file
226
+ try {
227
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
228
+ const parsed = yaml.load(fileContent);
229
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
230
+ this.error('Credentials file has invalid format.');
231
+ }
232
+ return parsed;
233
+ }
234
+ catch (error) {
235
+ this.error(`Failed to parse credentials file: ${error}`);
236
+ }
237
+ }
238
+ }
@@ -0,0 +1,19 @@
1
+ import BaseCommand from '../../../base-command.js';
2
+ export default class Push extends BaseCommand {
3
+ static args: {
4
+ directory: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ static flags: {
9
+ workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ /**
14
+ * Recursively collect all .xs files from a directory, sorted by
15
+ * type subdirectory name then filename for deterministic ordering.
16
+ */
17
+ private collectFiles;
18
+ private loadCredentials;
19
+ }
@@ -0,0 +1,163 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import * as yaml from 'js-yaml';
6
+ import BaseCommand from '../../../base-command.js';
7
+ export default class Push extends BaseCommand {
8
+ static args = {
9
+ directory: Args.string({
10
+ description: 'Directory containing documents to push (as produced by workspace pull)',
11
+ required: true,
12
+ }),
13
+ };
14
+ static description = 'Push local documents to a workspace via the Xano Metadata API multidoc endpoint';
15
+ static examples = [
16
+ `$ xano workspace push ./my-workspace
17
+ Pushed 42 documents from ./my-workspace
18
+ `,
19
+ `$ xano workspace push ./output -w 40
20
+ Pushed 15 documents from ./output
21
+ `,
22
+ `$ xano workspace push ./backup --profile production
23
+ Pushed 58 documents from ./backup
24
+ `,
25
+ ];
26
+ static flags = {
27
+ ...BaseCommand.baseFlags,
28
+ workspace: Flags.string({
29
+ char: 'w',
30
+ description: 'Workspace ID (optional if set in profile)',
31
+ required: false,
32
+ }),
33
+ };
34
+ async run() {
35
+ const { args, flags } = await this.parse(Push);
36
+ // Get profile name (default or from flag/env)
37
+ const profileName = flags.profile || this.getDefaultProfile();
38
+ // Load credentials
39
+ const credentials = this.loadCredentials();
40
+ // Get the profile configuration
41
+ if (!(profileName in credentials.profiles)) {
42
+ this.error(`Profile '${profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
43
+ `Create a profile using 'xano profile:create'`);
44
+ }
45
+ const profile = credentials.profiles[profileName];
46
+ // Validate required fields
47
+ if (!profile.instance_origin) {
48
+ this.error(`Profile '${profileName}' is missing instance_origin`);
49
+ }
50
+ if (!profile.access_token) {
51
+ this.error(`Profile '${profileName}' is missing access_token`);
52
+ }
53
+ // Determine workspace_id from flag or profile
54
+ let workspaceId;
55
+ if (flags.workspace) {
56
+ workspaceId = flags.workspace;
57
+ }
58
+ else if (profile.workspace) {
59
+ workspaceId = profile.workspace;
60
+ }
61
+ else {
62
+ this.error(`Workspace ID is required. Either:\n` +
63
+ ` 1. Provide it as a flag: xano workspace push <directory> -w <workspace_id>\n` +
64
+ ` 2. Set it in your profile using: xano profile:edit ${profileName} -w <workspace_id>`);
65
+ }
66
+ // Resolve the input directory
67
+ const inputDir = path.resolve(args.directory);
68
+ if (!fs.existsSync(inputDir)) {
69
+ this.error(`Directory not found: ${inputDir}`);
70
+ }
71
+ if (!fs.statSync(inputDir).isDirectory()) {
72
+ this.error(`Not a directory: ${inputDir}`);
73
+ }
74
+ // Collect all .xs files from the directory tree
75
+ const files = this.collectFiles(inputDir);
76
+ if (files.length === 0) {
77
+ this.error(`No .xs files found in ${args.directory}`);
78
+ }
79
+ // Read each file and join with --- separator
80
+ const documents = [];
81
+ for (const filePath of files) {
82
+ const content = fs.readFileSync(filePath, 'utf8').trim();
83
+ if (content) {
84
+ documents.push(content);
85
+ }
86
+ }
87
+ if (documents.length === 0) {
88
+ this.error(`All .xs files in ${args.directory} are empty`);
89
+ }
90
+ const multidoc = documents.join('\n---\n');
91
+ // Construct the API URL
92
+ const apiUrl = `${profile.instance_origin}/api:meta/beta/workspace/${workspaceId}/multidoc`;
93
+ // POST the multidoc to the API
94
+ try {
95
+ const response = await fetch(apiUrl, {
96
+ method: 'POST',
97
+ headers: {
98
+ 'accept': 'application/json',
99
+ 'Authorization': `Bearer ${profile.access_token}`,
100
+ 'Content-Type': 'text/x-xanoscript',
101
+ },
102
+ body: multidoc,
103
+ });
104
+ if (!response.ok) {
105
+ const errorText = await response.text();
106
+ this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
107
+ }
108
+ // Log the response if any
109
+ const responseText = await response.text();
110
+ if (responseText) {
111
+ this.log(responseText);
112
+ }
113
+ }
114
+ catch (error) {
115
+ if (error instanceof Error) {
116
+ this.error(`Failed to push multidoc: ${error.message}`);
117
+ }
118
+ else {
119
+ this.error(`Failed to push multidoc: ${String(error)}`);
120
+ }
121
+ }
122
+ this.log(`Pushed ${documents.length} documents from ${args.directory}`);
123
+ }
124
+ /**
125
+ * Recursively collect all .xs files from a directory, sorted by
126
+ * type subdirectory name then filename for deterministic ordering.
127
+ */
128
+ collectFiles(dir) {
129
+ const files = [];
130
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ const fullPath = path.join(dir, entry.name);
133
+ if (entry.isDirectory()) {
134
+ files.push(...this.collectFiles(fullPath));
135
+ }
136
+ else if (entry.isFile() && entry.name.endsWith('.xs')) {
137
+ files.push(fullPath);
138
+ }
139
+ }
140
+ return files.sort();
141
+ }
142
+ loadCredentials() {
143
+ const configDir = path.join(os.homedir(), '.xano');
144
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
145
+ // Check if credentials file exists
146
+ if (!fs.existsSync(credentialsPath)) {
147
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
148
+ `Create a profile using 'xano profile:create'`);
149
+ }
150
+ // Read credentials file
151
+ try {
152
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
153
+ const parsed = yaml.load(fileContent);
154
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
155
+ this.error('Credentials file has invalid format.');
156
+ }
157
+ return parsed;
158
+ }
159
+ catch (error) {
160
+ this.error(`Failed to parse credentials file: ${error}`);
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Base command for all run commands
3
+ */
4
+ import BaseCommand from '../base-command.js';
5
+ import { RunHttpClient } from './run-http-client.js';
6
+ export interface ProfileConfig {
7
+ account_origin?: string;
8
+ instance_origin: string;
9
+ access_token: string;
10
+ workspace?: string;
11
+ branch?: string;
12
+ project?: string;
13
+ run_project?: string;
14
+ run_base_url?: string;
15
+ }
16
+ export interface CredentialsFile {
17
+ profiles: {
18
+ [key: string]: ProfileConfig;
19
+ };
20
+ default?: string;
21
+ }
22
+ export default abstract class BaseRunCommand extends BaseCommand {
23
+ protected httpClient: RunHttpClient;
24
+ protected profile: ProfileConfig;
25
+ protected profileName: string;
26
+ /**
27
+ * Initialize the run command with profile and HTTP client
28
+ */
29
+ protected initRunCommand(profileFlag?: string): Promise<void>;
30
+ /**
31
+ * Initialize with project required
32
+ */
33
+ protected initRunCommandWithProject(profileFlag?: string): Promise<void>;
34
+ /**
35
+ * Load credentials from file
36
+ */
37
+ protected loadCredentials(): CredentialsFile;
38
+ /**
39
+ * Format a response for JSON output
40
+ */
41
+ protected outputJson(data: unknown): void;
42
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Base command for all run commands
3
+ */
4
+ import * as fs from 'node:fs';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import * as yaml from 'js-yaml';
8
+ import BaseCommand from '../base-command.js';
9
+ import { DEFAULT_RUN_BASE_URL, RunHttpClient } from './run-http-client.js';
10
+ export default class BaseRunCommand extends BaseCommand {
11
+ httpClient;
12
+ profile;
13
+ profileName;
14
+ /**
15
+ * Initialize the run command with profile and HTTP client
16
+ */
17
+ async initRunCommand(profileFlag) {
18
+ this.profileName = profileFlag || this.getDefaultProfile();
19
+ const credentials = this.loadCredentials();
20
+ if (!(this.profileName in credentials.profiles)) {
21
+ this.error(`Profile '${this.profileName}' not found. Available profiles: ${Object.keys(credentials.profiles).join(', ')}\n` +
22
+ `Create a profile using 'xano profile:create'`);
23
+ }
24
+ this.profile = credentials.profiles[this.profileName];
25
+ if (!this.profile.access_token) {
26
+ this.error(`Profile '${this.profileName}' is missing access_token`);
27
+ }
28
+ const baseUrl = this.profile.run_base_url || DEFAULT_RUN_BASE_URL;
29
+ // Use run_project if available, fall back to project for backward compatibility
30
+ const projectId = this.profile.run_project || this.profile.project;
31
+ this.httpClient = new RunHttpClient({
32
+ baseUrl,
33
+ authToken: this.profile.access_token,
34
+ projectId,
35
+ });
36
+ }
37
+ /**
38
+ * Initialize with project required
39
+ */
40
+ async initRunCommandWithProject(profileFlag) {
41
+ await this.initRunCommand(profileFlag);
42
+ if (!this.profile.run_project && !this.profile.project) {
43
+ this.error(`Profile '${this.profileName}' is missing run_project. ` +
44
+ `Run 'xano profile:wizard' to set up your profile or use 'xano profile:edit --run-project <project-id>'`);
45
+ }
46
+ }
47
+ /**
48
+ * Load credentials from file
49
+ */
50
+ loadCredentials() {
51
+ const configDir = path.join(os.homedir(), '.xano');
52
+ const credentialsPath = path.join(configDir, 'credentials.yaml');
53
+ if (!fs.existsSync(credentialsPath)) {
54
+ this.error(`Credentials file not found at ${credentialsPath}\n` +
55
+ `Create a profile using 'xano profile:create'`);
56
+ }
57
+ try {
58
+ const fileContent = fs.readFileSync(credentialsPath, 'utf8');
59
+ const parsed = yaml.load(fileContent);
60
+ if (!parsed || typeof parsed !== 'object' || !('profiles' in parsed)) {
61
+ this.error('Credentials file has invalid format.');
62
+ }
63
+ return parsed;
64
+ }
65
+ catch (error) {
66
+ this.error(`Failed to parse credentials file: ${error}`);
67
+ }
68
+ }
69
+ /**
70
+ * Format a response for JSON output
71
+ */
72
+ outputJson(data) {
73
+ this.log(JSON.stringify(data, null, 2));
74
+ }
75
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * HTTP client for Xano Run API
3
+ * Based on @xano/run-sdk HttpClient
4
+ */
5
+ export declare const DEFAULT_RUN_BASE_URL = "https://app.xano.com/";
6
+ export interface RunHttpClientConfig {
7
+ baseUrl: string;
8
+ authToken: string;
9
+ projectId?: string;
10
+ }
11
+ export declare class RunHttpClient {
12
+ private readonly config;
13
+ constructor(config: RunHttpClientConfig);
14
+ /**
15
+ * Get the project ID
16
+ */
17
+ getProjectId(): string | undefined;
18
+ /**
19
+ * Build headers for a request
20
+ */
21
+ getHeaders(contentType?: string): HeadersInit;
22
+ /**
23
+ * Build a URL with optional query parameters
24
+ */
25
+ buildUrl(path: string, queryParams?: Record<string, unknown>): string;
26
+ /**
27
+ * Build a URL scoped to the current project
28
+ */
29
+ buildProjectUrl(path: string, queryParams?: Record<string, unknown>): string;
30
+ /**
31
+ * Build a URL scoped to a specific session
32
+ */
33
+ buildSessionUrl(sessionId: string, path?: string, queryParams?: Record<string, unknown>): string;
34
+ /**
35
+ * Make an HTTP request
36
+ */
37
+ request<T>(url: string, options: RequestInit): Promise<T>;
38
+ /**
39
+ * Make a GET request
40
+ */
41
+ get<T>(url: string): Promise<T>;
42
+ /**
43
+ * Make a POST request with JSON body
44
+ */
45
+ post<T>(url: string, body?: unknown): Promise<T>;
46
+ /**
47
+ * Make a POST request with XanoScript body
48
+ */
49
+ postXanoScript<T>(url: string, code: string): Promise<T>;
50
+ /**
51
+ * Make a PATCH request
52
+ */
53
+ patch<T>(url: string, body: unknown): Promise<T>;
54
+ /**
55
+ * Make a DELETE request
56
+ */
57
+ delete<T>(url: string, body?: unknown): Promise<T>;
58
+ }