@xano/cli 1.0.4-beta.11 → 1.0.4-beta.12
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/README.md +26 -1
- package/dist/commands/function/edit/index.js +1 -1
- package/dist/commands/function/run/index.js +1 -1
- package/dist/commands/knowledge/get/index.d.ts +18 -0
- package/dist/commands/knowledge/get/index.js +126 -0
- package/dist/commands/knowledge/list/index.d.ts +18 -0
- package/dist/commands/knowledge/list/index.js +135 -0
- package/dist/utils/knowledge-sync.js +18 -1
- package/oclif.manifest.json +3260 -3045
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ npm install -g @xano/cli
|
|
|
35
35
|
Destructive commands include imperative safety prefixes in their help text and flag descriptions so automated agents (e.g. Claude Code, Cursor) pause before running them in auto-accept mode:
|
|
36
36
|
|
|
37
37
|
- **`[CRITICAL]`** — Agents must STOP and confirm with the user before running. Used for irreversible or high-blast-radius operations, including (but not limited to) `--force` deletions, `workspace edit --allow-push`, `workspace push --records`, `workspace/sandbox push --truncate`, `--no-transaction`, `--sync --delete`, backup restore/delete, cluster delete, `env set_all`, `sandbox reset`, `sandbox delete`, `profile delete`, and tenant deploys.
|
|
38
|
-
- **`[IMPORTANT]`** — Agents should confirm with the user (and prefer `--dry-run` previews where applicable). Used for base `workspace push` and `sandbox push`, `branch set_live`, `release import`, `release push`, `release deploy`,
|
|
38
|
+
- **`[IMPORTANT]`** — Agents should confirm with the user (and prefer `--dry-run` previews where applicable). Used for base `workspace push` and `sandbox push`, `branch set_live`, `release import`, `release push`, `release deploy`, and single-variable env / workflow-test deletes.
|
|
39
39
|
|
|
40
40
|
These warnings are layer 1 of broader push-safety work; ephemeral sandbox environments and push preview remain the structural safeguards.
|
|
41
41
|
|
|
@@ -284,6 +284,31 @@ when the function returns an error status.
|
|
|
284
284
|
> denied with an access error — grant run/debug to the role (or use a token whose role has
|
|
285
285
|
> it). `function list`/`get`/`create`/`edit` only need the Function permission.
|
|
286
286
|
|
|
287
|
+
### Knowledge
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# List all enabled knowledge as markdown (always-on items show full content, on-demand show name + description)
|
|
291
|
+
xano knowledge list -w 40
|
|
292
|
+
|
|
293
|
+
# List as JSON
|
|
294
|
+
xano knowledge list -w 40 --output json
|
|
295
|
+
|
|
296
|
+
# Filter by knowledge type (skill, doc, agents.md)
|
|
297
|
+
xano knowledge list -w 40 --type skill
|
|
298
|
+
|
|
299
|
+
# Include disabled items
|
|
300
|
+
xano knowledge list --no-enabled-only
|
|
301
|
+
|
|
302
|
+
# Get a specific knowledge item's content by name
|
|
303
|
+
xano knowledge get "deploy-runbook" -w 40
|
|
304
|
+
|
|
305
|
+
# Get as JSON (includes all metadata)
|
|
306
|
+
xano knowledge get "deploy-runbook" -w 40 --output json
|
|
307
|
+
|
|
308
|
+
# Get a reference file attached to a knowledge item
|
|
309
|
+
xano knowledge get "deploy-runbook" -w 40 --file checklist.md
|
|
310
|
+
```
|
|
311
|
+
|
|
287
312
|
### Releases
|
|
288
313
|
|
|
289
314
|
All release commands use **release names** (e.g., `v1.0`), not IDs.
|
|
@@ -12,7 +12,7 @@ export default class FunctionEdit extends BaseCommand {
|
|
|
12
12
|
required: false,
|
|
13
13
|
}),
|
|
14
14
|
};
|
|
15
|
-
static description = '
|
|
15
|
+
static description = 'Edit a function in a workspace';
|
|
16
16
|
static examples = [
|
|
17
17
|
`$ xano function:edit 163
|
|
18
18
|
# Fetches the function code and opens it in $EDITOR for editing
|
|
@@ -9,7 +9,7 @@ export default class FunctionRun extends BaseCommand {
|
|
|
9
9
|
required: false,
|
|
10
10
|
}),
|
|
11
11
|
};
|
|
12
|
-
static description = '
|
|
12
|
+
static description = 'Run (execute) a named function in a workspace and print its result';
|
|
13
13
|
static examples = [
|
|
14
14
|
`$ xano function:run calcScore -w 40
|
|
15
15
|
# Prompts for any declared inputs, then runs the function`,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class KnowledgeGet extends BaseCommand {
|
|
3
|
+
static args: {
|
|
4
|
+
name: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
};
|
|
17
|
+
run(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class KnowledgeGet extends BaseCommand {
|
|
4
|
+
static args = {
|
|
5
|
+
name: Args.string({
|
|
6
|
+
description: 'Knowledge item name',
|
|
7
|
+
required: true,
|
|
8
|
+
}),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Get a knowledge item by name, or one of its reference files';
|
|
11
|
+
static examples = [
|
|
12
|
+
'$ xano knowledge get "deploy-runbook" -w 40',
|
|
13
|
+
'$ xano knowledge get "deploy-runbook" -w 40 --output json',
|
|
14
|
+
'$ xano knowledge get "deploy-runbook" -w 40 --file checklist.md',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...BaseCommand.baseFlags,
|
|
18
|
+
branch: Flags.string({
|
|
19
|
+
char: 'b',
|
|
20
|
+
description: 'Branch ID',
|
|
21
|
+
required: false,
|
|
22
|
+
}),
|
|
23
|
+
file: Flags.string({
|
|
24
|
+
char: 'f',
|
|
25
|
+
description: 'Path of a reference file to fetch instead of the main content',
|
|
26
|
+
required: false,
|
|
27
|
+
}),
|
|
28
|
+
output: Flags.string({
|
|
29
|
+
char: 'o',
|
|
30
|
+
default: 'text',
|
|
31
|
+
description: 'Output format',
|
|
32
|
+
options: ['text', 'json'],
|
|
33
|
+
required: false,
|
|
34
|
+
}),
|
|
35
|
+
workspace: Flags.string({
|
|
36
|
+
char: 'w',
|
|
37
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
38
|
+
required: false,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { args, flags } = await this.parse(KnowledgeGet);
|
|
43
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
44
|
+
let workspaceId;
|
|
45
|
+
if (flags.workspace) {
|
|
46
|
+
workspaceId = flags.workspace;
|
|
47
|
+
}
|
|
48
|
+
else if (profile.workspace) {
|
|
49
|
+
workspaceId = profile.workspace;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
53
|
+
` 1. Provide it as a flag: xano knowledge get <name> -w <workspace_id>\n` +
|
|
54
|
+
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
55
|
+
}
|
|
56
|
+
const queryParams = new URLSearchParams();
|
|
57
|
+
queryParams.set('enabled_only', 'false');
|
|
58
|
+
queryParams.set('include_content', 'true');
|
|
59
|
+
if (flags.branch) {
|
|
60
|
+
queryParams.set('branch_id', flags.branch);
|
|
61
|
+
}
|
|
62
|
+
const listUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge?${queryParams.toString()}`;
|
|
63
|
+
try {
|
|
64
|
+
const listResponse = await this.verboseFetch(listUrl, {
|
|
65
|
+
headers: {
|
|
66
|
+
'accept': 'application/json',
|
|
67
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
68
|
+
},
|
|
69
|
+
method: 'GET',
|
|
70
|
+
}, flags.verbose, profile.access_token);
|
|
71
|
+
if (!listResponse.ok) {
|
|
72
|
+
const errorText = await listResponse.text();
|
|
73
|
+
this.error(`API request failed with status ${listResponse.status}: ${listResponse.statusText}\n${errorText}`);
|
|
74
|
+
}
|
|
75
|
+
const data = await listResponse.json();
|
|
76
|
+
const items = Array.isArray(data) ? data : (data.items ?? []);
|
|
77
|
+
const targetName = args.name.toLowerCase();
|
|
78
|
+
const item = items.find((i) => i.name?.toLowerCase() === targetName);
|
|
79
|
+
if (!item) {
|
|
80
|
+
const available = items.map((i) => i.name).filter(Boolean).join(', ');
|
|
81
|
+
this.error(`Knowledge item not found: "${args.name}"\n` +
|
|
82
|
+
(available ? `Available items: ${available}` : 'No knowledge items exist in this workspace.'));
|
|
83
|
+
}
|
|
84
|
+
if (flags.file) {
|
|
85
|
+
const fileParams = new URLSearchParams({ path: flags.file });
|
|
86
|
+
const fileUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge/${item.id}/file?${fileParams.toString()}`;
|
|
87
|
+
const fileResponse = await this.verboseFetch(fileUrl, {
|
|
88
|
+
headers: {
|
|
89
|
+
'accept': 'application/json',
|
|
90
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
91
|
+
},
|
|
92
|
+
method: 'GET',
|
|
93
|
+
}, flags.verbose, profile.access_token);
|
|
94
|
+
if (!fileResponse.ok) {
|
|
95
|
+
const errorText = await fileResponse.text();
|
|
96
|
+
this.error(`Failed to fetch file "${flags.file}" from knowledge item "${item.name}": ${fileResponse.status} ${fileResponse.statusText}\n${errorText}`);
|
|
97
|
+
}
|
|
98
|
+
const fileJson = await fileResponse.json();
|
|
99
|
+
const fileContent = fileJson.file?.content ?? '';
|
|
100
|
+
if (flags.output === 'json') {
|
|
101
|
+
this.log(JSON.stringify({ content: fileContent, name: item.name, path: flags.file }, null, 2));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.log(fileContent);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (flags.output === 'json') {
|
|
109
|
+
this.log(JSON.stringify(item, null, 2));
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
this.log(item.content ?? '');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
if (error instanceof Error && 'oclif' in error)
|
|
117
|
+
throw error;
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
this.error(`Failed to fetch knowledge: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
this.error(`Failed to fetch knowledge: ${String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import BaseCommand from '../../../base-command.js';
|
|
2
|
+
export default class KnowledgeList extends BaseCommand {
|
|
3
|
+
static args: {};
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
branch: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'enabled-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
workspace: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
profile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
verbose: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
private formatMarkdown;
|
|
18
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import BaseCommand from '../../../base-command.js';
|
|
3
|
+
export default class KnowledgeList extends BaseCommand {
|
|
4
|
+
static args = {};
|
|
5
|
+
static description = 'List workspace knowledge and skills as plain-text markdown';
|
|
6
|
+
static examples = [
|
|
7
|
+
`$ xano knowledge list -w 40
|
|
8
|
+
# Always-on Knowledge
|
|
9
|
+
|
|
10
|
+
## deploy-runbook
|
|
11
|
+
|
|
12
|
+
# Deploy Runbook
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
# On-demand Knowledge
|
|
16
|
+
|
|
17
|
+
- **code-style**: Our team coding conventions
|
|
18
|
+
`,
|
|
19
|
+
'$ xano knowledge list -w 40 --output json',
|
|
20
|
+
'$ xano knowledge list -w 40 --type skill',
|
|
21
|
+
'$ xano knowledge list --no-enabled-only',
|
|
22
|
+
];
|
|
23
|
+
static flags = {
|
|
24
|
+
...BaseCommand.baseFlags,
|
|
25
|
+
branch: Flags.string({
|
|
26
|
+
char: 'b',
|
|
27
|
+
description: 'Branch ID',
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
'enabled-only': Flags.boolean({
|
|
31
|
+
allowNo: true,
|
|
32
|
+
default: true,
|
|
33
|
+
description: 'Only show enabled knowledge (use --no-enabled-only to include disabled)',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
output: Flags.string({
|
|
37
|
+
char: 'o',
|
|
38
|
+
default: 'markdown',
|
|
39
|
+
description: 'Output format',
|
|
40
|
+
options: ['markdown', 'json'],
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
43
|
+
type: Flags.string({
|
|
44
|
+
char: 't',
|
|
45
|
+
description: 'Filter by knowledge type',
|
|
46
|
+
options: ['skill', 'doc', 'agents.md'],
|
|
47
|
+
required: false,
|
|
48
|
+
}),
|
|
49
|
+
workspace: Flags.string({
|
|
50
|
+
char: 'w',
|
|
51
|
+
description: 'Workspace ID (optional if set in profile)',
|
|
52
|
+
required: false,
|
|
53
|
+
}),
|
|
54
|
+
};
|
|
55
|
+
async run() {
|
|
56
|
+
const { flags } = await this.parse(KnowledgeList);
|
|
57
|
+
const { profile, profileName } = this.resolveProfile(flags);
|
|
58
|
+
let workspaceId;
|
|
59
|
+
if (flags.workspace) {
|
|
60
|
+
workspaceId = flags.workspace;
|
|
61
|
+
}
|
|
62
|
+
else if (profile.workspace) {
|
|
63
|
+
workspaceId = profile.workspace;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
this.error(`Workspace ID is required. Either:\n` +
|
|
67
|
+
` 1. Provide it as a flag: xano knowledge list -w <workspace_id>\n` +
|
|
68
|
+
` 2. Set it in your profile using: xano profile edit ${profileName} -w <workspace_id>`);
|
|
69
|
+
}
|
|
70
|
+
const queryParams = new URLSearchParams();
|
|
71
|
+
queryParams.set('enabled_only', flags['enabled-only'].toString());
|
|
72
|
+
queryParams.set('include_content', 'true');
|
|
73
|
+
if (flags.branch) {
|
|
74
|
+
queryParams.set('branch_id', flags.branch);
|
|
75
|
+
}
|
|
76
|
+
if (flags.type) {
|
|
77
|
+
queryParams.set('type', flags.type);
|
|
78
|
+
}
|
|
79
|
+
const apiUrl = `${profile.instance_origin}/api:meta/workspace/${workspaceId}/knowledge?${queryParams.toString()}`;
|
|
80
|
+
try {
|
|
81
|
+
const response = await this.verboseFetch(apiUrl, {
|
|
82
|
+
headers: {
|
|
83
|
+
'accept': 'application/json',
|
|
84
|
+
'Authorization': `Bearer ${profile.access_token}`,
|
|
85
|
+
},
|
|
86
|
+
method: 'GET',
|
|
87
|
+
}, flags.verbose, profile.access_token);
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
const errorText = await response.text();
|
|
90
|
+
this.error(`API request failed with status ${response.status}: ${response.statusText}\n${errorText}`);
|
|
91
|
+
}
|
|
92
|
+
const data = await response.json();
|
|
93
|
+
const items = Array.isArray(data) ? data : (data.items ?? []);
|
|
94
|
+
if (flags.output === 'json') {
|
|
95
|
+
this.log(JSON.stringify(items, null, 2));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this.log(this.formatMarkdown(items));
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof Error && 'oclif' in error)
|
|
102
|
+
throw error;
|
|
103
|
+
if (error instanceof Error) {
|
|
104
|
+
this.error(`Failed to fetch knowledge: ${error.message}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.error(`Failed to fetch knowledge: ${String(error)}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
formatMarkdown(items) {
|
|
112
|
+
if (items.length === 0) {
|
|
113
|
+
return 'No knowledge items found.';
|
|
114
|
+
}
|
|
115
|
+
const alwaysOn = items.filter((item) => item.mode === 'always');
|
|
116
|
+
const onDemand = items.filter((item) => item.mode !== 'always');
|
|
117
|
+
const lines = [];
|
|
118
|
+
if (alwaysOn.length > 0) {
|
|
119
|
+
lines.push('# Always-on Knowledge');
|
|
120
|
+
for (const item of alwaysOn) {
|
|
121
|
+
lines.push('', `## ${item.name ?? '(unnamed)'}`, '', item.content ?? '', '', '---');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (onDemand.length > 0) {
|
|
125
|
+
if (lines.length > 0)
|
|
126
|
+
lines.push('');
|
|
127
|
+
lines.push('# On-demand Knowledge', '');
|
|
128
|
+
for (const item of onDemand) {
|
|
129
|
+
const desc = item.description || '(no description)';
|
|
130
|
+
lines.push(`- **${item.name ?? '(unnamed)'}**: ${desc}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return lines.join('\n');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -79,6 +79,23 @@ export function parseFrontmatter(content) {
|
|
|
79
79
|
result.name = meta.name;
|
|
80
80
|
return result;
|
|
81
81
|
}
|
|
82
|
+
// ── Mode normalization ───────────────────────────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* The API only accepts the backend `mode` enum (`auto` | `referenced` | `always`),
|
|
85
|
+
* but the Xano UI displays these as "On Demand" / "Manual" / "Always Included".
|
|
86
|
+
* Hand-authored frontmatter (or content copied from the UI) commonly uses the
|
|
87
|
+
* display label instead, which the API rejects with e.g. `Input "on demand" is
|
|
88
|
+
* not one of the allowable values.` (DEV-7380/DEV-7382). Normalize known aliases
|
|
89
|
+
* before every push so the backend enum value is always what's sent.
|
|
90
|
+
*/
|
|
91
|
+
const MODE_ALIASES = {
|
|
92
|
+
'always included': 'always',
|
|
93
|
+
'manual': 'referenced',
|
|
94
|
+
'on demand': 'auto',
|
|
95
|
+
};
|
|
96
|
+
function normalizeMode(mode) {
|
|
97
|
+
return MODE_ALIASES[mode.trim().toLowerCase()] ?? mode;
|
|
98
|
+
}
|
|
82
99
|
// ── Path mapping (CLI-owned layout) ────────────────────────────────────────────
|
|
83
100
|
/**
|
|
84
101
|
* Allocate a unique snake_case folder/file segment per call, suffixing
|
|
@@ -211,7 +228,7 @@ export function collectKnowledgeObjects(inputDir, include, exclude) {
|
|
|
211
228
|
enabled: typeof meta.enabled === 'boolean' ? meta.enabled : true,
|
|
212
229
|
filePath: absPath,
|
|
213
230
|
knowledge_type: knowledgeType,
|
|
214
|
-
mode: typeof meta.mode === 'string' ? meta.mode : 'auto',
|
|
231
|
+
mode: typeof meta.mode === 'string' ? normalizeMode(meta.mode) : 'auto',
|
|
215
232
|
name: typeof meta.name === 'string' ? meta.name : deriveName(posixPath),
|
|
216
233
|
scope: typeof meta.scope === 'string' ? meta.scope : 'workspace',
|
|
217
234
|
};
|