@naturali/cli 0.1.0 → 0.27.0
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 +19 -21
- package/dist/index.mjs +24 -105
- package/package.json +6 -7
package/README.md
CHANGED
|
@@ -10,16 +10,15 @@ naturali configure
|
|
|
10
10
|
|
|
11
11
|
## Configuration
|
|
12
12
|
|
|
13
|
-
`naturali configure` saves a profile to `~/.naturali/config.json`:
|
|
14
|
-
|
|
13
|
+
`naturali configure` saves a profile to `~/.naturali/config.json`: a token
|
|
14
|
+
(`nat_sk_…` or a session JWT). The CLI always talks to `https://api.naturali.ai`
|
|
15
|
+
— the only naturali.ai API origin, and not configurable.
|
|
15
16
|
|
|
16
17
|
Environment variables take precedence, which is what CI should use:
|
|
17
18
|
|
|
18
19
|
| Variable | Purpose |
|
|
19
20
|
| --- | --- |
|
|
20
|
-
| `
|
|
21
|
-
| `NATURALI_TOKEN` | Bearer credential. With `NATURALI_BASE_URL` set, no profile is needed. |
|
|
22
|
-
| `NATURALI_PROJECT` | Default `project_id`, so project-scoped commands can omit `--project-id`. |
|
|
21
|
+
| `NATURALI_TOKEN` | Bearer credential. When set, no profile is needed. |
|
|
23
22
|
| `NATURALI_PROFILE` | Profile to use when `--profile` is not passed. Defaults to `default`. |
|
|
24
23
|
|
|
25
24
|
Use `--profile <name>` to switch profiles per command.
|
|
@@ -62,28 +61,15 @@ the spec, so you never have to know.
|
|
|
62
61
|
|
|
63
62
|
An array-typed field is always sent as a list, even with one value.
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
With a default project configured, `--project-id` can be omitted:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
export NATURALI_PROJECT=proj_V1StGXR8Z5jdHi6B
|
|
71
|
-
|
|
72
|
-
naturali list-agents
|
|
73
|
-
naturali get-agent agent_V1StGXR8Z5jdHi6B # positional fills the last path param
|
|
74
|
-
naturali get-agent --id agent_V1StGXR8Z5jdHi6B
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
A positional argument or `--id` fills the **one** path parameter still missing;
|
|
78
|
-
when more than one is unresolved the command fails and names them rather than
|
|
79
|
-
guessing.
|
|
64
|
+
Every path parameter a command needs must be passed explicitly; a command that
|
|
65
|
+
leaves one out fails and names it rather than guessing.
|
|
80
66
|
|
|
81
67
|
## Output
|
|
82
68
|
|
|
83
69
|
The response body is printed to stdout as formatted JSON, so it pipes into `jq`:
|
|
84
70
|
|
|
85
71
|
```bash
|
|
86
|
-
naturali list-agents | jq -r '.data[].id'
|
|
72
|
+
naturali list-agents --project-id proj_V1StGXR8Z5jdHi6B | jq -r '.data[].id'
|
|
87
73
|
```
|
|
88
74
|
|
|
89
75
|
A non-2xx response prints the platform's error envelope to **stderr** and exits
|
|
@@ -105,3 +91,15 @@ pipeline.
|
|
|
105
91
|
|
|
106
92
|
Hand-written source is limited to dispatch, flag parsing, credentials and help;
|
|
107
93
|
there is no per-endpoint code.
|
|
94
|
+
|
|
95
|
+
## Versioning
|
|
96
|
+
|
|
97
|
+
Releases are automatic, and the version tracks the API rather than a hand-picked
|
|
98
|
+
schedule. Any change to `api/openapi/v1/*.yaml` regenerates this package and
|
|
99
|
+
publishes it — new endpoints become new commands without anyone editing the CLI
|
|
100
|
+
— and so does a change to `@naturali/sdk`, which is bundled in at build time
|
|
101
|
+
rather than resolved from npm.
|
|
102
|
+
|
|
103
|
+
`fix:`/`chore:` changes release a patch, `feat:` a minor, and a breaking change
|
|
104
|
+
is held for human approval before it reaches npm. See the repository
|
|
105
|
+
[README](../../README.md#releasing).
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
import input from "@inquirer/input";
|
|
4
3
|
import password from "@inquirer/password";
|
|
5
4
|
import { Command } from "commander";
|
|
6
5
|
import * as fs from "node:fs";
|
|
@@ -18,7 +17,7 @@ var __exportAll = (all, no_symbols) => {
|
|
|
18
17
|
};
|
|
19
18
|
//#endregion
|
|
20
19
|
//#region package.json
|
|
21
|
-
var version = "0.
|
|
20
|
+
var version = "0.27.0";
|
|
22
21
|
//#endregion
|
|
23
22
|
//#region ../sdk/src/generated/core/bodySerializer.gen.ts
|
|
24
23
|
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
@@ -1735,6 +1734,8 @@ const bindResource = (SdkClass, client) => {
|
|
|
1735
1734
|
return value;
|
|
1736
1735
|
} });
|
|
1737
1736
|
};
|
|
1737
|
+
/** The naturali.ai API origin. Not configurable — there is only one. */
|
|
1738
|
+
const API_BASE_URL$1 = "https://api.naturali.ai";
|
|
1738
1739
|
/**
|
|
1739
1740
|
* The naturali.ai API client.
|
|
1740
1741
|
*
|
|
@@ -1745,7 +1746,6 @@ const bindResource = (SdkClass, client) => {
|
|
|
1745
1746
|
* import { NaturaliClient } from '@naturali/sdk';
|
|
1746
1747
|
*
|
|
1747
1748
|
* const naturali = new NaturaliClient({
|
|
1748
|
-
* baseUrl: 'https://api.naturali.ai',
|
|
1749
1749
|
* token: process.env.NATURALI_TOKEN,
|
|
1750
1750
|
* });
|
|
1751
1751
|
*
|
|
@@ -1775,9 +1775,9 @@ var NaturaliClient = class {
|
|
|
1775
1775
|
traces;
|
|
1776
1776
|
/** The underlying HTTP client, for interceptors or one-off requests. */
|
|
1777
1777
|
http;
|
|
1778
|
-
constructor({
|
|
1778
|
+
constructor({ token, headers } = {}) {
|
|
1779
1779
|
this.http = createClient(createConfig({
|
|
1780
|
-
baseUrl:
|
|
1780
|
+
baseUrl: API_BASE_URL$1,
|
|
1781
1781
|
headers: {
|
|
1782
1782
|
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
1783
1783
|
...headers
|
|
@@ -1820,6 +1820,8 @@ var src_exports = /* @__PURE__ */ __exportAll({
|
|
|
1820
1820
|
});
|
|
1821
1821
|
//#endregion
|
|
1822
1822
|
//#region src/config.ts
|
|
1823
|
+
/** The naturali.ai API origin. Not configurable — there is only one. */
|
|
1824
|
+
const API_BASE_URL = "https://api.naturali.ai";
|
|
1823
1825
|
const CONFIG_FILE = path.join(os.homedir(), ".naturali", "config.json");
|
|
1824
1826
|
const readConfig = () => {
|
|
1825
1827
|
try {
|
|
@@ -1838,57 +1840,28 @@ const writeProfile = (name, profile) => {
|
|
|
1838
1840
|
const configFilePath = () => {
|
|
1839
1841
|
return CONFIG_FILE;
|
|
1840
1842
|
};
|
|
1841
|
-
/**
|
|
1842
|
-
* The resolved project is deliberately *not* sent as a header. It exists to fill
|
|
1843
|
-
* the `project_id` **path parameter** a command left out; the API authorizes that
|
|
1844
|
-
* path parameter and nothing else.
|
|
1845
|
-
*/
|
|
1846
1843
|
const buildClient = (args) => {
|
|
1847
|
-
const {
|
|
1844
|
+
const { token } = args;
|
|
1848
1845
|
return createClient(createConfig({
|
|
1849
|
-
baseUrl,
|
|
1846
|
+
baseUrl: API_BASE_URL,
|
|
1850
1847
|
headers: { ...token ? { Authorization: `Bearer ${token}` } : {} }
|
|
1851
1848
|
}));
|
|
1852
1849
|
};
|
|
1853
1850
|
/**
|
|
1854
1851
|
* Resolves credentials in precedence order:
|
|
1855
1852
|
*
|
|
1856
|
-
* 1. `
|
|
1857
|
-
* 2. A named profile (`--profile` → `NATURALI_PROFILE` → `default`)
|
|
1858
|
-
* `NATURALI_BASE_URL` / `NATURALI_PROJECT` overriding its stored values.
|
|
1859
|
-
* 3. `NATURALI_BASE_URL` alone — an unauthenticated client, enough for the
|
|
1860
|
-
* public endpoints (`/health`, `/v1/openapi.json`, login, signup).
|
|
1853
|
+
* 1. `NATURALI_TOKEN` — no profile needed.
|
|
1854
|
+
* 2. A named profile (`--profile` → `NATURALI_PROFILE` → `default`).
|
|
1861
1855
|
*
|
|
1862
|
-
* Exits with an actionable message when
|
|
1856
|
+
* Exits with an actionable message when neither applies.
|
|
1863
1857
|
*/
|
|
1864
1858
|
const resolveContext = (profileName) => {
|
|
1865
|
-
const envBaseUrl = process.env["NATURALI_BASE_URL"];
|
|
1866
1859
|
const envToken = process.env["NATURALI_TOKEN"];
|
|
1867
|
-
|
|
1868
|
-
if (envBaseUrl && envToken) return {
|
|
1869
|
-
client: buildClient({
|
|
1870
|
-
baseUrl: envBaseUrl,
|
|
1871
|
-
token: envToken
|
|
1872
|
-
}),
|
|
1873
|
-
project: envProject
|
|
1874
|
-
};
|
|
1860
|
+
if (envToken) return { client: buildClient({ token: envToken }) };
|
|
1875
1861
|
const name = profileName ?? process.env["NATURALI_PROFILE"] ?? "default";
|
|
1876
1862
|
const profile = readConfig()[name];
|
|
1877
|
-
if (profile) {
|
|
1878
|
-
|
|
1879
|
-
return {
|
|
1880
|
-
client: buildClient({
|
|
1881
|
-
baseUrl: envBaseUrl ?? profile.baseUrl,
|
|
1882
|
-
token: profile.token
|
|
1883
|
-
}),
|
|
1884
|
-
project
|
|
1885
|
-
};
|
|
1886
|
-
}
|
|
1887
|
-
if (envBaseUrl) return {
|
|
1888
|
-
client: buildClient({ baseUrl: envBaseUrl }),
|
|
1889
|
-
project: envProject
|
|
1890
|
-
};
|
|
1891
|
-
console.error(`Profile "${name}" not found. Run: naturali configure${name !== "default" ? ` --profile ${name}` : ""}\nOr set NATURALI_BASE_URL and NATURALI_TOKEN in the environment.`);
|
|
1863
|
+
if (profile) return { client: buildClient({ token: profile.token }) };
|
|
1864
|
+
console.error(`Profile "${name}" not found. Run: naturali configure${name !== "default" ? ` --profile ${name}` : ""}\nOr set NATURALI_TOKEN in the environment.`);
|
|
1892
1865
|
process.exit(1);
|
|
1893
1866
|
};
|
|
1894
1867
|
//#endregion
|
|
@@ -1922,28 +1895,6 @@ const parseUnknownWithRepeats = (args) => {
|
|
|
1922
1895
|
};
|
|
1923
1896
|
};
|
|
1924
1897
|
/**
|
|
1925
|
-
* Extracts bare (non-`--flag`) tokens, e.g. the `agent_123` in
|
|
1926
|
-
* `naturali get-agent agent_123`. Walks the same flag/value pairing as
|
|
1927
|
-
* {@link parseUnknownWithRepeats} so a flag's value is never mistaken for a
|
|
1928
|
-
* standalone positional argument.
|
|
1929
|
-
*/
|
|
1930
|
-
const extractPositionalArgs = (args) => {
|
|
1931
|
-
const { cliArgs } = args;
|
|
1932
|
-
const positional = [];
|
|
1933
|
-
for (let i = 0; i < cliArgs.length; i++) {
|
|
1934
|
-
const arg = cliArgs[i];
|
|
1935
|
-
if (arg === void 0) continue;
|
|
1936
|
-
if (!arg.startsWith("--")) {
|
|
1937
|
-
positional.push(arg);
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
if (arg.indexOf("=") > 2) continue;
|
|
1941
|
-
const next = cliArgs[i + 1];
|
|
1942
|
-
if (next !== void 0 && !next.startsWith("--")) i++;
|
|
1943
|
-
}
|
|
1944
|
-
return positional;
|
|
1945
|
-
};
|
|
1946
|
-
/**
|
|
1947
1898
|
* Normalizes a flag name to camelCase so kebab, snake and camel spellings of
|
|
1948
1899
|
* the same parameter all match: `agent-id`, `agent_id` and `agentId` → `agentId`.
|
|
1949
1900
|
*/
|
|
@@ -2006,9 +1957,6 @@ const oneLine = (text) => {
|
|
|
2006
1957
|
const GLOBAL_FLAGS = [{
|
|
2007
1958
|
name: "profile",
|
|
2008
1959
|
description: "config profile to use"
|
|
2009
|
-
}, {
|
|
2010
|
-
name: "id",
|
|
2011
|
-
description: "alias for the command's remaining path parameter"
|
|
2012
1960
|
}];
|
|
2013
1961
|
/**
|
|
2014
1962
|
* Renders `naturali <command> --help`: the operation's own description, a link
|
|
@@ -2058,12 +2006,8 @@ const renderCommandHelp = (args) => {
|
|
|
2058
2006
|
};
|
|
2059
2007
|
//#endregion
|
|
2060
2008
|
//#region src/routeCall.ts
|
|
2061
|
-
/**
|
|
2062
|
-
|
|
2063
|
-
* credentials, `--id` is the generic alias for a command's remaining path
|
|
2064
|
-
* parameter.
|
|
2065
|
-
*/
|
|
2066
|
-
const RESERVED_FLAGS = /* @__PURE__ */ new Set(["profile", "id"]);
|
|
2009
|
+
/** Flags the dispatcher owns, never forwarded to the API. */
|
|
2010
|
+
const RESERVED_FLAGS = /* @__PURE__ */ new Set(["profile"]);
|
|
2067
2011
|
const placeFlags = (args) => {
|
|
2068
2012
|
const { route, parsedFlags, call } = args;
|
|
2069
2013
|
const typeByCanonical = new Map(route.flags.map((flag) => {
|
|
@@ -2097,21 +2041,11 @@ const placeFlags = (args) => {
|
|
|
2097
2041
|
/**
|
|
2098
2042
|
* Turns parsed flags into the `{ path, query, body }` an SDK method expects.
|
|
2099
2043
|
*
|
|
2100
|
-
* Two conveniences fill path parameters the caller left out, in this order —
|
|
2101
|
-
* an explicit flag always wins over both:
|
|
2102
|
-
*
|
|
2103
|
-
* 1. `project_id` comes from the resolved default project (`NATURALI_PROJECT`
|
|
2104
|
-
* or the profile's `project`), since almost every resource is nested under
|
|
2105
|
-
* a project.
|
|
2106
|
-
* 2. `--id <value>`, or a bare positional argument, fills the one path
|
|
2107
|
-
* parameter still missing — so `naturali get-agent agent_123` works once a
|
|
2108
|
-
* default project is configured.
|
|
2109
|
-
*
|
|
2110
2044
|
* A path parameter that stays unresolved is reported rather than sent: a
|
|
2111
2045
|
* request with a literal `{agent_id}` left in the URL is never useful.
|
|
2112
2046
|
*/
|
|
2113
2047
|
const buildRouteCall = (args) => {
|
|
2114
|
-
const { route, parsedFlags
|
|
2048
|
+
const { route, parsedFlags } = args;
|
|
2115
2049
|
const call = {
|
|
2116
2050
|
path: {},
|
|
2117
2051
|
query: {},
|
|
@@ -2122,12 +2056,6 @@ const buildRouteCall = (args) => {
|
|
|
2122
2056
|
parsedFlags,
|
|
2123
2057
|
call
|
|
2124
2058
|
});
|
|
2125
|
-
if (defaultProject && route.pathParams.includes("project_id") && !("project_id" in call.path)) call.path["project_id"] = defaultProject;
|
|
2126
|
-
const unresolved = route.pathParams.filter((param) => {
|
|
2127
|
-
return !(param in call.path);
|
|
2128
|
-
});
|
|
2129
|
-
const idAlias = parsedFlags.single["id"] ?? positionalArgs[0];
|
|
2130
|
-
if (idAlias !== void 0 && unresolved.length === 1) call.path[unresolved[0]] = parseFlagValue(idAlias);
|
|
2131
2059
|
const missingPathParams = route.pathParams.filter((param) => {
|
|
2132
2060
|
return !(param in call.path);
|
|
2133
2061
|
});
|
|
@@ -2217,13 +2145,13 @@ const dispatchCommand = async (args) => {
|
|
|
2217
2145
|
const context = resolveContext(parsedFlags.single["profile"] ?? profileFromProgram);
|
|
2218
2146
|
const built = buildRouteCall({
|
|
2219
2147
|
route,
|
|
2220
|
-
parsedFlags
|
|
2221
|
-
positionalArgs: extractPositionalArgs({ cliArgs: rawArgs }),
|
|
2222
|
-
defaultProject: context.project
|
|
2148
|
+
parsedFlags
|
|
2223
2149
|
});
|
|
2224
2150
|
if (!built.ok) {
|
|
2225
|
-
const
|
|
2226
|
-
|
|
2151
|
+
const flagsHint = built.missingPathParams.map((param) => {
|
|
2152
|
+
return `--${param.replace(/_/g, "-")} <value>`;
|
|
2153
|
+
}).join(", ");
|
|
2154
|
+
console.error(`Missing required path parameter(s): ${built.missingPathParams.join(", ")}.\nProvide with ${flagsHint}.`);
|
|
2227
2155
|
process.exit(1);
|
|
2228
2156
|
}
|
|
2229
2157
|
const serviceClass = resolveServiceClass(route.serviceClass);
|
|
@@ -5243,17 +5171,8 @@ const routes = {
|
|
|
5243
5171
|
*/
|
|
5244
5172
|
const addConfigureCommand = (program) => {
|
|
5245
5173
|
program.command("configure").description(`Save credentials to a named profile (${configFilePath()})`).option("-p, --profile <name>", "profile name", "default").action(async (opts) => {
|
|
5246
|
-
const baseUrl = await input({
|
|
5247
|
-
message: "Base URL:",
|
|
5248
|
-
default: "https://api.naturali.ai"
|
|
5249
|
-
});
|
|
5250
5174
|
const token = await password({ message: "Token (nat_sk_… or a session JWT, hidden):" });
|
|
5251
|
-
|
|
5252
|
-
writeProfile(opts.profile, {
|
|
5253
|
-
baseUrl,
|
|
5254
|
-
token,
|
|
5255
|
-
...project ? { project } : {}
|
|
5256
|
-
});
|
|
5175
|
+
writeProfile(opts.profile, { token });
|
|
5257
5176
|
console.log(`Profile "${opts.profile}" saved to ${configFilePath()}.`);
|
|
5258
5177
|
});
|
|
5259
5178
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturali/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"description": "Command-line interface for the naturali.ai API, generated from its OpenAPI specs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@inquirer/input": "^5.1.2",
|
|
25
24
|
"@inquirer/password": "^5.1.1",
|
|
26
25
|
"commander": "^15.0.0"
|
|
27
26
|
},
|
|
@@ -32,12 +31,12 @@
|
|
|
32
31
|
"tsx": "^4.23.1",
|
|
33
32
|
"typescript": "~6.0.3",
|
|
34
33
|
"vitest": "^4.1.10",
|
|
35
|
-
"@naturali/sdk": "0.
|
|
34
|
+
"@naturali/sdk": "0.27.0"
|
|
36
35
|
},
|
|
37
36
|
"scripts": {
|
|
38
|
-
"generate": "
|
|
39
|
-
"typecheck": "
|
|
40
|
-
"test": "
|
|
41
|
-
"build": "
|
|
37
|
+
"generate": "tsx scripts/generate.ts",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"build": "tsdown"
|
|
42
41
|
}
|
|
43
42
|
}
|