@xano/cli 1.0.4-beta.1 → 1.0.4-beta.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.
@@ -53,6 +53,12 @@ Push all files except tables
53
53
  `,
54
54
  `$ xano workspace push -i "function/*" -e "**/test*"
55
55
  Push functions but exclude test files
56
+ `,
57
+ `$ xano workspace push -i "knowledge/**"
58
+ Push only knowledge files (agents.md / skills / docs)
59
+ `,
60
+ `$ xano workspace push --sync --delete
61
+ Full sync including knowledge files; removes server objects not present locally
56
62
  `,
57
63
  ];
58
64
  static flags = {
@@ -62,17 +68,17 @@ Push functions but exclude test files
62
68
  description: 'Branch name (optional if set in profile, defaults to live)',
63
69
  required: false,
64
70
  }),
71
+ delete: Flags.boolean({
72
+ default: false,
73
+ description: '[CRITICAL] STOP and confirm with the user before running. Delete workspace objects not included in the push (requires --sync).',
74
+ required: false,
75
+ }),
65
76
  directory: Flags.string({
66
77
  char: 'd',
67
78
  default: '.',
68
79
  description: 'Directory containing documents to push (defaults to current directory)',
69
80
  required: false,
70
81
  }),
71
- delete: Flags.boolean({
72
- default: false,
73
- description: '[CRITICAL] STOP and confirm with the user before running. Delete workspace objects not included in the push (requires --sync).',
74
- required: false,
75
- }),
76
82
  'dry-run': Flags.boolean({
77
83
  default: false,
78
84
  description: 'Show preview of changes without pushing (exit after preview)',
@@ -186,6 +192,10 @@ Push functions but exclude test files
186
192
  branch,
187
193
  command: this,
188
194
  inputDir,
195
+ knowledge: {
196
+ listUrl: () => `${baseUrl}/knowledge/sync`,
197
+ rootDir: inputDir,
198
+ },
189
199
  verboseFetch: this.verboseFetch.bind(this),
190
200
  }, target, pushFlags);
191
201
  }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Input assembly for `xano function run`.
3
+ *
4
+ * A run's `input` is a single JSON object sent to the Metadata API. Users can
5
+ * build that object from several sources, which this module normalizes and
6
+ * merges:
7
+ *
8
+ * --json <inline | @file | -> a base JSON object
9
+ * --data key=value a string field (httpie convention)
10
+ * --data key:=<json> a raw-JSON field (numbers, bools, arrays, ...)
11
+ * --data key@<file> a field whose value is a file's contents (string)
12
+ *
13
+ * Merge order is: JSON base first, then --data pairs override — so the common
14
+ * "reuse a payload file, tweak one field" flow works without editing the file.
15
+ *
16
+ * Everything here is pure and free of oclif/network deps so it is unit-testable
17
+ * in isolation; the command layer handles fetching, prompting and output.
18
+ */
19
+ export type JsonObject = Record<string, unknown>;
20
+ /** A single declared function input parameter, as returned by the meta API. */
21
+ export interface FunctionInputParam {
22
+ children?: FunctionInputParam[];
23
+ default?: unknown;
24
+ description?: string;
25
+ name: string;
26
+ nullable?: boolean;
27
+ required?: boolean;
28
+ style?: {
29
+ type?: string;
30
+ };
31
+ type?: string;
32
+ values?: unknown[];
33
+ }
34
+ /**
35
+ * Split a `--data` token into its key, operator and value.
36
+ * Recognizes, in priority order: `:=` (raw json), `@` (file), `=` (string).
37
+ * The operator is the FIRST occurrence scanned left-to-right, but `:=` is
38
+ * detected before a bare `=` at the same position so `k:=1` is raw-json, not
39
+ * a string key `k:` set to `=1`.
40
+ */
41
+ export declare function splitDataToken(token: string): {
42
+ key: string;
43
+ op: ':=' | '=' | '@';
44
+ value: string;
45
+ };
46
+ /**
47
+ * Parse `--data` tokens into an object, applying httpie-style typing.
48
+ * Later tokens override earlier ones with the same key.
49
+ */
50
+ export declare function parseDataPairs(pairs: string[], readFile?: (p: string) => string): JsonObject;
51
+ /**
52
+ * Load the `--json` source: inline JSON, `@file`, or `-` (stdin, supplied by
53
+ * the caller via `stdin`). Must parse to a plain object; arrays/scalars are
54
+ * rejected because a function's input is always a keyed object.
55
+ */
56
+ export declare function loadJsonSource(source: string | undefined, opts?: {
57
+ readFile?: (p: string) => string;
58
+ stdin?: () => string;
59
+ }): JsonObject | undefined;
60
+ /**
61
+ * Merge a JSON base with --data overrides into the final input object.
62
+ * Precedence: base first, then dataPairs override (shallow).
63
+ */
64
+ export declare function assembleInput(opts: {
65
+ dataPairs?: JsonObject;
66
+ jsonBase?: JsonObject;
67
+ }): JsonObject;
68
+ /**
69
+ * Validate a supplied input object against a function's declared input schema.
70
+ * Non-destructive: returns lists the caller can act on (prompt for missing
71
+ * required params on a TTY, warn on enum mismatches, etc.). The server remains
72
+ * the source of truth, so type mismatches are warnings, not hard failures.
73
+ */
74
+ export declare function validateAgainstSchema(input: JsonObject, schema: FunctionInputParam[] | undefined): {
75
+ missingRequired: FunctionInputParam[];
76
+ warnings: string[];
77
+ };
@@ -0,0 +1,148 @@
1
+ import * as fs from 'node:fs';
2
+ /**
3
+ * Split a `--data` token into its key, operator and value.
4
+ * Recognizes, in priority order: `:=` (raw json), `@` (file), `=` (string).
5
+ * The operator is the FIRST occurrence scanned left-to-right, but `:=` is
6
+ * detected before a bare `=` at the same position so `k:=1` is raw-json, not
7
+ * a string key `k:` set to `=1`.
8
+ */
9
+ export function splitDataToken(token) {
10
+ // Find the earliest `=` and `@`; `:=` is an `=` preceded by `:`.
11
+ const eq = token.indexOf('=');
12
+ const at = token.indexOf('@');
13
+ // Neither operator present -> invalid.
14
+ if (eq === -1 && at === -1) {
15
+ throw new Error(`Invalid --data '${token}'. Expected key=value, key:=json, or key@file.`);
16
+ }
17
+ // Choose whichever operator appears first. On a tie an '=' can't tie with '@'
18
+ // at the same index, so a simple "smallest index wins" is unambiguous.
19
+ const useAt = at !== -1 && (eq === -1 || at < eq);
20
+ if (useAt) {
21
+ const key = token.slice(0, at);
22
+ if (!key)
23
+ throw new Error(`Invalid --data '${token}': missing key before '@'.`);
24
+ return { key, op: '@', value: token.slice(at + 1) };
25
+ }
26
+ // `=` path — check for the `:=` raw-json form (a ':' immediately before '=').
27
+ if (eq > 0 && token[eq - 1] === ':') {
28
+ const key = token.slice(0, eq - 1);
29
+ if (!key)
30
+ throw new Error(`Invalid --data '${token}': missing key before ':='.`);
31
+ return { key, op: ':=', value: token.slice(eq + 1) };
32
+ }
33
+ const key = token.slice(0, eq);
34
+ if (!key)
35
+ throw new Error(`Invalid --data '${token}': missing key before '='.`);
36
+ return { key, op: '=', value: token.slice(eq + 1) };
37
+ }
38
+ /**
39
+ * Parse `--data` tokens into an object, applying httpie-style typing.
40
+ * Later tokens override earlier ones with the same key.
41
+ */
42
+ export function parseDataPairs(pairs, readFile = defaultReadFile) {
43
+ const out = {};
44
+ for (const token of pairs) {
45
+ const { key, op, value } = splitDataToken(token);
46
+ switch (op) {
47
+ case ':=': {
48
+ try {
49
+ out[key] = JSON.parse(value);
50
+ }
51
+ catch (error) {
52
+ throw new Error(`Invalid JSON for --data '${key}:=': ${error.message}`);
53
+ }
54
+ break;
55
+ }
56
+ case '@': {
57
+ out[key] = readFile(value);
58
+ break;
59
+ }
60
+ default: {
61
+ out[key] = value;
62
+ }
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+ /**
68
+ * Load the `--json` source: inline JSON, `@file`, or `-` (stdin, supplied by
69
+ * the caller via `stdin`). Must parse to a plain object; arrays/scalars are
70
+ * rejected because a function's input is always a keyed object.
71
+ */
72
+ export function loadJsonSource(source, opts = {}) {
73
+ if (source === undefined)
74
+ return undefined;
75
+ const readFile = opts.readFile ?? defaultReadFile;
76
+ let raw;
77
+ if (source === '-') {
78
+ if (!opts.stdin)
79
+ throw new Error('stdin reader not available for --json -');
80
+ raw = opts.stdin();
81
+ }
82
+ else if (source.startsWith('@')) {
83
+ raw = readFile(source.slice(1));
84
+ }
85
+ else {
86
+ raw = source;
87
+ }
88
+ if (raw.trim() === '')
89
+ return undefined;
90
+ let parsed;
91
+ try {
92
+ parsed = JSON.parse(raw);
93
+ }
94
+ catch (error) {
95
+ throw new Error(`Invalid JSON input: ${error.message}`);
96
+ }
97
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
98
+ throw new Error('Function input must be a JSON object (e.g. {"name":"..."}), not an array or scalar.');
99
+ }
100
+ return parsed;
101
+ }
102
+ /**
103
+ * Merge a JSON base with --data overrides into the final input object.
104
+ * Precedence: base first, then dataPairs override (shallow).
105
+ */
106
+ export function assembleInput(opts) {
107
+ return { ...opts.jsonBase, ...opts.dataPairs };
108
+ }
109
+ /**
110
+ * Validate a supplied input object against a function's declared input schema.
111
+ * Non-destructive: returns lists the caller can act on (prompt for missing
112
+ * required params on a TTY, warn on enum mismatches, etc.). The server remains
113
+ * the source of truth, so type mismatches are warnings, not hard failures.
114
+ */
115
+ export function validateAgainstSchema(input, schema) {
116
+ const missingRequired = [];
117
+ const warnings = [];
118
+ if (!schema)
119
+ return { missingRequired, warnings };
120
+ for (const param of schema) {
121
+ const present = Object.hasOwn(input, param.name);
122
+ if (!present) {
123
+ // A required param with no default must be supplied.
124
+ if (param.required && param.default === undefined) {
125
+ missingRequired.push(param);
126
+ }
127
+ continue;
128
+ }
129
+ const value = input[param.name];
130
+ if (value === null && param.nullable === false) {
131
+ warnings.push(`Input '${param.name}' is null but the parameter is not nullable.`);
132
+ }
133
+ // Enum membership check (only for non-null scalar values).
134
+ if (Array.isArray(param.values) && param.values.length > 0 && value !== null && !param.values.includes(value)) {
135
+ warnings.push(`Input '${param.name}' = ${JSON.stringify(value)} is not one of the allowed values: ` +
136
+ param.values.map((v) => JSON.stringify(v)).join(', '));
137
+ }
138
+ }
139
+ return { missingRequired, warnings };
140
+ }
141
+ function defaultReadFile(p) {
142
+ try {
143
+ return fs.readFileSync(p, 'utf8');
144
+ }
145
+ catch (error) {
146
+ throw new Error(`Failed to read file '${p}': ${error.message}`);
147
+ }
148
+ }
@@ -0,0 +1,113 @@
1
+ export declare const KNOWLEDGE_DIR = "knowledge";
2
+ export type KnowledgeType = 'agents.md' | 'doc' | 'skill';
3
+ /** A reference file attached to a skill (knowledge_file). */
4
+ export interface KnowledgeFile {
5
+ content: string;
6
+ /** Path relative to the skill's `references/` folder, POSIX-separated. */
7
+ name: string;
8
+ }
9
+ /** A workspace-scoped knowledge object as exchanged with the API. */
10
+ export interface KnowledgeObject {
11
+ content: string;
12
+ description?: string;
13
+ enabled?: boolean;
14
+ files?: KnowledgeFile[];
15
+ guid?: string;
16
+ knowledge_type: KnowledgeType;
17
+ mode?: string;
18
+ name: string;
19
+ scope?: string;
20
+ }
21
+ /** A locally-collected object that also remembers its primary file on disk (for GUID writeback). */
22
+ export interface LocalKnowledgeObject extends KnowledgeObject {
23
+ filePath: string;
24
+ }
25
+ export interface KnowledgePushBody {
26
+ branch?: string;
27
+ delete?: boolean;
28
+ dry_run?: boolean;
29
+ force?: boolean;
30
+ items: KnowledgeObject[];
31
+ }
32
+ export interface KnowledgePushResult {
33
+ deleted?: number;
34
+ guid_map?: Array<{
35
+ guid: string;
36
+ name: string;
37
+ }>;
38
+ imported?: number;
39
+ operations?: KnowledgeDryRunOperation[];
40
+ summary?: Record<string, KnowledgeDryRunSummary>;
41
+ }
42
+ export interface KnowledgeDryRunOperation {
43
+ action: string;
44
+ name: string;
45
+ type: string;
46
+ }
47
+ export interface KnowledgeDryRunSummary {
48
+ created: number;
49
+ deleted: number;
50
+ unchanged: number;
51
+ updated: number;
52
+ }
53
+ export interface KnowledgeDryRunResult {
54
+ operations: KnowledgeDryRunOperation[];
55
+ summary: Record<string, KnowledgeDryRunSummary>;
56
+ }
57
+ type VerboseFetch = (url: string, options: RequestInit, verbose: boolean, authToken?: string) => Promise<Response>;
58
+ /**
59
+ * Build a primary `.md` file body: YAML frontmatter (built from the object's
60
+ * structured fields) followed by the markdown content.
61
+ */
62
+ export declare function buildPrimaryContent(obj: KnowledgeObject): string;
63
+ /**
64
+ * Split a primary `.md` file into its frontmatter fields and markdown body.
65
+ * Returns `{meta: {}, body: <raw>}` when there is no leading `---` block.
66
+ */
67
+ export declare function parsePrimaryContent(raw: string): {
68
+ body: string;
69
+ meta: Record<string, unknown>;
70
+ };
71
+ /** Read just `guid`/`name` from frontmatter (used by GUID matching). */
72
+ export declare function parseFrontmatter(content: string): {
73
+ guid?: string;
74
+ name?: string;
75
+ };
76
+ /**
77
+ * Write knowledge objects under `<outputDir>/knowledge/`, building frontmatter
78
+ * and laying out skill reference files under `<skill>/references/`.
79
+ * Returns the number of files written (primaries + references).
80
+ */
81
+ export declare function writeKnowledge(objects: KnowledgeObject[], outputDir: string): number;
82
+ /**
83
+ * Walk `<inputDir>/knowledge/`, reconstructing structured knowledge objects from
84
+ * primary `.md` files (frontmatter → fields, body → content) and attaching each
85
+ * skill's `references/` files. Include/exclude globs are matched against
86
+ * `knowledge/<path>` (relative to inputDir), consistent with multidoc filtering.
87
+ * Returns `[]` when the directory is absent.
88
+ */
89
+ export declare function collectKnowledgeObjects(inputDir: string, include?: string[], exclude?: string[]): LocalKnowledgeObject[];
90
+ /** Strip the local-only `filePath` before sending objects to the API. */
91
+ export declare function toPushItems(objects: LocalKnowledgeObject[]): KnowledgeObject[];
92
+ /**
93
+ * GET workspace knowledge objects (with content). Returns `[]` on 404 (instance
94
+ * without the feature) or any network error, so pull degrades gracefully.
95
+ */
96
+ export declare function fetchKnowledge(baseUrl: string, branch: string, accessToken: string, verboseFetch: VerboseFetch, verbose: boolean): Promise<KnowledgeObject[]>;
97
+ /**
98
+ * POST knowledge objects to the workspace. Throws on non-2xx. When `body.dry_run`
99
+ * is set the result may carry `operations`/`summary` instead of `imported`.
100
+ */
101
+ export declare function pushKnowledge(url: string, accessToken: string, verboseFetch: VerboseFetch, verbose: boolean, body: KnowledgePushBody): Promise<KnowledgePushResult>;
102
+ /**
103
+ * Compute a dry-run-style preview by diffing local objects against server
104
+ * objects (match by `guid`, then `name`). Used when the server doesn't honor
105
+ * the `dry_run` flag.
106
+ */
107
+ export declare function knowledgePreview(local: KnowledgeObject[], server: KnowledgeObject[], willDelete: boolean): KnowledgeDryRunResult;
108
+ /**
109
+ * Set or update `guid:` in a primary `.md` file's YAML frontmatter.
110
+ * Returns true if the file was modified.
111
+ */
112
+ export declare function syncGuidToFrontmatter(filePath: string, guid: string): boolean;
113
+ export {};