@heyditto/cli 2.5.1 → 2.6.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 +52 -20
- package/dist/commands.d.ts +34 -3
- package/dist/commands.js +130 -59
- package/dist/commands.js.map +1 -1
- package/dist/secret-stores/aws.d.ts +8 -0
- package/dist/secret-stores/aws.js +59 -0
- package/dist/secret-stores/aws.js.map +1 -0
- package/dist/secret-stores/azure.d.ts +7 -0
- package/dist/secret-stores/azure.js +63 -0
- package/dist/secret-stores/azure.js.map +1 -0
- package/dist/secret-stores/cloudflare.d.ts +7 -0
- package/dist/secret-stores/cloudflare.js +46 -0
- package/dist/secret-stores/cloudflare.js.map +1 -0
- package/dist/secret-stores/doppler.d.ts +7 -0
- package/dist/secret-stores/doppler.js +52 -0
- package/dist/secret-stores/doppler.js.map +1 -0
- package/dist/secret-stores/exec.d.ts +55 -0
- package/dist/secret-stores/exec.js +132 -0
- package/dist/secret-stores/exec.js.map +1 -0
- package/dist/secret-stores/fly.d.ts +7 -0
- package/dist/secret-stores/fly.js +54 -0
- package/dist/secret-stores/fly.js.map +1 -0
- package/dist/secret-stores/gcloud.d.ts +7 -0
- package/dist/secret-stores/gcloud.js +62 -0
- package/dist/secret-stores/gcloud.js.map +1 -0
- package/dist/secret-stores/github.d.ts +9 -0
- package/dist/secret-stores/github.js +74 -0
- package/dist/secret-stores/github.js.map +1 -0
- package/dist/secret-stores/gitlab.d.ts +7 -0
- package/dist/secret-stores/gitlab.js +82 -0
- package/dist/secret-stores/gitlab.js.map +1 -0
- package/dist/secret-stores/index.d.ts +42 -0
- package/dist/secret-stores/index.js +106 -0
- package/dist/secret-stores/index.js.map +1 -0
- package/dist/secret-stores/kubernetes.d.ts +8 -0
- package/dist/secret-stores/kubernetes.js +76 -0
- package/dist/secret-stores/kubernetes.js.map +1 -0
- package/dist/secret-stores/naming.d.ts +20 -0
- package/dist/secret-stores/naming.js +62 -0
- package/dist/secret-stores/naming.js.map +1 -0
- package/dist/secret-stores/onepassword.d.ts +7 -0
- package/dist/secret-stores/onepassword.js +50 -0
- package/dist/secret-stores/onepassword.js.map +1 -0
- package/dist/secret-stores/types.d.ts +108 -0
- package/dist/secret-stores/types.js +25 -0
- package/dist/secret-stores/types.js.map +1 -0
- package/dist/secret-stores/vault.d.ts +7 -0
- package/dist/secret-stores/vault.js +47 -0
- package/dist/secret-stores/vault.js.map +1 -0
- package/dist/secret-stores/vercel.d.ts +9 -0
- package/dist/secret-stores/vercel.js +49 -0
- package/dist/secret-stores/vercel.js.map +1 -0
- package/dist/teleport/harness.d.ts +5 -0
- package/dist/teleport/harness.js +21 -5
- package/dist/teleport/harness.js.map +1 -1
- package/dist/teleport/types.d.ts +7 -0
- package/dist/teleport/types.js +12 -1
- package/dist/teleport/types.js.map +1 -1
- package/dist/teleport/worktree.js +2 -0
- package/dist/teleport/worktree.js.map +1 -1
- package/package.json +1 -1
- package/dist/gh-secret.d.ts +0 -31
- package/dist/gh-secret.js +0 -94
- package/dist/gh-secret.js.map +0 -1
package/README.md
CHANGED
|
@@ -406,9 +406,10 @@ heyditto endpoints set <endpoint> --model … --system-prompt … --spend-limit
|
|
|
406
406
|
--record on|off --memory-depth <n>
|
|
407
407
|
heyditto endpoints delete <endpoint> [--yes]
|
|
408
408
|
heyditto endpoints keys <endpoint> list keys
|
|
409
|
-
heyditto endpoints keys create <endpoint> --
|
|
409
|
+
heyditto endpoints keys create <endpoint> --store <destination> --secret <NAME> [scoping flags]
|
|
410
410
|
[--name <label>] [--expires <1h…never>] [--budget <tokens>]
|
|
411
411
|
[--spend-period <p>] [--yes]
|
|
412
|
+
heyditto endpoints keys stores which destinations this machine can write to
|
|
412
413
|
heyditto endpoints keys revoke <endpoint> <keyId> [--yes]
|
|
413
414
|
```
|
|
414
415
|
|
|
@@ -416,30 +417,57 @@ Endpoints spend your Ditto credits, so deleting one, revoking a key or raising a
|
|
|
416
417
|
spend limit asks you to type the slug back; pass `--yes` in scripts. `--output
|
|
417
418
|
json` is available everywhere and includes the gateway base URL.
|
|
418
419
|
|
|
419
|
-
#### Put a key
|
|
420
|
-
|
|
421
|
-
`keys create
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
does not appear in argv, `ps`, shell
|
|
425
|
-
|
|
426
|
-
is revoked again.
|
|
420
|
+
#### Put a key straight into a secret manager
|
|
421
|
+
|
|
422
|
+
`keys create` mints a long-lived key on an endpoint and stores it in a secret
|
|
423
|
+
manager by delegating to that platform's own CLI — **the key is never shown**.
|
|
424
|
+
The plaintext goes to the platform CLI over stdin (or through `/dev/stdin`, for
|
|
425
|
+
the CLIs that read a file instead), so it does not appear in argv, `ps`, shell
|
|
426
|
+
history, logs or the CLI's own output, and nothing is kept locally. If the
|
|
427
|
+
platform CLI fails, the freshly minted key is revoked again.
|
|
428
|
+
|
|
429
|
+
| Destination | Shorthand | Scoping flags | CLI it delegates to |
|
|
430
|
+
| --- | --- | --- | --- |
|
|
431
|
+
| GitHub Actions | `--gh-secret NAME` | `--repo` `--env` `--org` | `gh secret set` |
|
|
432
|
+
| GitLab CI/CD | `--gitlab-var NAME` | `--project` `--org` `--env` | `glab variable set --masked` |
|
|
433
|
+
| AWS Secrets Manager | `--aws-secret NAME` | `--region` | `aws secretsmanager create-secret` (then `put-secret-value`) |
|
|
434
|
+
| Google Secret Manager | `--gcp-secret NAME` | `--project` | `gcloud secrets create` (then `versions add`) |
|
|
435
|
+
| Azure Key Vault | `--az-secret NAME` | `--key-vault` (required) | `az keyvault secret set --file` |
|
|
436
|
+
| 1Password | `--op-item TITLE` | `--op-vault` | `op item create` (JSON template on stdin) |
|
|
437
|
+
| HashiCorp Vault | `--vault-secret PATH` | `--mount` `--field` | `vault kv patch` (then `kv put`) |
|
|
438
|
+
| Doppler | `--doppler-secret NAME` | `--project` `--doppler-config` | `doppler secrets set` |
|
|
439
|
+
| Cloudflare Workers | `--cf-secret NAME` | `--worker` | `wrangler secret put` |
|
|
440
|
+
| Vercel | `--vercel-env NAME` | `--vercel-target` | `vercel env add --sensitive` |
|
|
441
|
+
| Kubernetes | `--k8s-secret NAME` | `--namespace` `--k8s-key` | `kubectl patch secret` (then `apply`) |
|
|
442
|
+
| Fly.io | `--fly-secret NAME` | `--app` | `fly secrets import` |
|
|
443
|
+
|
|
444
|
+
Every shorthand is sugar for `--store <id> --secret <NAME>`; use whichever
|
|
445
|
+
reads better. `heyditto endpoints keys stores` lists the destinations with the
|
|
446
|
+
CLI each one needs and whether it is installed here (`--output json` for
|
|
447
|
+
scripts).
|
|
427
448
|
|
|
428
449
|
```bash
|
|
429
450
|
cd my-repo
|
|
430
451
|
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY # repo from the current directory (like gh)
|
|
431
452
|
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --repo acme/app --budget 5000000
|
|
432
|
-
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --repo acme/app --env production --yes
|
|
433
453
|
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --org acme --expires 6mo --output json
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
454
|
+
heyditto endpoints keys create my-endpoint --store aws --secret DITTO_KEY --region us-east-1
|
|
455
|
+
heyditto endpoints keys create my-endpoint --gcp-secret ditto-key --project my-gcp-project
|
|
456
|
+
heyditto endpoints keys create my-endpoint --az-secret ditto-key --key-vault my-vault
|
|
457
|
+
heyditto endpoints keys create my-endpoint --op-item "Ditto inference" --op-vault Engineering
|
|
458
|
+
heyditto endpoints keys create my-endpoint --vault-secret ditto/inference --mount secret --field token
|
|
459
|
+
heyditto endpoints keys create my-endpoint --k8s-secret ditto-inference --namespace prod
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Requirements: the platform CLI on `PATH` and signed in — both are checked
|
|
463
|
+
*before* anything is minted, so a missing CLI costs you nothing. Without a
|
|
464
|
+
terminal the command needs `--yes`; interactively it shows what it will do and
|
|
465
|
+
asks you to type the secret name back. Defaults: expiry `1y`, key label
|
|
466
|
+
`<store>:<target>:<NAME>` (GitHub keeps its original `gh:<owner>/<repo>:<NAME>`);
|
|
467
|
+
`--budget` caps the key's spend (`--spend-period` defaults to `monthly`). A
|
|
468
|
+
scoping flag that belongs to another destination is an error rather than being
|
|
469
|
+
ignored. The output shows the key's last four characters, expiry, budget, where
|
|
470
|
+
it was stored, and how to consume it — for GitHub Actions:
|
|
443
471
|
|
|
444
472
|
```yaml
|
|
445
473
|
env:
|
|
@@ -537,7 +565,11 @@ the thread URL the backend returns for its linked app (falling back to the
|
|
|
537
565
|
production app link on older backends). `pull --resume` always resumes the
|
|
538
566
|
harness inside the restored tree (add `--dry-run` to see the launch plan), so
|
|
539
567
|
the next prompt lands in the restored transcript even when the original
|
|
540
|
-
directory still exists on the same machine.
|
|
568
|
+
directory still exists on the same machine. Restored Claude transcripts are
|
|
569
|
+
filed under Claude Code's own project slug for the destination path (every
|
|
570
|
+
non-alphanumeric character becomes `-`), so `claude --resume` finds them in
|
|
571
|
+
paths with underscores or dots such as `/opt/workspace_base/capsule`. macOS
|
|
572
|
+
AppleDouble `._*` files and `.DS_Store` never enter the worktree archive. Worktrees compressed with `zstd`
|
|
541
573
|
are decompressed by Node itself when the `zstd` binary is missing; on a Node
|
|
542
574
|
without zstd support the pull fails with a message naming the binary to install.
|
|
543
575
|
|
package/dist/commands.d.ts
CHANGED
|
@@ -52,10 +52,36 @@ export declare function cmdEndpointKeysRevoke(ref: string, keyId: string, option
|
|
|
52
52
|
}): Promise<void>;
|
|
53
53
|
interface KeysCreateOptions {
|
|
54
54
|
output?: string;
|
|
55
|
+
/** Canonical destination selector; every store also has a shorthand flag. */
|
|
56
|
+
store?: string;
|
|
57
|
+
secret?: string;
|
|
55
58
|
ghSecret?: string;
|
|
59
|
+
gitlabVar?: string;
|
|
60
|
+
awsSecret?: string;
|
|
61
|
+
gcpSecret?: string;
|
|
62
|
+
azSecret?: string;
|
|
63
|
+
opItem?: string;
|
|
64
|
+
vaultSecret?: string;
|
|
65
|
+
dopplerSecret?: string;
|
|
66
|
+
cfSecret?: string;
|
|
67
|
+
vercelEnv?: string;
|
|
68
|
+
k8sSecret?: string;
|
|
69
|
+
flySecret?: string;
|
|
56
70
|
repo?: string;
|
|
57
71
|
env?: string;
|
|
58
72
|
org?: string;
|
|
73
|
+
region?: string;
|
|
74
|
+
project?: string;
|
|
75
|
+
keyVault?: string;
|
|
76
|
+
opVault?: string;
|
|
77
|
+
mount?: string;
|
|
78
|
+
field?: string;
|
|
79
|
+
dopplerConfig?: string;
|
|
80
|
+
worker?: string;
|
|
81
|
+
vercelTarget?: string;
|
|
82
|
+
namespace?: string;
|
|
83
|
+
k8sKey?: string;
|
|
84
|
+
app?: string;
|
|
59
85
|
name?: string;
|
|
60
86
|
expires?: string;
|
|
61
87
|
budget?: string;
|
|
@@ -63,11 +89,16 @@ interface KeysCreateOptions {
|
|
|
63
89
|
yes?: boolean;
|
|
64
90
|
}
|
|
65
91
|
/**
|
|
66
|
-
* Mints a key on an endpoint and hands the plaintext straight to
|
|
67
|
-
*
|
|
68
|
-
*
|
|
92
|
+
* Mints a key on an endpoint and hands the plaintext straight to the platform
|
|
93
|
+
* CLI over stdin (`gh secret set`, `gcloud secrets create --data-file=-`, …).
|
|
94
|
+
* The key is never printed, logged or stored locally; when the platform CLI
|
|
95
|
+
* fails it is revoked again so nothing usable is left behind.
|
|
69
96
|
*/
|
|
70
97
|
export declare function cmdEndpointKeysCreate(ref: string, options: KeysCreateOptions): Promise<void>;
|
|
98
|
+
/** Lists every destination and whether this machine can delegate to it. */
|
|
99
|
+
export declare function cmdEndpointKeyStores(options: {
|
|
100
|
+
output?: string;
|
|
101
|
+
}): Promise<void>;
|
|
71
102
|
/** Registers the `endpoints` group; bare `heyditto endpoints [flags]` still lists. */
|
|
72
103
|
export declare function registerEndpointCommands(program: Command, addExamples: (c: Command, ex: string) => Command, outputOption: () => Option): void;
|
|
73
104
|
interface SessionsOptions {
|
package/dist/commands.js
CHANGED
|
@@ -7,12 +7,20 @@ import { createEndpoint, createKey, deleteEndpoint, findEndpoint, getEndpoint, i
|
|
|
7
7
|
import { openInBrowser } from "./browser.js";
|
|
8
8
|
import { endpointURL } from "./config.js";
|
|
9
9
|
import { activationLink, formatActivation } from "./endpoint-format.js";
|
|
10
|
-
import {
|
|
10
|
+
import { STORE_IDS, VERCEL_TARGETS, deliver, probeStores, selectStore, } from "./secret-stores/index.js";
|
|
11
11
|
import { SESSION_ENV, SESSION_ID_HEADER, endSession, readSessionHistory, resolveActiveSession, startSession, useSession, } from "./mcp-session.js";
|
|
12
12
|
import { readStoredAuth, updateStoredAuth } from "./store.js";
|
|
13
13
|
function pad(s, n) {
|
|
14
14
|
return s.length >= n ? s : s + " ".repeat(n - s.length);
|
|
15
15
|
}
|
|
16
|
+
/** Left-aligned columns sized to their widest cell; the last column is not padded. */
|
|
17
|
+
function printTable(header, rows) {
|
|
18
|
+
const widths = header.map((h, i) => Math.max(h.length, ...rows.map((r) => r[i].length)));
|
|
19
|
+
const line = (r) => r.map((c, i) => (i === r.length - 1 ? c : pad(c, widths[i]))).join(" ");
|
|
20
|
+
process.stdout.write(`${line(header)}\n`);
|
|
21
|
+
for (const r of rows)
|
|
22
|
+
process.stdout.write(`${line(r)}\n`);
|
|
23
|
+
}
|
|
16
24
|
function spendColumn(e) {
|
|
17
25
|
const used = (e.spentTokens ?? 0).toLocaleString();
|
|
18
26
|
if (e.spendLimitTokens == null || e.spendLimitTokens < 0)
|
|
@@ -305,11 +313,7 @@ export async function cmdEndpointKeys(ref, options) {
|
|
|
305
313
|
k.lastUsedAt ? `used ${k.lastUsedAt.slice(0, 16).replace("T", " ")}` : "",
|
|
306
314
|
]);
|
|
307
315
|
const header = ["ID", "KEY", "NAME", "STATE", "LAST USED"];
|
|
308
|
-
|
|
309
|
-
const line = (r) => r.map((c, i) => (i === r.length - 1 ? c : pad(c, widths[i]))).join(" ");
|
|
310
|
-
process.stdout.write(`${line(header)}\n`);
|
|
311
|
-
for (const r of rows)
|
|
312
|
-
process.stdout.write(`${line(r)}\n`);
|
|
316
|
+
printTable(header, rows);
|
|
313
317
|
}
|
|
314
318
|
export async function cmdEndpointKeysRevoke(ref, keyId, options) {
|
|
315
319
|
const { endpoint } = await getEndpoint(ref);
|
|
@@ -321,6 +325,23 @@ export async function cmdEndpointKeysRevoke(ref, keyId, options) {
|
|
|
321
325
|
}
|
|
322
326
|
process.stdout.write(`Revoked key ${keyId} on ${endpoint.slug}.\n`);
|
|
323
327
|
}
|
|
328
|
+
/** Shorthand flag values keyed by the store they select. */
|
|
329
|
+
function shorthandsOf(options) {
|
|
330
|
+
return {
|
|
331
|
+
github: options.ghSecret,
|
|
332
|
+
gitlab: options.gitlabVar,
|
|
333
|
+
aws: options.awsSecret,
|
|
334
|
+
gcloud: options.gcpSecret,
|
|
335
|
+
azure: options.azSecret,
|
|
336
|
+
"1password": options.opItem,
|
|
337
|
+
vault: options.vaultSecret,
|
|
338
|
+
doppler: options.dopplerSecret,
|
|
339
|
+
cloudflare: options.cfSecret,
|
|
340
|
+
vercel: options.vercelEnv,
|
|
341
|
+
kubernetes: options.k8sSecret,
|
|
342
|
+
fly: options.flySecret,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
324
345
|
function parseKeyBudget(raw) {
|
|
325
346
|
if (raw === undefined)
|
|
326
347
|
return undefined;
|
|
@@ -335,53 +356,38 @@ function parseKeyExpiry(raw) {
|
|
|
335
356
|
return value;
|
|
336
357
|
throw new Error(`--expires must be one of: ${KEY_EXPIRIES.join(", ")}`);
|
|
337
358
|
}
|
|
338
|
-
/** Resolves where the secret goes from --repo / --env / --org (repo falls back to the cwd, like gh). */
|
|
339
|
-
function resolveSecretTarget(options) {
|
|
340
|
-
const org = options.org?.trim();
|
|
341
|
-
const env = options.env?.trim();
|
|
342
|
-
if (org) {
|
|
343
|
-
if (options.repo || env)
|
|
344
|
-
throw new Error("--org cannot be combined with --repo or --env (organization secrets are not scoped to one repository)");
|
|
345
|
-
if (!/^[A-Za-z0-9_.-]+$/.test(org))
|
|
346
|
-
throw new Error(`--org must be an organization login, got "${options.org}"`);
|
|
347
|
-
return { kind: "org", org };
|
|
348
|
-
}
|
|
349
|
-
const repo = options.repo ? validateRepo(options.repo) : resolveRepoFromCwd();
|
|
350
|
-
if (env)
|
|
351
|
-
return { kind: "env", repo, env };
|
|
352
|
-
return { kind: "repo", repo };
|
|
353
|
-
}
|
|
354
359
|
/**
|
|
355
|
-
* Mints a key on an endpoint and hands the plaintext straight to
|
|
356
|
-
*
|
|
357
|
-
*
|
|
360
|
+
* Mints a key on an endpoint and hands the plaintext straight to the platform
|
|
361
|
+
* CLI over stdin (`gh secret set`, `gcloud secrets create --data-file=-`, …).
|
|
362
|
+
* The key is never printed, logged or stored locally; when the platform CLI
|
|
363
|
+
* fails it is revoked again so nothing usable is left behind.
|
|
358
364
|
*/
|
|
359
365
|
export async function cmdEndpointKeysCreate(ref, options) {
|
|
360
|
-
|
|
361
|
-
throw new Error("--gh-secret <NAME> is required: this command only mints keys straight into a GitHub Actions secret");
|
|
362
|
-
const secretName = validateSecretName(options.ghSecret);
|
|
366
|
+
const { store, name: secretName } = selectStore({ ...options, shorthands: shorthandsOf(options) });
|
|
363
367
|
const budget = parseKeyBudget(options.budget);
|
|
364
368
|
const expiresIn = parseKeyExpiry(options.expires);
|
|
365
369
|
if (options.spendPeriod !== undefined && budget === undefined)
|
|
366
370
|
throw new Error("--spend-period only applies together with --budget");
|
|
367
371
|
const spendPeriod = budget !== undefined ? (options.spendPeriod ?? "monthly") : undefined;
|
|
368
|
-
// Everything that can fail cheaply happens before any write:
|
|
369
|
-
// signed in, target
|
|
370
|
-
|
|
371
|
-
|
|
372
|
+
// Everything that can fail cheaply happens before any write: the platform
|
|
373
|
+
// CLI is present and signed in, its target is known, the endpoint exists,
|
|
374
|
+
// and the operator confirmed.
|
|
375
|
+
store.preflight();
|
|
376
|
+
const target = store.resolveTarget(options);
|
|
372
377
|
const { endpoint, catalog } = await getEndpoint(ref);
|
|
373
|
-
const keyName = options.name?.trim() || (target
|
|
374
|
-
const plan = `Will mint key "${keyName}" on ${endpoint.slug} (expires ${expiresIn}${budget !== undefined ? `, budget ${budget.toLocaleString()} tokens ${spendPeriod}` : ""}) and
|
|
375
|
-
await confirmTyped({ action: `mint a key on ${endpoint.slug} and
|
|
378
|
+
const keyName = options.name?.trim() || defaultKeyName(store, target, secretName);
|
|
379
|
+
const plan = `Will mint key "${keyName}" on ${endpoint.slug} (expires ${expiresIn}${budget !== undefined ? `, budget ${budget.toLocaleString()} tokens ${spendPeriod}` : ""}) and store ${secretName} in ${target.describe}.`;
|
|
380
|
+
await confirmTyped({ action: `mint a key on ${endpoint.slug} and store ${secretName} in ${store.label}`, expected: secretName, label: `the ${store.nameLabel.toLowerCase()}`, yes: options.yes, preview: plan });
|
|
376
381
|
const minted = await createKey(endpoint.id, {
|
|
377
382
|
name: keyName,
|
|
378
383
|
expiresIn,
|
|
379
384
|
...(budget !== undefined ? { spendLimitTokens: budget, spendPeriod } : {}),
|
|
380
385
|
});
|
|
381
|
-
// Split the plaintext off immediately; only `plaintext` may reach
|
|
386
|
+
// Split the plaintext off immediately; only `plaintext` may reach the
|
|
387
|
+
// platform CLI's stdin.
|
|
382
388
|
const { key: plaintext, ...key } = minted;
|
|
383
389
|
try {
|
|
384
|
-
|
|
390
|
+
deliver(store, secretName, store.deliveries(secretName, target), plaintext ?? "");
|
|
385
391
|
}
|
|
386
392
|
catch (err) {
|
|
387
393
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -396,7 +402,7 @@ export async function cmdEndpointKeysCreate(ref, options) {
|
|
|
396
402
|
}
|
|
397
403
|
const anthropicBaseUrl = apiRootOf(catalog.baseUrl);
|
|
398
404
|
const openaiBaseUrl = catalog.baseUrl;
|
|
399
|
-
const
|
|
405
|
+
const gateway = { anthropicBaseUrl, openaiBaseUrl };
|
|
400
406
|
if (isJSON(options)) {
|
|
401
407
|
process.stdout.write(`${JSON.stringify({
|
|
402
408
|
endpoint: { id: endpoint.id, slug: endpoint.slug },
|
|
@@ -409,24 +415,57 @@ export async function cmdEndpointKeysCreate(ref, options) {
|
|
|
409
415
|
spendLimitTokens: key.spendLimitTokens ?? budget ?? null,
|
|
410
416
|
spendPeriod: key.spendPeriod ?? spendPeriod ?? null,
|
|
411
417
|
},
|
|
412
|
-
|
|
418
|
+
store: store.id,
|
|
419
|
+
secret: {
|
|
420
|
+
name: secretName,
|
|
421
|
+
...target.fields,
|
|
422
|
+
describe: target.describe,
|
|
423
|
+
...(store.snippet ? { snippet: store.snippet(secretName, target) } : {}),
|
|
424
|
+
},
|
|
413
425
|
gateway: { baseUrl: catalog.baseUrl, anthropicBaseUrl, openaiBaseUrl },
|
|
414
426
|
}, null, 2)}\n`);
|
|
415
427
|
return;
|
|
416
428
|
}
|
|
417
429
|
process.stdout.write([
|
|
418
430
|
`Minted key …${key.keyHint} (${key.name ?? keyName}) on ${endpoint.slug}: expires ${expiresIn}${budget !== undefined ? `, budget ${budget.toLocaleString()} tokens ${spendPeriod}` : ", no spend cap"}.`,
|
|
419
|
-
`Stored
|
|
431
|
+
`Stored ${secretName} in ${target.describe} via ${store.bin}. The key was not printed and is not kept locally.`,
|
|
420
432
|
"",
|
|
421
|
-
|
|
422
|
-
" env:",
|
|
423
|
-
` ANTHROPIC_AUTH_TOKEN: ${snippet}`,
|
|
424
|
-
` ANTHROPIC_BASE_URL: ${anthropicBaseUrl}`,
|
|
425
|
-
` # OpenAI-compatible clients: OPENAI_API_KEY: ${snippet} with OPENAI_BASE_URL: ${openaiBaseUrl}`,
|
|
433
|
+
...store.usage(secretName, target, gateway),
|
|
426
434
|
"",
|
|
427
435
|
`Revoke later with: heyditto endpoints keys revoke ${endpoint.slug} ${key.id}`,
|
|
428
436
|
].join("\n") + "\n");
|
|
429
437
|
}
|
|
438
|
+
/** Key label in the Ditto app: which store, which target, which name. */
|
|
439
|
+
function defaultKeyName(store, target, secretName) {
|
|
440
|
+
if (store.keyName)
|
|
441
|
+
return store.keyName(secretName, target);
|
|
442
|
+
const scope = Object.entries(target.fields)
|
|
443
|
+
.filter(([key, value]) => key !== "kind" && value)
|
|
444
|
+
.map(([, value]) => value)
|
|
445
|
+
.join("/");
|
|
446
|
+
return `${store.id}:${scope ? `${scope}:` : ""}${secretName}`;
|
|
447
|
+
}
|
|
448
|
+
/** Lists every destination and whether this machine can delegate to it. */
|
|
449
|
+
export async function cmdEndpointKeyStores(options) {
|
|
450
|
+
const probes = probeStores();
|
|
451
|
+
if (isJSON(options)) {
|
|
452
|
+
process.stdout.write(`${JSON.stringify(probes, null, 2)}\n`);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const header = ["STORE", "DESTINATION", "CLI", "STATUS", "FLAGS"];
|
|
456
|
+
const rows = probes.map((p) => [
|
|
457
|
+
p.id,
|
|
458
|
+
p.label,
|
|
459
|
+
p.resolvedBin,
|
|
460
|
+
p.installed ? "installed" : "not installed",
|
|
461
|
+
[`${p.shorthand} <${p.nameLabel}>`, ...p.flags].join(" "),
|
|
462
|
+
]);
|
|
463
|
+
printTable(header, rows);
|
|
464
|
+
const missing = probes.filter((p) => !p.installed);
|
|
465
|
+
if (missing.length > 0) {
|
|
466
|
+
process.stdout.write(`\nNot installed: ${missing.map((p) => p.bin).join(", ")}. Install the one you need, or store the key by hand from the console.\n`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
430
469
|
/** Registers the `endpoints` group; bare `heyditto endpoints [flags]` still lists. */
|
|
431
470
|
export function registerEndpointCommands(program, addExamples, outputOption) {
|
|
432
471
|
const endpoints = program
|
|
@@ -437,7 +476,7 @@ export function registerEndpointCommands(program, addExamples, outputOption) {
|
|
|
437
476
|
.addHelpText("after", `
|
|
438
477
|
Endpoint controls spend your Ditto credits, so delete, key revocation and
|
|
439
478
|
spend-limit increases ask you to type the slug back (or pass --yes).
|
|
440
|
-
'keys create
|
|
479
|
+
'keys create' mints a key straight into a secret manager via its own CLI
|
|
441
480
|
through the gh CLI; the plaintext never reaches your terminal.`);
|
|
442
481
|
addExamples(endpoints
|
|
443
482
|
.command("list", { isDefault: true })
|
|
@@ -513,13 +552,38 @@ through the gh CLI; the plaintext never reaches your terminal.`);
|
|
|
513
552
|
.action(cmdEndpointKeys);
|
|
514
553
|
addExamples(keys
|
|
515
554
|
.command("create")
|
|
516
|
-
.description("mint a key and store it straight into a
|
|
555
|
+
.description("mint a key and store it straight into a secret manager through its own CLI (the key is never printed)")
|
|
517
556
|
.argument("<endpoint>", "endpoint slug or id")
|
|
518
|
-
.
|
|
519
|
-
.option("--
|
|
520
|
-
.option("--
|
|
521
|
-
.option("--
|
|
522
|
-
.option("--
|
|
557
|
+
.addOption(new Option("--store <destination>", "where the key goes").choices([...STORE_IDS]))
|
|
558
|
+
.option("--secret <NAME>", "name of the secret, variable, item or path to store (with --store)")
|
|
559
|
+
.option("--gh-secret <NAME>", "shorthand for --store github --secret NAME")
|
|
560
|
+
.option("--gitlab-var <NAME>", "shorthand for --store gitlab --secret NAME")
|
|
561
|
+
.option("--aws-secret <NAME>", "shorthand for --store aws --secret NAME")
|
|
562
|
+
.option("--gcp-secret <NAME>", "shorthand for --store gcloud --secret NAME")
|
|
563
|
+
.option("--az-secret <NAME>", "shorthand for --store azure --secret NAME")
|
|
564
|
+
.option("--op-item <TITLE>", "shorthand for --store 1password --secret TITLE")
|
|
565
|
+
.option("--vault-secret <PATH>", "shorthand for --store vault --secret PATH")
|
|
566
|
+
.option("--doppler-secret <NAME>", "shorthand for --store doppler --secret NAME")
|
|
567
|
+
.option("--cf-secret <NAME>", "shorthand for --store cloudflare --secret NAME")
|
|
568
|
+
.option("--vercel-env <NAME>", "shorthand for --store vercel --secret NAME")
|
|
569
|
+
.option("--k8s-secret <NAME>", "shorthand for --store kubernetes --secret NAME")
|
|
570
|
+
.option("--fly-secret <NAME>", "shorthand for --store fly --secret NAME")
|
|
571
|
+
.option("--repo <owner/repo>", "github: repository for the secret (default: the repo of the current directory)")
|
|
572
|
+
.option("--env <environment>", "github: deployment environment; gitlab: variable environment scope")
|
|
573
|
+
.option("--org <org>", "github: organization secret; gitlab: group variable (cannot be combined with --repo/--env)")
|
|
574
|
+
.option("--region <region>", "aws: region for the secret (default: the configured region)")
|
|
575
|
+
.option("--project <project>", "gitlab, gcloud, doppler: project (default: the CLI's configured project)")
|
|
576
|
+
.option("--key-vault <name>", "azure: Key Vault the secret goes into (required)")
|
|
577
|
+
.option("--op-vault <name>", "1password: vault for the item (default: Private)")
|
|
578
|
+
.option("--mount <mount>", "vault: kv mount (default: secret)")
|
|
579
|
+
.option("--field <field>", "vault: field at the path (default: token)")
|
|
580
|
+
.option("--doppler-config <config>", "doppler: config within the project, e.g. prod")
|
|
581
|
+
.option("--worker <name>", "cloudflare: Worker name (default: the local wrangler config)")
|
|
582
|
+
.addOption(new Option("--vercel-target <target>", "vercel: environment").choices([...VERCEL_TARGETS]))
|
|
583
|
+
.option("--namespace <namespace>", "kubernetes: namespace (default: default)")
|
|
584
|
+
.option("--k8s-key <key>", "kubernetes: key within the secret (default: key)")
|
|
585
|
+
.option("--app <app>", "fly: app name (default: the local fly.toml)")
|
|
586
|
+
.option("--name <label>", "key name shown in the Ditto app (default: <store>:<target>:<NAME>)")
|
|
523
587
|
.addOption(new Option("--expires <duration>", "server-side key expiry").choices([...KEY_EXPIRIES]).default("1y"))
|
|
524
588
|
.option("--budget <tokens>", "spend cap for the key, in Ditto tokens")
|
|
525
589
|
.addOption(new Option("--spend-period <period>", "window the key's spend cap resets on (with --budget; default monthly)").choices(["daily", "weekly", "monthly", "yearly", "never"]))
|
|
@@ -527,8 +591,19 @@ through the gh CLI; the plaintext never reaches your terminal.`);
|
|
|
527
591
|
.addOption(outputOption())
|
|
528
592
|
.action(cmdEndpointKeysCreate), ` heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY # repo of the current directory
|
|
529
593
|
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --repo acme/app --budget 5000000
|
|
530
|
-
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --
|
|
531
|
-
heyditto endpoints keys create my-endpoint --
|
|
594
|
+
heyditto endpoints keys create my-endpoint --gh-secret DITTO_KEY --org acme --expires 6mo --output json
|
|
595
|
+
heyditto endpoints keys create my-endpoint --store aws --secret DITTO_KEY --region us-east-1
|
|
596
|
+
heyditto endpoints keys create my-endpoint --gcp-secret DITTO_KEY --project my-gcp-project
|
|
597
|
+
heyditto endpoints keys create my-endpoint --az-secret DITTO_KEY --key-vault my-vault
|
|
598
|
+
heyditto endpoints keys create my-endpoint --op-item "Ditto inference" --op-vault Engineering
|
|
599
|
+
heyditto endpoints keys create my-endpoint --vault-secret ditto/inference --mount secret --field token
|
|
600
|
+
heyditto endpoints keys create my-endpoint --gitlab-var DITTO_KEY --project acme/app
|
|
601
|
+
heyditto endpoints keys create my-endpoint --k8s-secret ditto-inference --namespace prod`);
|
|
602
|
+
keys
|
|
603
|
+
.command("stores")
|
|
604
|
+
.description("list the secret managers keys can be minted into, and whether their CLI is installed here")
|
|
605
|
+
.addOption(outputOption())
|
|
606
|
+
.action(cmdEndpointKeyStores);
|
|
532
607
|
keys
|
|
533
608
|
.command("revoke")
|
|
534
609
|
.description("revoke one key")
|
|
@@ -765,10 +840,6 @@ export async function cmdAgents(options) {
|
|
|
765
840
|
connectionsColumn(a),
|
|
766
841
|
]);
|
|
767
842
|
const header = ["ID", "KIND", "NAME", "THREADS", "LAST ACTIVITY", "CONNECTIONS"];
|
|
768
|
-
|
|
769
|
-
const line = (r) => r.map((c, i) => (i === r.length - 1 ? c : pad(c, widths[i]))).join(" ");
|
|
770
|
-
process.stdout.write(`${line(header)}\n`);
|
|
771
|
-
for (const r of rows)
|
|
772
|
-
process.stdout.write(`${line(r)}\n`);
|
|
843
|
+
printTable(header, rows);
|
|
773
844
|
}
|
|
774
845
|
//# sourceMappingURL=commands.js.map
|