@eightstate/escli 0.5.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/CONVENTIONS.md +59 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/RELEASE-NOTES-0.5.0.md +34 -0
- package/dist/base-command.js +166 -0
- package/dist/commands/audio/get.js +39 -0
- package/dist/commands/audio/index.js +18 -0
- package/dist/commands/audio/list.js +39 -0
- package/dist/commands/audio/status.js +34 -0
- package/dist/commands/audio/transcribe.js +99 -0
- package/dist/commands/auth/index.js +18 -0
- package/dist/commands/auth/login.js +38 -0
- package/dist/commands/auth/logout.js +27 -0
- package/dist/commands/auth/profiles.js +31 -0
- package/dist/commands/auth/status.js +27 -0
- package/dist/commands/auth/switch.js +24 -0
- package/dist/commands/docs/fetch.js +37 -0
- package/dist/commands/docs/get.js +47 -0
- package/dist/commands/docs/index.js +18 -0
- package/dist/commands/docs/search.js +55 -0
- package/dist/commands/fetch.js +55 -0
- package/dist/commands/image/edit.js +59 -0
- package/dist/commands/image/generate.js +67 -0
- package/dist/commands/image/index.js +18 -0
- package/dist/commands/models.js +27 -0
- package/dist/commands/research.js +92 -0
- package/dist/commands/search.js +54 -0
- package/dist/commands/social.js +69 -0
- package/dist/commands/usage.js +51 -0
- package/dist/commands/version.js +22 -0
- package/dist/entry.js +120 -0
- package/dist/io/io.js +322 -0
- package/dist/lib/build-flags.js +2 -0
- package/dist/lib/command-metadata.js +8 -0
- package/dist/lib/envelope.js +28 -0
- package/dist/lib/escli-error.js +20 -0
- package/dist/lib/global-flags.js +29 -0
- package/dist/lib/globals.js +2 -0
- package/dist/lib/manifest.js +67 -0
- package/dist/lib/oclif-manifest-check.js +11 -0
- package/dist/lib/registry.js +228 -0
- package/dist/services/audio.js +454 -0
- package/dist/services/auth.js +329 -0
- package/dist/services/credentials.js +137 -0
- package/dist/services/docs.js +303 -0
- package/dist/services/fetch.js +197 -0
- package/dist/services/image.js +297 -0
- package/dist/services/models.js +131 -0
- package/dist/services/research.js +504 -0
- package/dist/services/search.js +195 -0
- package/dist/services/social.js +224 -0
- package/dist/services/usage.js +165 -0
- package/oclif.manifest.json +3377 -0
- package/package.json +57 -0
package/CONVENTIONS.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# escli conventions
|
|
2
|
+
|
|
3
|
+
## Command grammar
|
|
4
|
+
|
|
5
|
+
- Prefer `noun verb [args] [options]`.
|
|
6
|
+
- Root leaves are allowed only for established top-level services: `version`, `models`, `search`, `fetch`, `research`, `social`, `usage`.
|
|
7
|
+
- Namespaces (`auth`, `audio`, `docs`, `image`) must have a bare-topic command that emits usage, exits `2`, and uses the envelope in JSON mode.
|
|
8
|
+
- Aliases are declared in `src-ts/lib/registry.ts`, not ad hoc in command code.
|
|
9
|
+
|
|
10
|
+
## Machine envelope
|
|
11
|
+
|
|
12
|
+
All JSON output uses:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
{ ok: true, data, error: null, meta }
|
|
16
|
+
{ ok: false, data: null, error, meta }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`meta.schema_version` is `"1"`. `meta.warnings` is always an array. `error.code` is a dotted `ErrorCode` from `@eightstate/contracts/errors`.
|
|
20
|
+
|
|
21
|
+
## stdout / stderr
|
|
22
|
+
|
|
23
|
+
- `--json`: stdout is exactly one envelope plus newline.
|
|
24
|
+
- `--quiet`: suppress diagnostics/progress.
|
|
25
|
+
- Human output may use stdout for data and stderr for diagnostics, but must be deterministic under `NO_COLOR` and non-TTY.
|
|
26
|
+
- Command and service modules must not write directly to stdout/stderr. Use `src-ts/io/**`; ESLint enforces this.
|
|
27
|
+
|
|
28
|
+
## TTY and color
|
|
29
|
+
|
|
30
|
+
Renderers must degrade cleanly for non-TTY, `NO_COLOR=1`, and `CI=1`. No JSON parser should ever need to strip ANSI or progress text from stdout.
|
|
31
|
+
|
|
32
|
+
## Exit codes
|
|
33
|
+
|
|
34
|
+
- `0` success
|
|
35
|
+
- `1` non-retryable error
|
|
36
|
+
- `2` usage/validation
|
|
37
|
+
- `4` auth/permission
|
|
38
|
+
- `5` not found
|
|
39
|
+
- `8` transient/retryable
|
|
40
|
+
|
|
41
|
+
Runtime exit mapping must match `packages/contracts/src/failure-taxonomy.ts`.
|
|
42
|
+
|
|
43
|
+
## Error codes
|
|
44
|
+
|
|
45
|
+
Use lowercase dotted codes with a namespace prefix (`auth.required`, `docs.fetch_failed`, `api.error`). Do not add a code without adding taxonomy coverage, registry coverage, and contract tests.
|
|
46
|
+
|
|
47
|
+
Current usage decision: `usage.fetch_failed` is 404/not-found only. Other usage 4xx failures use `api.error` unless they have a more specific auth/rate-limit code.
|
|
48
|
+
|
|
49
|
+
## Enforcement
|
|
50
|
+
|
|
51
|
+
- Runner owns parse errors, validation errors, envelope construction, and exit codes.
|
|
52
|
+
- Commands return typed data or throw `EscliError`.
|
|
53
|
+
- `pnpm lint:bypass:check` proves direct process IO, `process.exit`, `console`, and legacy `{success: ...}` payload bypasses fail lint.
|
|
54
|
+
- Test endpoint env vars must be guarded by inline `typeof __ESCLI_TEST__ === 'undefined' || __ESCLI_TEST__` checks so prod `--define __ESCLI_TEST__:false` dead-code-eliminates `ESCLI_TEST_*` strings.
|
|
55
|
+
- `pnpm check:prod-symbols` must report `ESCLI_TEST_ symbol count: 0` for the prod binary.
|
|
56
|
+
|
|
57
|
+
## Manifest
|
|
58
|
+
|
|
59
|
+
`buildManifest()` walks the command registry and validates against `ManifestSchema`. Root `--schema`/`--describe` returns all commands; `--schema <namespace>` and `--schema <namespace> <verb>` return scoped command sets by command-id prefix.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Eightstate
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# escli
|
|
2
|
+
|
|
3
|
+
`escli` is the Eightstate command line for agent-friendly AI workflows: auth, models, usage, docs, fetch, search, social search, deep research, audio transcription, and image generation/editing.
|
|
4
|
+
|
|
5
|
+
Version `0.5.0` is the TypeScript/Bun cutover. The CLI now ships as native binaries and uses the stable `{ok,data,error,meta}` JSON envelope.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
### macOS / Linux
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
curl -fsSL https://raw.githubusercontent.com/eight-state/eightstate/main/cli/install.sh | bash
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Set `ESCLI_VERSION`, `ESCLI_RELEASE_BASE_URL`, or `ESCLI_INSTALL_DIR` to override the release or install location. Default install dir: `$HOME/.local/bin`.
|
|
16
|
+
|
|
17
|
+
### Windows PowerShell
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
irm https://raw.githubusercontent.com/eight-state/eightstate/main/cli/install.ps1 | iex
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Default install dir: `$env:LOCALAPPDATA\eightstate\bin`.
|
|
24
|
+
|
|
25
|
+
### npm
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add -g @eightstate/escli
|
|
29
|
+
# or npm install -g @eightstate/escli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The native archive installers are the primary zero-runtime path. npm is supported for JS environments.
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
escli auth login
|
|
38
|
+
escli models
|
|
39
|
+
escli docs get react "useEffect cleanup" --tokens 5000
|
|
40
|
+
escli search "Cloudflare Workers 2026 limits"
|
|
41
|
+
escli fetch https://example.com --objective "pricing"
|
|
42
|
+
escli research "market map for residential HVAC software" -o report.md
|
|
43
|
+
escli audio transcribe meeting.mp3 --speakers
|
|
44
|
+
escli image generate "a sunset over the ocean" --open
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Short aliases include `i g`/`i e`, `a s`, `d g`, `au t`, `m`, `s`, `f`, `r`, and `u`.
|
|
48
|
+
|
|
49
|
+
## JSON envelope
|
|
50
|
+
|
|
51
|
+
Use `--json --quiet` for automation. stdout is exactly one JSON object plus a trailing newline.
|
|
52
|
+
|
|
53
|
+
Success:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{"ok":true,"data":{},"error":null,"meta":{"schema_version":"1","escli_version":"0.5.0","command":"version","warnings":[]}}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Failure:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{"ok":false,"data":null,"error":{"code":"auth.required","message":"not authenticated","remediation":{"command":"escli auth login"}},"meta":{"schema_version":"1","escli_version":"0.5.0","command":"models","warnings":[]}}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This replaces the pre-0.5 `{success,...}` shape.
|
|
66
|
+
|
|
67
|
+
## Exit codes
|
|
68
|
+
|
|
69
|
+
| Code | Meaning |
|
|
70
|
+
|---:|---|
|
|
71
|
+
| 0 | success |
|
|
72
|
+
| 1 | non-retryable service/client error |
|
|
73
|
+
| 2 | usage, parsing, validation, missing argument |
|
|
74
|
+
| 4 | auth or permission failure |
|
|
75
|
+
| 5 | not found |
|
|
76
|
+
| 8 | transient/retryable network, rate-limit, timeout, or service-unavailable failure |
|
|
77
|
+
|
|
78
|
+
## Manifest and schema
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
escli --describe # compact full manifest
|
|
82
|
+
escli --schema # full manifest with schemas
|
|
83
|
+
escli --schema docs # docs namespace only
|
|
84
|
+
escli --schema docs get # docs get leaf only
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Manifest output is contract-validated and includes command ids, aliases, args, flags, examples, error codes, input schemas, and output envelope schemas.
|
|
88
|
+
|
|
89
|
+
## Command shape
|
|
90
|
+
|
|
91
|
+
Commands use `noun verb [args] [options]`. Root leaves (`models`, `search`, `fetch`, `research`, `social`, `usage`, `version`) remain single-token. Namespaced topics (`auth`, `audio`, `docs`, `image`) print a usage envelope and exit `2` when called without a verb.
|
|
92
|
+
|
|
93
|
+
## Environment
|
|
94
|
+
|
|
95
|
+
Common variables:
|
|
96
|
+
|
|
97
|
+
| Variable | Description |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `ESCLI_API_KEY` | API key override |
|
|
100
|
+
| `ESCLI_BASE_URL` | OpenAI-compatible endpoint override |
|
|
101
|
+
| `ESCLI_GATE_URL` | Eightstate gate endpoint override |
|
|
102
|
+
| `ESCLI_CONFIG_DIR` | config directory, default `~/.escli` |
|
|
103
|
+
| `ESCLI_CACHE_DIR` | cache directory |
|
|
104
|
+
| service keys | `OPENAI_API_KEY`, `PARALLEL_API_KEY`, `ASSEMBLYAI_API_KEY`, `TAVILY_API_KEY`, `CONTEXT7_API_KEY` |
|
|
105
|
+
|
|
106
|
+
Credentials are stored in `config.json` under `ESCLI_CONFIG_DIR` with owner-only permissions where supported.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# escli 0.5.0 release notes
|
|
2
|
+
|
|
3
|
+
## Breaking: JSON envelope
|
|
4
|
+
|
|
5
|
+
`--json` output changed from the legacy Python `{success: ...}` shape to the stable agent envelope:
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{"ok":true,"data":{},"error":null,"meta":{"schema_version":"1","escli_version":"0.5.0","command":"version","warnings":[]}}
|
|
9
|
+
{"ok":false,"data":null,"error":{"code":"auth.required","message":"not authenticated"},"meta":{"schema_version":"1","escli_version":"0.5.0","command":"models","warnings":[]}}
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Consumers must read `ok`, then branch on `data` or `error.code`.
|
|
13
|
+
|
|
14
|
+
## Install change
|
|
15
|
+
|
|
16
|
+
Installers no longer install Python or `uv`. They download native release archives, verify `checksums.txt`, and install `escli` into:
|
|
17
|
+
|
|
18
|
+
- macOS/Linux: `${ESCLI_INSTALL_DIR:-$HOME/.local/bin}`
|
|
19
|
+
- Windows: `$env:ESCLI_INSTALL_DIR` or `$env:LOCALAPPDATA\eightstate\bin`
|
|
20
|
+
|
|
21
|
+
The installer warns if another `escli` shadows the installed binary on PATH.
|
|
22
|
+
|
|
23
|
+
## Python removed
|
|
24
|
+
|
|
25
|
+
The Python CLI package (`src/escli/**`, `pyproject.toml`, and the root `./escli` shadow script) is removed from this branch. Parity test files remain in the repo, but skip cleanly when the Python oracle is absent.
|
|
26
|
+
|
|
27
|
+
## Command polish
|
|
28
|
+
|
|
29
|
+
- Added `auth` topic alias `a`, including `a s`, `a login`, `a profiles`, `a switch`, and `a logout` forms.
|
|
30
|
+
- Added bare-topic usage stubs for `auth`, `audio`, and `docs`.
|
|
31
|
+
- Scoped `--schema <namespace>` and `--schema <namespace> <verb>` manifests.
|
|
32
|
+
- `usage.fetch_failed` is now reserved for usage 404/not-found; other usage 4xx failures report `api.error`.
|
|
33
|
+
- Production binaries are built with `__ESCLI_TEST__:false` and checked to contain zero `ESCLI_TEST_*` strings.
|
|
34
|
+
- `research --processors` now intentionally returns a compact processor list: `data.processors[]` entries are `{name}` only. The previous descriptive fields (`variant`, `latency`, `description`, `max_fields`) were removed from the 0.5.0 JSON contract.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Command, Flags, Errors } from '@oclif/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { EnvelopeSchema } from '@eightstate/contracts/envelope';
|
|
4
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
5
|
+
import { ExitCodes } from '@eightstate/contracts/exit-codes';
|
|
6
|
+
import { getCommandMetadata } from './lib/command-metadata.js';
|
|
7
|
+
import { buildErrorEnvelope, meta } from './lib/envelope.js';
|
|
8
|
+
import { EscliError } from './lib/escli-error.js';
|
|
9
|
+
import { globalFlags } from './lib/globals.js';
|
|
10
|
+
import { writeStdout, renderKeyValue } from './io/io.js';
|
|
11
|
+
export class BaseCommand extends Command {
|
|
12
|
+
static emitsJsonEnvelope = false;
|
|
13
|
+
static baseFlags = {
|
|
14
|
+
...globalFlags,
|
|
15
|
+
json: Flags.boolean({ description: 'Format output as json.', helpGroup: 'GLOBAL' }),
|
|
16
|
+
};
|
|
17
|
+
humanOutputHandled = false;
|
|
18
|
+
async parseFlags(command) {
|
|
19
|
+
const { flags } = await this.parse(command);
|
|
20
|
+
return flags;
|
|
21
|
+
}
|
|
22
|
+
async run() {
|
|
23
|
+
const start = Date.now();
|
|
24
|
+
let flags = {};
|
|
25
|
+
try {
|
|
26
|
+
const manifestMode = this.manifestMode();
|
|
27
|
+
if (manifestMode) {
|
|
28
|
+
this.parsed = true;
|
|
29
|
+
await this.emitManifest(manifestMode === 'describe');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
flags = await this.parseFlags(this.ctor);
|
|
33
|
+
const data = await this.execute();
|
|
34
|
+
const envelope = this.successEnvelope(data, Date.now() - start);
|
|
35
|
+
if (flags.json || this.ctor.emitsJsonEnvelope) {
|
|
36
|
+
await this.emitJson(envelope);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
await this.emitHuman(data);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
this.parsed = true;
|
|
44
|
+
const escliError = toEscliError(error);
|
|
45
|
+
process.exitCode = escliError.exitCode;
|
|
46
|
+
const envelope = this.errorEnvelope(escliError, Date.now() - start);
|
|
47
|
+
await this.emitJson(envelope);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
successEnvelope(data, durationMs) {
|
|
51
|
+
const metadata = getCommandMetadata(this.id ?? 'unknown');
|
|
52
|
+
const serviceMeta = this.serviceMeta(data);
|
|
53
|
+
const parsedData = metadata ? safeInternalParse(metadata.dataSchema, data) : data;
|
|
54
|
+
const envelope = {
|
|
55
|
+
ok: true,
|
|
56
|
+
data: parsedData,
|
|
57
|
+
error: null,
|
|
58
|
+
meta: { ...this.meta(durationMs), ...serviceMeta },
|
|
59
|
+
};
|
|
60
|
+
return (metadata ? safeInternalParse(EnvelopeSchema(metadata.dataSchema), envelope) : envelope);
|
|
61
|
+
}
|
|
62
|
+
errorEnvelope(error, durationMs) {
|
|
63
|
+
return buildErrorEnvelope(this.id ?? 'unknown', this.config.version, error, durationMs);
|
|
64
|
+
}
|
|
65
|
+
meta(durationMs) {
|
|
66
|
+
return meta(this.id ?? 'unknown', this.config.version, durationMs);
|
|
67
|
+
}
|
|
68
|
+
serviceMeta(data) {
|
|
69
|
+
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
70
|
+
return {};
|
|
71
|
+
return Object.fromEntries(Object.entries(data).filter(([key, value]) => (key.endsWith('_count') || ['credits', 'elapsed_seconds'].includes(key)) && (typeof value === 'number' || value === null)));
|
|
72
|
+
}
|
|
73
|
+
async emitJson(envelope) {
|
|
74
|
+
await writeStdout(`${JSON.stringify(envelope)}\n`);
|
|
75
|
+
}
|
|
76
|
+
manifestMode() {
|
|
77
|
+
for (let index = 0; index < this.argv.length; index += 1) {
|
|
78
|
+
const token = this.argv[index];
|
|
79
|
+
if (!token?.startsWith('--'))
|
|
80
|
+
return undefined;
|
|
81
|
+
if (token === '--describe' || token.startsWith('--describe='))
|
|
82
|
+
return 'describe';
|
|
83
|
+
if (token === '--schema' && (!this.argv[index + 1] || this.argv[index + 1]?.startsWith('--')))
|
|
84
|
+
return 'schema';
|
|
85
|
+
if (token.startsWith('--schema='))
|
|
86
|
+
return 'schema';
|
|
87
|
+
if (['--api-key', '--base-url', '--timeout'].includes(token) && this.argv[index + 1] && !this.argv[index + 1]?.startsWith('--'))
|
|
88
|
+
index += 1;
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
async emitHuman(data) {
|
|
93
|
+
if (this.humanOutputHandled)
|
|
94
|
+
return;
|
|
95
|
+
const metadata = getCommandMetadata(this.id ?? 'unknown');
|
|
96
|
+
if (metadata?.render) {
|
|
97
|
+
await writeStdout(`${await metadata.render(data)}\n`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (data === null || data === undefined) {
|
|
101
|
+
await writeStdout('\n');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
105
|
+
await writeStdout(`${typeof data === 'string' ? data : JSON.stringify(data, null, 2)}\n`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const entries = Object.entries(data);
|
|
109
|
+
const hasNested = entries.some(([, value]) => value !== null && (typeof value === 'object' || Array.isArray(value)));
|
|
110
|
+
if (hasNested) {
|
|
111
|
+
await writeStdout(`${JSON.stringify(data, null, 2)}\n`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await writeStdout(`${renderKeyValue(entries.map(([k, v]) => [k, v === null || v === undefined ? '' : String(v)]))}\n`);
|
|
115
|
+
}
|
|
116
|
+
async emitManifest(compact) {
|
|
117
|
+
const { buildManifest } = await import('./lib/manifest.js');
|
|
118
|
+
await writeStdout(`${JSON.stringify(buildManifest(compact, this.argv.includes('--root-manifest') ? undefined : this.id ?? undefined))}\n`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function safeInternalParse(schema, value) {
|
|
122
|
+
try {
|
|
123
|
+
return schema.parse(value);
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error instanceof z.ZodError) {
|
|
127
|
+
throw new EscliError('internal data validation failed', { code: ErrorCode.Internal, exitCode: ExitCodes.Error, details: error.issues });
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function toEscliError(error) {
|
|
133
|
+
if (error instanceof EscliError)
|
|
134
|
+
return error;
|
|
135
|
+
if (error instanceof z.ZodError) {
|
|
136
|
+
return new EscliError('input validation failed', { code: ErrorCode.ValidationFailed, exitCode: ExitCodes.Usage, details: error.issues });
|
|
137
|
+
}
|
|
138
|
+
if (error instanceof Errors.ExitError) {
|
|
139
|
+
return new EscliError(error.message, { code: ErrorCode.UsageInvalid, exitCode: ExitCodes.Usage, details: { oclif_exit: error.oclif?.exit } });
|
|
140
|
+
}
|
|
141
|
+
if (error instanceof Errors.CLIError) {
|
|
142
|
+
return oclifErrorToEscliError(error);
|
|
143
|
+
}
|
|
144
|
+
if (error instanceof Error)
|
|
145
|
+
return new EscliError(error.message, { code: ErrorCode.Internal });
|
|
146
|
+
return new EscliError('unknown error', { code: ErrorCode.Unknown, details: error });
|
|
147
|
+
}
|
|
148
|
+
function oclifErrorToEscliError(error) {
|
|
149
|
+
const code = classifyOclifUsageError(error);
|
|
150
|
+
return new EscliError(cleanOclifMessage(error.message), { code, exitCode: ExitCodes.Usage, details: { oclif_exit: error.oclif?.exit } });
|
|
151
|
+
}
|
|
152
|
+
function classifyOclifUsageError(error) {
|
|
153
|
+
const name = error.constructor.name;
|
|
154
|
+
const message = error.message;
|
|
155
|
+
if (name === 'NonExistentFlagsError' || message.startsWith('Nonexistent flag') || message.startsWith('Unexpected flag'))
|
|
156
|
+
return ErrorCode.UsageUnknownFlag;
|
|
157
|
+
if (name === 'RequiredArgsError' || message.startsWith('Missing ') || message.includes('expects a value'))
|
|
158
|
+
return ErrorCode.UsageRequired;
|
|
159
|
+
if (message.includes('command ') && message.includes(' not found'))
|
|
160
|
+
return ErrorCode.UsageUnknownCommand;
|
|
161
|
+
return ErrorCode.UsageInvalid;
|
|
162
|
+
}
|
|
163
|
+
function cleanOclifMessage(message) {
|
|
164
|
+
return message.replace(/\nSee more help with --help$/u, '');
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=base-command.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import { writeStdout } from '../../io/io.js';
|
|
5
|
+
import { getAudioTranscript } from '../../services/audio.js';
|
|
6
|
+
import { AudioTranscriptSchema } from './transcribe.js';
|
|
7
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
8
|
+
export const AudioGetDataSchema = z.union([
|
|
9
|
+
AudioTranscriptSchema,
|
|
10
|
+
z.object({ output: z.string(), format: z.enum(['text', 'json', 'srt', 'vtt']), id: z.string().optional() }).passthrough(),
|
|
11
|
+
z.object({ path: z.string(), format: z.enum(['text', 'json', 'srt', 'vtt']), id: z.string().optional() }).passthrough(),
|
|
12
|
+
]);
|
|
13
|
+
export default class AudioGet extends BaseCommand {
|
|
14
|
+
static errors = [ErrorCode.UsageRequired, ErrorCode.UsageUnknownFlag, ErrorCode.AuthRequired, ErrorCode.GateCredentialUnavailable, ErrorCode.ApiUnauthorized, ErrorCode.ApiForbidden, ErrorCode.ApiRateLimited, ErrorCode.ApiError, ErrorCode.NetworkError, ErrorCode.NetworkTimeout, ErrorCode.ServiceUnavailable, ErrorCode.GateInvalidResponse, ErrorCode.FileWriteFailed, ErrorCode.AudioTranscriptionFailed, ErrorCode.AudioTranscriptNotFound];
|
|
15
|
+
static summary = 'Fetch a completed transcript';
|
|
16
|
+
static description = 'Fetch a completed AssemblyAI transcript as text, JSON, SRT, or VTT.';
|
|
17
|
+
static examples = ['<%= config.bin %> audio get tr_xxx', '<%= config.bin %> audio get tr_xxx --format srt', '<%= config.bin %> audio get tr_xxx -o transcript.txt'];
|
|
18
|
+
static aliases = ['audio g', 'au get', 'au g'];
|
|
19
|
+
static flags = {
|
|
20
|
+
format: Flags.string({ description: 'Output format.', options: ['text', 'json', 'srt', 'vtt'], default: 'text' }),
|
|
21
|
+
output: Flags.string({ char: 'o', description: 'Output file.' }),
|
|
22
|
+
};
|
|
23
|
+
static args = {
|
|
24
|
+
transcript_id: Args.string({ description: 'Transcript ID.', required: true }),
|
|
25
|
+
};
|
|
26
|
+
static enableJsonFlag = true;
|
|
27
|
+
static strict = true;
|
|
28
|
+
async execute() {
|
|
29
|
+
const { args, flags } = await this.parse(AudioGet);
|
|
30
|
+
const data = await getAudioTranscript(args.transcript_id, flags.format, flags.output);
|
|
31
|
+
if (!flags.json) {
|
|
32
|
+
if ('output' in data && typeof data.output === 'string')
|
|
33
|
+
await writeStdout(`${data.output}${data.output.endsWith('\n') ? '' : '\n'}`);
|
|
34
|
+
this.humanOutputHandled = true;
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=get.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
import { buildUsageError } from '../../lib/envelope.js';
|
|
3
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
4
|
+
export default class AudioTopic extends BaseCommand {
|
|
5
|
+
static errors = [ErrorCode.UsageInvalid];
|
|
6
|
+
static summary = 'Transcribe and manage audio transcripts';
|
|
7
|
+
static description = 'Use audio transcribe, audio status, audio get, or audio list.';
|
|
8
|
+
static examples = ['<%= config.bin %> audio transcribe meeting.mp3', '<%= config.bin %> audio status tr_xxx', '<%= config.bin %> audio list'];
|
|
9
|
+
static aliases = ['au'];
|
|
10
|
+
static flags = {};
|
|
11
|
+
static args = {};
|
|
12
|
+
static enableJsonFlag = true;
|
|
13
|
+
static strict = true;
|
|
14
|
+
async execute() {
|
|
15
|
+
throw buildUsageError('usage: escli audio <transcribe|status|get|list>');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseCommand } from '../../base-command.js';
|
|
3
|
+
import { writeStdout } from '../../io/io.js';
|
|
4
|
+
import { listAudioTranscripts } from '../../services/audio.js';
|
|
5
|
+
import { AudioTranscriptSchema } from './transcribe.js';
|
|
6
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
7
|
+
export const AudioListDataSchema = z.object({
|
|
8
|
+
transcripts: z.array(AudioTranscriptSchema),
|
|
9
|
+
count: z.number().int().nonnegative(),
|
|
10
|
+
});
|
|
11
|
+
export default class AudioList extends BaseCommand {
|
|
12
|
+
static errors = [ErrorCode.AuthRequired, ErrorCode.GateCredentialUnavailable, ErrorCode.ApiUnauthorized, ErrorCode.ApiForbidden, ErrorCode.ApiRateLimited, ErrorCode.ApiError, ErrorCode.NetworkError, ErrorCode.NetworkTimeout, ErrorCode.ServiceUnavailable, ErrorCode.GateInvalidResponse];
|
|
13
|
+
static summary = 'List recent transcripts';
|
|
14
|
+
static description = 'List recent AssemblyAI transcripts.';
|
|
15
|
+
static examples = ['<%= config.bin %> audio list', '<%= config.bin %> --json audio list'];
|
|
16
|
+
static aliases = ['audio ls', 'au list', 'au ls'];
|
|
17
|
+
static flags = {};
|
|
18
|
+
static args = {};
|
|
19
|
+
static enableJsonFlag = true;
|
|
20
|
+
static strict = true;
|
|
21
|
+
async execute() {
|
|
22
|
+
const { flags } = await this.parse(AudioList);
|
|
23
|
+
const data = await listAudioTranscripts();
|
|
24
|
+
if (!flags.json) {
|
|
25
|
+
if (data.transcripts.length === 0)
|
|
26
|
+
await writeStdout(' No transcripts found.\n');
|
|
27
|
+
else {
|
|
28
|
+
const lines = [`\n ${'ID'.padEnd(40)} ${'STATUS'.padEnd(12)} CREATED`, ` ${'─'.repeat(70)}`];
|
|
29
|
+
for (const transcript of data.transcripts)
|
|
30
|
+
lines.push(` ${transcript.id.padEnd(40)} ${String(transcript.status ?? '').padEnd(12)} ${String(transcript.created ?? '')}`);
|
|
31
|
+
lines.push('');
|
|
32
|
+
await writeStdout(`${lines.join('\n')}\n`);
|
|
33
|
+
}
|
|
34
|
+
this.humanOutputHandled = true;
|
|
35
|
+
}
|
|
36
|
+
return data;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import { writeStdout } from '../../io/io.js';
|
|
5
|
+
import { getAudioStatus } from '../../services/audio.js';
|
|
6
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
7
|
+
export const AudioStatusDataSchema = z.object({
|
|
8
|
+
id: z.string(),
|
|
9
|
+
status: z.string(),
|
|
10
|
+
error: z.string().nullable().optional(),
|
|
11
|
+
});
|
|
12
|
+
export default class AudioStatus extends BaseCommand {
|
|
13
|
+
static errors = [ErrorCode.UsageRequired, ErrorCode.UsageUnknownFlag, ErrorCode.AuthRequired, ErrorCode.GateCredentialUnavailable, ErrorCode.ApiUnauthorized, ErrorCode.ApiForbidden, ErrorCode.ApiRateLimited, ErrorCode.ApiError, ErrorCode.NetworkError, ErrorCode.NetworkTimeout, ErrorCode.ServiceUnavailable, ErrorCode.GateInvalidResponse, ErrorCode.AudioTranscriptNotFound];
|
|
14
|
+
static summary = 'Check transcript status';
|
|
15
|
+
static description = 'Fetch an AssemblyAI transcript job status.';
|
|
16
|
+
static examples = ['<%= config.bin %> audio status tr_xxx', '<%= config.bin %> --json audio status tr_xxx'];
|
|
17
|
+
static aliases = ['audio s', 'au status', 'au s'];
|
|
18
|
+
static flags = {};
|
|
19
|
+
static args = {
|
|
20
|
+
transcript_id: Args.string({ description: 'Transcript ID.', required: true }),
|
|
21
|
+
};
|
|
22
|
+
static enableJsonFlag = true;
|
|
23
|
+
static strict = true;
|
|
24
|
+
async execute() {
|
|
25
|
+
const { args, flags } = await this.parse(AudioStatus);
|
|
26
|
+
const data = await getAudioStatus(args.transcript_id);
|
|
27
|
+
if (!flags.json) {
|
|
28
|
+
await writeStdout(` ${data.id}: ${data.status}${data.error ? `\n error: ${data.error}` : ''}\n`);
|
|
29
|
+
this.humanOutputHandled = true;
|
|
30
|
+
}
|
|
31
|
+
return data;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseCommand } from '../../base-command.js';
|
|
4
|
+
import { writeStdout } from '../../io/io.js';
|
|
5
|
+
import { formatTranscriptText, transcribeAudio } from '../../services/audio.js';
|
|
6
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
7
|
+
export const AudioWordSchema = z.object({ text: z.string() }).passthrough();
|
|
8
|
+
export const AudioUtteranceSchema = z.object({ speaker: z.string().optional(), text: z.string() }).passthrough();
|
|
9
|
+
export const AudioTranscriptSchema = z.object({
|
|
10
|
+
id: z.string(),
|
|
11
|
+
status: z.string().optional(),
|
|
12
|
+
text: z.string().optional(),
|
|
13
|
+
words: z.array(AudioWordSchema).optional(),
|
|
14
|
+
utterances: z.array(AudioUtteranceSchema).optional(),
|
|
15
|
+
}).passthrough();
|
|
16
|
+
export const AudioTranscribeDataSchema = z.union([
|
|
17
|
+
z.object({
|
|
18
|
+
id: z.string(),
|
|
19
|
+
elapsed_seconds: z.number().nonnegative(),
|
|
20
|
+
text: z.string(),
|
|
21
|
+
speakers: z.number().int().nonnegative(),
|
|
22
|
+
words: z.array(AudioWordSchema),
|
|
23
|
+
utterances: z.array(AudioUtteranceSchema),
|
|
24
|
+
}).passthrough(),
|
|
25
|
+
AudioTranscriptSchema,
|
|
26
|
+
z.object({ output: z.string(), format: z.enum(['text', 'json', 'srt', 'vtt']), id: z.string().optional() }).passthrough(),
|
|
27
|
+
z.object({ path: z.string(), format: z.enum(['text', 'json', 'srt', 'vtt']), id: z.string().optional() }).passthrough(),
|
|
28
|
+
]);
|
|
29
|
+
export default class AudioTranscribe extends BaseCommand {
|
|
30
|
+
static errors = [ErrorCode.UsageRequired, ErrorCode.UsageUnknownFlag, ErrorCode.AuthRequired, ErrorCode.GateCredentialUnavailable, ErrorCode.ApiUnauthorized, ErrorCode.ApiForbidden, ErrorCode.ApiRateLimited, ErrorCode.ApiError, ErrorCode.NetworkError, ErrorCode.NetworkTimeout, ErrorCode.ServiceUnavailable, ErrorCode.GateInvalidResponse, ErrorCode.FileNotFound, ErrorCode.FileReadFailed, ErrorCode.FileWriteFailed, ErrorCode.AudioInputMissing, ErrorCode.AudioUploadFailed, ErrorCode.AudioTranscriptionFailed, ErrorCode.AudioTranscriptNotFound];
|
|
31
|
+
static summary = 'Transcribe an audio file or URL';
|
|
32
|
+
static description = 'Upload a local audio file or submit an audio URL to AssemblyAI, poll to completion, and return transcript output.';
|
|
33
|
+
static examples = ['<%= config.bin %> audio transcribe meeting.mp3 --speakers', '<%= config.bin %> audio transcribe https://example.com/audio.mp3 --speakers-expected 3', '<%= config.bin %> --json audio transcribe file.mp3'];
|
|
34
|
+
static aliases = ['audio t', 'au transcribe', 'au t'];
|
|
35
|
+
static flags = {
|
|
36
|
+
output: Flags.string({ char: 'o', description: 'Output file.' }),
|
|
37
|
+
format: Flags.string({ description: 'Output format.', options: ['text', 'json', 'srt', 'vtt'], default: 'text' }),
|
|
38
|
+
speakers: Flags.boolean({ description: 'Enable speaker diarization.', default: false }),
|
|
39
|
+
'speakers-expected': Flags.integer({ description: 'Expected speaker count.' }),
|
|
40
|
+
'speaker-names': Flags.string({ description: 'Identify speakers by name, comma-separated.' }),
|
|
41
|
+
sentiment: Flags.boolean({ description: 'Enable sentiment analysis.', default: false }),
|
|
42
|
+
chapters: Flags.boolean({ description: 'Enable auto chapters.', default: false }),
|
|
43
|
+
entities: Flags.boolean({ description: 'Enable entity detection.', default: false }),
|
|
44
|
+
summarize: Flags.boolean({ description: 'Enable summarization.', default: false }),
|
|
45
|
+
highlights: Flags.boolean({ description: 'Enable auto highlights.', default: false }),
|
|
46
|
+
topics: Flags.boolean({ description: 'Enable topic detection.', default: false }),
|
|
47
|
+
'content-safety': Flags.boolean({ description: 'Enable content safety detection.', default: false }),
|
|
48
|
+
language: Flags.string({ description: 'Language code. Defaults to auto-detection.' }),
|
|
49
|
+
'dual-channel': Flags.boolean({ description: 'Enable dual-channel transcription.', default: false }),
|
|
50
|
+
multichannel: Flags.boolean({ description: 'Enable multichannel transcription.', default: false }),
|
|
51
|
+
'word-boost': Flags.string({ description: 'Boost accuracy for comma-separated words.' }),
|
|
52
|
+
disfluencies: Flags.boolean({ description: 'Include filler words.', default: false }),
|
|
53
|
+
'filter-profanity': Flags.boolean({ description: 'Filter profanity.', default: false }),
|
|
54
|
+
'redact-pii': Flags.boolean({ description: 'Redact personally identifiable information.', default: false }),
|
|
55
|
+
};
|
|
56
|
+
static args = {
|
|
57
|
+
source: Args.string({ description: 'Audio file path or URL.', required: true }),
|
|
58
|
+
};
|
|
59
|
+
static enableJsonFlag = true;
|
|
60
|
+
static strict = true;
|
|
61
|
+
async execute() {
|
|
62
|
+
const { args, flags } = await this.parse(AudioTranscribe);
|
|
63
|
+
const options = {
|
|
64
|
+
source: args.source,
|
|
65
|
+
output: flags.output,
|
|
66
|
+
format: flags.format,
|
|
67
|
+
language: flags.language,
|
|
68
|
+
speakers: flags.speakers,
|
|
69
|
+
speakersExpected: flags['speakers-expected'],
|
|
70
|
+
speakerNames: flags['speaker-names'],
|
|
71
|
+
sentiment: flags.sentiment,
|
|
72
|
+
chapters: flags.chapters,
|
|
73
|
+
entities: flags.entities,
|
|
74
|
+
summarize: flags.summarize,
|
|
75
|
+
highlights: flags.highlights,
|
|
76
|
+
topics: flags.topics,
|
|
77
|
+
contentSafety: flags['content-safety'],
|
|
78
|
+
dualChannel: flags['dual-channel'],
|
|
79
|
+
multichannel: flags.multichannel,
|
|
80
|
+
wordBoost: flags['word-boost'],
|
|
81
|
+
disfluencies: flags.disfluencies,
|
|
82
|
+
filterProfanity: flags['filter-profanity'],
|
|
83
|
+
redactPii: flags['redact-pii'],
|
|
84
|
+
quiet: flags.quiet,
|
|
85
|
+
};
|
|
86
|
+
const data = await transcribeAudio(options);
|
|
87
|
+
if (!flags.json) {
|
|
88
|
+
if ('output' in data && typeof data.output === 'string')
|
|
89
|
+
await writeStdout(`${data.output}${data.output.endsWith('\n') ? '' : '\n'}`);
|
|
90
|
+
else if ('path' in data && typeof data.path === 'string')
|
|
91
|
+
await writeStdout(flags.quiet ? `${data.path}\n` : '');
|
|
92
|
+
else
|
|
93
|
+
await writeStdout(`${formatTranscriptText(data)}\n`);
|
|
94
|
+
this.humanOutputHandled = true;
|
|
95
|
+
}
|
|
96
|
+
return data;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=transcribe.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command.js';
|
|
2
|
+
import { buildUsageError } from '../../lib/envelope.js';
|
|
3
|
+
import { ErrorCode } from '@eightstate/contracts/errors';
|
|
4
|
+
export default class AuthTopic extends BaseCommand {
|
|
5
|
+
static errors = [ErrorCode.UsageInvalid];
|
|
6
|
+
static summary = 'Authenticate and manage profiles';
|
|
7
|
+
static description = 'Use auth login, auth status, auth profiles, auth switch, or auth logout.';
|
|
8
|
+
static examples = ['<%= config.bin %> auth status', '<%= config.bin %> auth login', '<%= config.bin %> auth profiles'];
|
|
9
|
+
static aliases = ['a'];
|
|
10
|
+
static flags = {};
|
|
11
|
+
static args = {};
|
|
12
|
+
static enableJsonFlag = true;
|
|
13
|
+
static strict = true;
|
|
14
|
+
async execute() {
|
|
15
|
+
throw buildUsageError('usage: escli auth <login|status|profiles|switch|logout>');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|