zinkee 0.1.1 → 0.1.4
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/AGENTS.md +4 -0
- package/README.md +25 -0
- package/dist/index.js +88 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/automations.ts +2 -2
- package/src/commands/comments.ts +2 -2
- package/src/commands/displays.ts +2 -2
- package/src/commands/files.ts +2 -2
- package/src/commands/navigation.ts +2 -2
- package/src/commands/records.ts +2 -2
- package/src/commands/schemas.ts +2 -2
- package/src/commands/teamspace.ts +2 -2
- package/src/config.test.ts +175 -0
- package/src/config.ts +50 -0
- package/src/runtime-context.ts +83 -4
- package/src/types.ts +5 -0
- package/src/utils/examples.ts +2 -2
package/AGENTS.md
CHANGED
|
@@ -13,6 +13,10 @@ Use this order when reasoning about behavior:
|
|
|
13
13
|
## Key Runtime Rules
|
|
14
14
|
|
|
15
15
|
- Existing config compatibility is mandatory: `~/.config/zinkee/config.toml`
|
|
16
|
+
- Runtime env compatibility is mandatory for agentic/backend use:
|
|
17
|
+
- `ZINKEE_PROFILE`
|
|
18
|
+
- `ZINKEE_API_BASE_URL`
|
|
19
|
+
- `ZINKEE_API_KEY`
|
|
16
20
|
- `--read-only` must block write/destructive commands before network calls
|
|
17
21
|
- `--example <command path>` should return concrete usage examples without running the command
|
|
18
22
|
- `--json` must keep stdout machine-readable
|
package/README.md
CHANGED
|
@@ -64,6 +64,31 @@ base_url = "https://api.zinkee.com"
|
|
|
64
64
|
| `--example` | Show examples for a command |
|
|
65
65
|
| `--json` | Emit JSON output |
|
|
66
66
|
|
|
67
|
+
## Runtime Resolution
|
|
68
|
+
|
|
69
|
+
The CLI resolves runtime settings with this precedence:
|
|
70
|
+
|
|
71
|
+
1. CLI flags: `--profile`, `--base-url`, `--api-key`
|
|
72
|
+
2. Environment variables: `ZINKEE_PROFILE`, `ZINKEE_API_BASE_URL`, `ZINKEE_API_KEY`
|
|
73
|
+
3. `~/.config/zinkee/config.toml`
|
|
74
|
+
|
|
75
|
+
If a runtime API key is provided, a runtime base URL must also be provided.
|
|
76
|
+
If a runtime base URL is provided, a runtime API key must also be provided.
|
|
77
|
+
|
|
78
|
+
Example for local development without config:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
ZINKEE_API_BASE_URL=http://localhost:8088 \
|
|
82
|
+
ZINKEE_API_KEY="<workspace_token>" \
|
|
83
|
+
zinkee --json schemas list
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Example using an env-selected profile:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
ZINKEE_PROFILE=local zinkee --json schemas list
|
|
90
|
+
```
|
|
91
|
+
|
|
67
92
|
## JSON Contract
|
|
68
93
|
|
|
69
94
|
Successful commands emit:
|
package/dist/index.js
CHANGED
|
@@ -1317,6 +1317,10 @@ import { dirname, join } from "path";
|
|
|
1317
1317
|
import { parseTOML, stringifyTOML } from "confbox/toml";
|
|
1318
1318
|
var defaultConfigPath = join(homedir(), ".config", "zinkee", "config.toml");
|
|
1319
1319
|
var redactedSecretMask = "********";
|
|
1320
|
+
var ENV_API_KEY = "ZINKEE_API_KEY";
|
|
1321
|
+
var ENV_BASE_URL = "ZINKEE_API_BASE_URL";
|
|
1322
|
+
var ENV_PROFILE = "ZINKEE_PROFILE";
|
|
1323
|
+
var DEFAULT_PRODUCTION_BASE_URL = "https://api-z2.zinkee.com";
|
|
1320
1324
|
var ProfileResolutionError = class extends Error {
|
|
1321
1325
|
name = "ProfileResolutionError";
|
|
1322
1326
|
constructor(message) {
|
|
@@ -1329,6 +1333,10 @@ function isRecord2(value) {
|
|
|
1329
1333
|
function toStringValue(value) {
|
|
1330
1334
|
return typeof value === "string" ? value : void 0;
|
|
1331
1335
|
}
|
|
1336
|
+
function toNonEmptyString(value) {
|
|
1337
|
+
const normalized = value?.trim();
|
|
1338
|
+
return normalized ? normalized : void 0;
|
|
1339
|
+
}
|
|
1332
1340
|
function parseProfileConfig(value) {
|
|
1333
1341
|
const profile = {};
|
|
1334
1342
|
if (!isRecord2(value)) {
|
|
@@ -1384,6 +1392,9 @@ function readConfigIfExists(filePath = defaultConfigPath) {
|
|
|
1384
1392
|
throw error;
|
|
1385
1393
|
}
|
|
1386
1394
|
}
|
|
1395
|
+
function readRuntimeConfig(filePath = defaultConfigPath) {
|
|
1396
|
+
return readConfigIfExists(filePath) ?? { profiles: {} };
|
|
1397
|
+
}
|
|
1387
1398
|
function serializeProfileConfig(profile) {
|
|
1388
1399
|
const serialized = {};
|
|
1389
1400
|
for (const [key, value] of Object.entries(profile)) {
|
|
@@ -1492,16 +1503,80 @@ function resolveProfile(config, overrides = {}) {
|
|
|
1492
1503
|
}
|
|
1493
1504
|
throw new ProfileResolutionError("No profile could be resolved from config.");
|
|
1494
1505
|
}
|
|
1506
|
+
function validateBaseUrl(value) {
|
|
1507
|
+
let parsed;
|
|
1508
|
+
try {
|
|
1509
|
+
parsed = new URL(value);
|
|
1510
|
+
} catch {
|
|
1511
|
+
throw new ProfileResolutionError(
|
|
1512
|
+
`Environment variable ${ENV_BASE_URL} must be an absolute http(s) URL.`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
if (!["http:", "https:"].includes(parsed.protocol) || !parsed.host) {
|
|
1516
|
+
throw new ProfileResolutionError(
|
|
1517
|
+
`Environment variable ${ENV_BASE_URL} must be an absolute http(s) URL.`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
return value.replace(/\/+$/, "");
|
|
1521
|
+
}
|
|
1522
|
+
function readEnvApiKey(env = process.env) {
|
|
1523
|
+
return toNonEmptyString(env[ENV_API_KEY]);
|
|
1524
|
+
}
|
|
1525
|
+
function readEnvBaseUrl(env = process.env) {
|
|
1526
|
+
const baseUrl = toNonEmptyString(env[ENV_BASE_URL]);
|
|
1527
|
+
if (!baseUrl) {
|
|
1528
|
+
return void 0;
|
|
1529
|
+
}
|
|
1530
|
+
return validateBaseUrl(baseUrl);
|
|
1531
|
+
}
|
|
1532
|
+
function readEnvProfile(env = process.env) {
|
|
1533
|
+
return toNonEmptyString(env[ENV_PROFILE]);
|
|
1534
|
+
}
|
|
1495
1535
|
|
|
1496
1536
|
// src/runtime-context.ts
|
|
1497
1537
|
function resolveRuntimeContext(config, overrides = {}) {
|
|
1498
|
-
const
|
|
1538
|
+
const envProfile = readEnvProfile();
|
|
1539
|
+
const envApiKey = readEnvApiKey();
|
|
1540
|
+
const envBaseUrl = readEnvBaseUrl();
|
|
1541
|
+
const profileOverride = overrides.profile ?? envProfile;
|
|
1542
|
+
const apiKeyOverride = overrides.apiKey ?? envApiKey;
|
|
1543
|
+
const baseUrlOverride = overrides.baseUrl ?? envBaseUrl;
|
|
1544
|
+
if (apiKeyOverride && !baseUrlOverride) {
|
|
1545
|
+
throw new ProfileResolutionError(
|
|
1546
|
+
`Runtime API key was provided, but ${ENV_BASE_URL} or --base-url is missing.`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
if (baseUrlOverride && !apiKeyOverride) {
|
|
1550
|
+
throw new ProfileResolutionError(
|
|
1551
|
+
`Runtime base URL was provided, but ${ENV_API_KEY} or --api-key is missing.`
|
|
1552
|
+
);
|
|
1553
|
+
}
|
|
1554
|
+
if (apiKeyOverride && baseUrlOverride) {
|
|
1555
|
+
return {
|
|
1556
|
+
profile: profileOverride ?? "runtime",
|
|
1557
|
+
baseUrl: baseUrlOverride,
|
|
1558
|
+
apiKey: apiKeyOverride,
|
|
1559
|
+
readOnly: overrides.readOnly ?? false,
|
|
1560
|
+
json: overrides.json ?? false,
|
|
1561
|
+
profileSource: overrides.profile !== void 0 ? "flag" : envProfile !== void 0 ? "env" : void 0,
|
|
1562
|
+
baseUrlSource: overrides.baseUrl !== void 0 ? "flag" : envBaseUrl !== void 0 ? "env" : void 0,
|
|
1563
|
+
apiKeySource: overrides.apiKey !== void 0 ? "flag" : envApiKey !== void 0 ? "env" : void 0
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
const profile = resolveProfile(config, {
|
|
1567
|
+
profile: profileOverride,
|
|
1568
|
+
baseUrl: baseUrlOverride,
|
|
1569
|
+
apiKey: apiKeyOverride
|
|
1570
|
+
});
|
|
1499
1571
|
return {
|
|
1500
1572
|
profile: profile.name,
|
|
1501
|
-
baseUrl:
|
|
1502
|
-
apiKey:
|
|
1573
|
+
baseUrl: baseUrlOverride ?? profile.baseUrl ?? DEFAULT_PRODUCTION_BASE_URL,
|
|
1574
|
+
apiKey: apiKeyOverride ?? profile.apiKey,
|
|
1503
1575
|
readOnly: overrides.readOnly ?? false,
|
|
1504
|
-
json: overrides.json ?? false
|
|
1576
|
+
json: overrides.json ?? false,
|
|
1577
|
+
profileSource: overrides.profile !== void 0 ? "flag" : envProfile !== void 0 ? "env" : "config",
|
|
1578
|
+
baseUrlSource: overrides.baseUrl !== void 0 ? "flag" : envBaseUrl !== void 0 ? "env" : "config",
|
|
1579
|
+
apiKeySource: overrides.apiKey !== void 0 ? "flag" : envApiKey !== void 0 ? "env" : "config"
|
|
1505
1580
|
};
|
|
1506
1581
|
}
|
|
1507
1582
|
|
|
@@ -2288,7 +2363,7 @@ async function createExampleAutomationContext(options = {}) {
|
|
|
2288
2363
|
return null;
|
|
2289
2364
|
}
|
|
2290
2365
|
try {
|
|
2291
|
-
const config =
|
|
2366
|
+
const config = readRuntimeConfig(options.configPath ?? defaultConfigPath);
|
|
2292
2367
|
const runtime = resolveRuntimeContext(config, options.overrides);
|
|
2293
2368
|
if (!runtime.baseUrl) {
|
|
2294
2369
|
return null;
|
|
@@ -2907,7 +2982,7 @@ function toConfigurationError2(filePath, error) {
|
|
|
2907
2982
|
}
|
|
2908
2983
|
function createAutomationsApi(command, filePath) {
|
|
2909
2984
|
try {
|
|
2910
|
-
const config =
|
|
2985
|
+
const config = readRuntimeConfig(filePath);
|
|
2911
2986
|
const runtime = resolveRuntimeContext(config, getGlobalOptions2(command));
|
|
2912
2987
|
if (!runtime.baseUrl) {
|
|
2913
2988
|
throw new CliError(
|
|
@@ -4144,7 +4219,7 @@ function toConfigurationError3(filePath, error) {
|
|
|
4144
4219
|
}
|
|
4145
4220
|
function createCommentsApi(command, filePath) {
|
|
4146
4221
|
try {
|
|
4147
|
-
const config =
|
|
4222
|
+
const config = readRuntimeConfig(filePath);
|
|
4148
4223
|
const runtime = resolveRuntimeContext(config, getGlobalOptions3(command));
|
|
4149
4224
|
if (!runtime.baseUrl) {
|
|
4150
4225
|
throw new CliError(
|
|
@@ -4497,7 +4572,7 @@ function toConfigurationError4(filePath, error) {
|
|
|
4497
4572
|
}
|
|
4498
4573
|
function createDisplaysApi(command, filePath) {
|
|
4499
4574
|
try {
|
|
4500
|
-
const config =
|
|
4575
|
+
const config = readRuntimeConfig(filePath);
|
|
4501
4576
|
const runtime = resolveRuntimeContext(config, getGlobalOptions4(command));
|
|
4502
4577
|
if (!runtime.baseUrl) {
|
|
4503
4578
|
throw new CliError("configuration_error", `Resolved profile "${runtime.profile}" is missing a base URL.`);
|
|
@@ -5092,7 +5167,7 @@ function toConfigurationError5(filePath, error) {
|
|
|
5092
5167
|
}
|
|
5093
5168
|
function createFilesApi(command, filePath) {
|
|
5094
5169
|
try {
|
|
5095
|
-
const config =
|
|
5170
|
+
const config = readRuntimeConfig(filePath);
|
|
5096
5171
|
const runtime = resolveRuntimeContext(config, getGlobalOptions5(command));
|
|
5097
5172
|
if (!runtime.baseUrl) {
|
|
5098
5173
|
throw new CliError(
|
|
@@ -5311,7 +5386,7 @@ function toConfigurationError6(filePath, error) {
|
|
|
5311
5386
|
}
|
|
5312
5387
|
function createNavigationApi(command, filePath) {
|
|
5313
5388
|
try {
|
|
5314
|
-
const config =
|
|
5389
|
+
const config = readRuntimeConfig(filePath);
|
|
5315
5390
|
const runtime = resolveRuntimeContext(config, getGlobalOptions6(command));
|
|
5316
5391
|
if (!runtime.baseUrl) {
|
|
5317
5392
|
throw new CliError("configuration_error", `Resolved profile "${runtime.profile}" is missing a base URL.`);
|
|
@@ -5861,7 +5936,7 @@ function toConfigurationError7(filePath, error) {
|
|
|
5861
5936
|
}
|
|
5862
5937
|
function createRecordsApi(command, filePath) {
|
|
5863
5938
|
try {
|
|
5864
|
-
const config =
|
|
5939
|
+
const config = readRuntimeConfig(filePath);
|
|
5865
5940
|
const runtime = resolveRuntimeContext(config, getGlobalOptions8(command));
|
|
5866
5941
|
if (!runtime.baseUrl) {
|
|
5867
5942
|
throw new CliError(
|
|
@@ -6283,7 +6358,7 @@ function toConfigurationError8(filePath, error) {
|
|
|
6283
6358
|
}
|
|
6284
6359
|
function createSchemasApi(command, filePath) {
|
|
6285
6360
|
try {
|
|
6286
|
-
const config =
|
|
6361
|
+
const config = readRuntimeConfig(filePath);
|
|
6287
6362
|
const runtime = resolveRuntimeContext(config, getGlobalOptions9(command));
|
|
6288
6363
|
if (!runtime.baseUrl) {
|
|
6289
6364
|
throw new CliError(
|
|
@@ -6777,7 +6852,7 @@ function toConfigurationError9(filePath, error) {
|
|
|
6777
6852
|
}
|
|
6778
6853
|
function createTeamspaceApi(command, filePath) {
|
|
6779
6854
|
try {
|
|
6780
|
-
const config =
|
|
6855
|
+
const config = readRuntimeConfig(filePath);
|
|
6781
6856
|
const runtime = resolveRuntimeContext(config, getGlobalOptions10(command));
|
|
6782
6857
|
if (!runtime.baseUrl) {
|
|
6783
6858
|
throw new CliError(
|