@enter-pro/enter-cli 0.4.2 → 0.4.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/README.md +222 -225
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +114 -6
- package/dist/client.d.ts +1 -4
- package/dist/client.js +13 -63
- package/dist/commands/config.js +5 -10
- package/dist/commands/domain.js +3 -6
- package/dist/commands/login.js +15 -11
- package/dist/commands/logout.js +7 -3
- package/dist/commands/project.js +75 -42
- package/dist/commands/thread.d.ts +37 -1
- package/dist/commands/thread.js +290 -74
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/workspace.js +7 -10
- package/dist/config.d.ts +0 -1
- package/dist/config.js +10 -4
- package/dist/output.d.ts +0 -16
- package/dist/output.js +0 -18
- package/dist/poll.d.ts +1 -0
- package/dist/poll.js +3 -1
- package/dist/thread-events.d.ts +1 -0
- package/dist/thread-events.js +16 -3
- package/dist/workflow.d.ts +46 -0
- package/dist/workflow.js +36 -0
- package/package.json +7 -3
- package/scripts/install-hosts.mjs +29 -0
- package/skills/enter/SKILL.md +40 -0
- package/skills/enter/references/configuration.md +19 -0
package/README.md
CHANGED
|
@@ -1,252 +1,249 @@
|
|
|
1
1
|
# Enter CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for Enter projects, tasks and approval cards. Node 18+ is required.
|
|
4
|
+
The [Enter Skill](skills/enter/SKILL.md) guides agents through the workflow;
|
|
5
|
+
command options are available through `enter-cli <command> --help`.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
production API at `https://api.enter.pro/code/api`. The web app's
|
|
7
|
-
`enter.converge.ai` domain is not the API base URL. Log in again if existing
|
|
8
|
-
credentials were issued by the former `auth.enter.pro` tenant.
|
|
7
|
+
## Authentication and configuration
|
|
9
8
|
|
|
9
|
+
`enter-cli login` uses `auth.converge.ai` and the API at
|
|
10
|
+
`https://api.enter.pro/code/api` (not the web domain `enter.converge.ai`).
|
|
11
|
+
Log in again if credentials came from the former `auth.enter.pro` tenant.
|
|
10
12
|
OAuth and API-key login verify the new token against `/v1/users/info` before
|
|
11
|
-
saving
|
|
12
|
-
credentials
|
|
13
|
+
saving it; verification failure preserves existing credentials. Expired local
|
|
14
|
+
OAuth credentials renew automatically. An already-sent renewal saves rotated
|
|
15
|
+
credentials before honoring cancellation, so the next invocation can still log in.
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
`ENTER_API_KEY` takes precedence over local credentials. `login` saves local
|
|
18
|
+
credentials but cannot switch an injected identity. `logout` clears local
|
|
19
|
+
credentials only and reports `logged_out: false, auth_source: environment` when
|
|
20
|
+
that variable remains set. Switch host-managed authentication in the host, or
|
|
21
|
+
unset the variable in your shell.
|
|
22
|
+
|
|
23
|
+
`config get/set/list` supports `api_url`, `base_path`, and `output` (`json`, `yaml`,
|
|
24
|
+
`table`). Environment overrides are `ENTER_API_URL`, `ENTER_BASE_PATH`, and
|
|
25
|
+
`ENTER_OUTPUT`. Workspace IDs are explicit command arguments; the unused
|
|
26
|
+
`default_workspace` setting is rejected and legacy entries are ignored.
|
|
27
|
+
|
|
28
|
+
## Submit, observe and deliver
|
|
15
29
|
|
|
16
30
|
```sh
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
enter-cli --output json thread chat PROJECT_ID --file requirement.txt
|
|
32
|
+
enter-cli --output json thread wait PROJECT_ID --task-id TASK_ID --timeout 10
|
|
33
|
+
enter-cli --output json thread status PROJECT_ID --task-id TASK_ID
|
|
34
|
+
enter-cli thread wait PROJECT_ID --task-id TASK_ID --stream --timeout 0
|
|
19
35
|
```
|
|
20
36
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
37
|
+
`chat --stdin` also accepts multiline input. Submission returns immediately with
|
|
38
|
+
`task_id`, `submission_status: accepted`, and continuation commands. Follow-ups
|
|
39
|
+
can be submitted while Enter works; acceptance is not completion.
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
| Command | Output and default lifetime |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `status` | One JSON/YAML snapshot, 30-second deadline |
|
|
44
|
+
| `wait --stream` | One continuous observer with batched NDJSON; unlimited by default, explicit --timeout bounds lifetime |
|
|
45
|
+
| `wait` | One result on new narrative progress, a card, terminal state or timeout; 10 seconds |
|
|
46
|
+
| `watch` | Changed snapshots as NDJSON, then `type: result`; 60 seconds; unlimited only with explicit `--timeout 0` |
|
|
47
|
+
|
|
48
|
+
Use one observer per task. Hosts that buffer shell output use bounded `wait`;
|
|
49
|
+
Helix uses `--timeout 10` to return control regularly. On timeout, first handle
|
|
50
|
+
incoming user messages, then report the actual state and continue the returned command for the same task,
|
|
51
|
+
without sleep, resubmission or repeated permission questions. Hosts that consume
|
|
52
|
+
stdout incrementally should use `wait --stream`; unchanged successful observations emit a
|
|
53
|
+
heartbeat every max(30 seconds, progress interval). Hosts own scheduling and interruption.
|
|
54
|
+
|
|
55
|
+
`--task-id` follows exactly the submitted task, including queue time. `--turn N`
|
|
56
|
+
selects a fixed turn instead; without either, observation pins the first turn.
|
|
57
|
+
`--chat-id` scopes lookup and continuation. An interjection may return an external
|
|
58
|
+
message ID that the backend cannot correlate to a turn: report `unknown`, inspect
|
|
59
|
+
messages and the resulting change, and do not substitute the newest turn.
|
|
26
60
|
|
|
27
|
-
|
|
28
|
-
|
|
61
|
+
| Status | Meaning |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `idle` | No turn yet |
|
|
64
|
+
| `queued` | Selected task is queued |
|
|
65
|
+
| `unknown` | Missing turn, unavailable status or uncorrelated submission |
|
|
66
|
+
| `running` | Nonterminal turn with no pending action |
|
|
67
|
+
| `blocked` | Inspect `actions` for input or approval |
|
|
68
|
+
| `completed` | Turn completed; build and deployment are separate |
|
|
69
|
+
| `failed` | Turn failed, errored or was cancelled |
|
|
70
|
+
|
|
71
|
+
`project create --wait` uses the same observer and requires a matching successful
|
|
72
|
+
build. For existing tasks use `--require-build`; stale builds cannot satisfy it.
|
|
73
|
+
`build_matches_turn` checks commit identity. `status/wait/watch` return monitoring fields by default, excluding usage and unrelated metadata.
|
|
74
|
+
Use `--full` for detailed sanitized metadata; `--compact` remains an explicit alias for the default.
|
|
75
|
+
The additive `workflow` projection supplies `state`, `task`, `next_action`,
|
|
76
|
+
`observation`, build matching and action descriptors; full card details remain in
|
|
77
|
+
`actions`. `use_existing_authorization` does not grant new permission.
|
|
78
|
+
|
|
79
|
+
Exit 0 means observation succeeded (including a blocked card), not build success.
|
|
80
|
+
Exit 2 means a wait timed out with work pending; exit 1 means failure, unknown
|
|
81
|
+
state or query failure. `query_timed_out` marks a query deadline; retained state
|
|
82
|
+
is the last complete snapshot. SIGINT/SIGTERM returns `interrupted: true` and
|
|
83
|
+
130/143 without cancelling remote work. A cancelled turn can still have a queued
|
|
84
|
+
backend operation; do not confuse it with completed cancellation or restoration.
|
|
85
|
+
|
|
86
|
+
Completed snapshots include `messages_command`. Use
|
|
87
|
+
`thread messages PROJECT_ID --turn N --text` to read Enter's delivery and validation
|
|
88
|
+
summary. Report build success, saved configuration and real-service validation
|
|
89
|
+
separately; do not replace Enter's validation with source keyword scans.
|
|
90
|
+
|
|
91
|
+
## Transport and errors
|
|
92
|
+
|
|
93
|
+
Observation defaults to HTTP polling every 2 seconds after each read. Each
|
|
94
|
+
snapshot query is bounded to 30 seconds. No WebSocket is opened by default.
|
|
95
|
+
`--transport auto` opts in to WebSocket events with HTTP fallback; 401/403 on the
|
|
96
|
+
stream stops reconnection but leaves state observation on HTTP. Raw message
|
|
97
|
+
follow still fails with STREAM_AUTH_ERROR. HTTP authentication failures stop
|
|
98
|
+
observation. WebSockets respect proxy variables and NO_PROXY.
|
|
99
|
+
|
|
100
|
+
`thread messages PROJECT_ID --follow --timeout 60 --cursor EVENT_ID` emits raw
|
|
101
|
+
NDJSON events with diagnostics on stderr. It supports `--max-events`, crosses turn
|
|
102
|
+
boundaries, and does not synthesize HTTP state when the socket is unavailable.
|
|
103
|
+
|
|
104
|
+
`--request-timeout SECONDS` covers API requests and response bodies, including
|
|
105
|
+
downloads. Monitoring always has its own deadline. `project publish --timeout`
|
|
106
|
+
validates before requests and bounds lookup, submission, observation and URL
|
|
107
|
+
verification. After uncertain publication, query `project publish-status` first.
|
|
108
|
+
Only transient observation reads retry automatically; mutations never do.
|
|
109
|
+
`error.outcome_unknown: true` means inspect actual state before retrying a write.
|
|
110
|
+
|
|
111
|
+
JSON execution errors go to stderr; observation errors accompany the last
|
|
112
|
+
snapshot on stdout. Error fields are `code/message/retryable/outcome_unknown`.
|
|
113
|
+
Keep stdout and stderr separate. JSON/YAML confirmations are structured;
|
|
114
|
+
interactive login instructions use stderr. Known credential fields and encoded
|
|
115
|
+
tool arguments are redacted in monitoring, approval, events and verbose bodies;
|
|
116
|
+
ordinary content is not arbitrarily rewritten.
|
|
117
|
+
|
|
118
|
+
## Approval cards
|
|
119
|
+
|
|
120
|
+
Feature cards call the actual enable endpoint and verify action state; subscription
|
|
121
|
+
refusal or enable failure remains an error. Plans require user approval. Questions
|
|
122
|
+
carry their original text, options and selection mode: use that text as the answer
|
|
123
|
+
key, not a host question ID. `thread approve --help` describes `selected_options`
|
|
124
|
+
and `other_text`; skip only when the user requests it.
|
|
125
|
+
|
|
126
|
+
Configuration cards expose required fields. Reuse authorized input, ask only for
|
|
127
|
+
missing values, and use stdin from a secure input or user-provided file, or Enter's
|
|
128
|
+
form. OAuth configuration is saved before credential-free approval; after form
|
|
129
|
+
submission, check status because the form may already have approved the action.
|
|
130
|
+
Provider fields, Secret/Stripe inputs and examples are in
|
|
131
|
+
[configuration guidance](skills/enter/references/configuration.md).
|
|
132
|
+
Do not echo secrets or embed them in shell text/build prompts: stdin does not
|
|
133
|
+
protect earlier chat or tool logs. User-approved placeholders remain pending
|
|
134
|
+
integrations; mock configuration does not prove real OAuth login or payment.
|
|
135
|
+
|
|
136
|
+
## Development and host installation
|
|
29
137
|
|
|
30
138
|
```sh
|
|
139
|
+
npm ci
|
|
140
|
+
npm test
|
|
31
141
|
npm run build
|
|
32
|
-
npm
|
|
142
|
+
npm pack
|
|
143
|
+
npm run install:hosts -- --package /absolute/path/enter-pro-enter-cli-0.4.4.tgz
|
|
33
144
|
```
|
|
34
145
|
|
|
35
|
-
|
|
146
|
+
Tests use loopback HTTP/WebSocket fixtures and dummy credentials, not real cloud
|
|
147
|
+
provisioning or host UI acceptance. The installer puts the same package and Skill
|
|
148
|
+
into Codex/DSH, renders the wrapper, and records its SHA-256. `--host codex|dsh`
|
|
149
|
+
selects one host; `--home` supports isolated installs. No checkout is needed at runtime.
|
|
150
|
+
|
|
151
|
+
For manual simulation, run `npm run mock:serve`, then in another terminal:
|
|
36
152
|
|
|
37
153
|
```sh
|
|
38
|
-
npm run local:cli -- whoami
|
|
39
|
-
npm run local:cli -- workspace list
|
|
40
154
|
npm run local:cli -- thread status cloud
|
|
41
155
|
npm run local:cli -- thread approve cloud cloud-action
|
|
42
156
|
npm run local:cli -- thread wait cloud --timeout 3
|
|
43
|
-
npm run local:cli -- project get cloud
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
The server binds only to `127.0.0.1:43180`. Set `MOCK_ENTER_PORT` in both terminals
|
|
47
|
-
to use another port. `local:cli` always targets loopback and injects only the
|
|
48
|
-
dummy mock key, even if the shell has real `ENTER_API_URL` or `ENTER_API_KEY`
|
|
49
|
-
values. Prefer this wrapper for manual simulation; the normal CLI still uses
|
|
50
|
-
its ordinary configuration. Stop the server with Ctrl-C. Restarting resets all
|
|
51
|
-
in-memory scenarios; no resources are written to Enter or cloud providers.
|
|
52
|
-
|
|
53
|
-
| Project ID | Scenario |
|
|
54
|
-
| --- | --- |
|
|
55
|
-
| `plan` | Plan approval (`plan-action`) with plan text |
|
|
56
|
-
| `cloud` | Supabase dedicated enable flow (`cloud-action`) |
|
|
57
|
-
| `ai` | AI capability dedicated enable flow (`ai-action`) |
|
|
58
|
-
| `secret` | Secret input (`secret-action`); use fake values only |
|
|
59
|
-
| `questions` | Structured question (`questions-action`) |
|
|
60
|
-
| `subscription` | Feature enable returns `VIP_REQUIRED` |
|
|
61
|
-
| `running` | Never completes; exercise wait timeout |
|
|
62
|
-
| `failed` | Backend turn failed |
|
|
63
|
-
| `http-error` | Backend HTTP 503 |
|
|
64
|
-
| `stalled` | HTTP request never returns; exercise request deadline |
|
|
65
|
-
|
|
66
|
-
Example input-card and timeout checks:
|
|
67
|
-
|
|
68
|
-
```sh
|
|
69
|
-
printf '%s\n' 'dummy-test-value' | npm run local:cli -- thread approve secret secret-action --secret-name LOCAL_TEST_KEY --secret-value-stdin
|
|
70
|
-
npm run local:cli -- thread approve questions questions-action --skip-answers
|
|
71
|
-
npm run local:cli -- thread wait running --timeout 1
|
|
72
|
-
npm run local:cli -- thread wait stalled --timeout 1
|
|
73
157
|
```
|
|
74
158
|
|
|
75
|
-
`
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
`
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
159
|
+
The server binds loopback port 43180 (`MOCK_ENTER_PORT` overrides it). The wrapper
|
|
160
|
+
forces loopback and a dummy key even if production environment variables exist.
|
|
161
|
+
Scenarios: `plan`, `cloud`, `ai`, `secret`, `questions`, `subscription`, `running`,
|
|
162
|
+
`failed`, `http-error`, `stalled`. Card IDs are `<scenario>-action`. Restart to reset;
|
|
163
|
+
unsupported routes fail, and request bodies/secrets are not retained. Only use
|
|
164
|
+
fake values. `.env.integration.example` lists real-test variables; its ignored
|
|
165
|
+
`.env.integration.local` counterpart is never loaded automatically.
|
|
166
|
+
|
|
167
|
+
### Agent host integration
|
|
168
|
+
|
|
169
|
+
All observers return `progress` from the selected task's HTTP message history:
|
|
170
|
+
`source: assistant_messages`, `available`, a scoped `revision`, and the latest
|
|
171
|
+
assistant text in `messages` (up to 1600 characters, marked `truncated` if clipped).
|
|
172
|
+
Reasoning and raw tool arguments are excluded. `observed_at` timestamps the last
|
|
173
|
+
successful state read; `emitted_at` timestamps watch output, including stale results.
|
|
174
|
+
A narrative read failure is separate from authoritative task/action status.
|
|
175
|
+
|
|
176
|
+
`wait` batches ordinary narrative for 10 seconds by default, keeping the latest text,
|
|
177
|
+
and returns early when that interval has elapsed and progress changed.
|
|
178
|
+
`--progress-interval <seconds>` controls batching; `0` opts into every change.
|
|
179
|
+
Actions, terminal states and errors bypass batching. The overall `--timeout` still
|
|
180
|
+
bounds the call and returns the latest observation even before the interval elapses.
|
|
181
|
+
`status` remains an immediate read; no phase is guessed from narrative keywords. `workflow.next_action=report_progress` means relay the text first, then `after_reporting=observe`. Exit 0 means an observation result, not build completion. Execute the returned `wait_command`, including
|
|
182
|
+
`--after-progress`, to wait for the next revision without replaying the same update.
|
|
183
|
+
`watch` uses the same interval for ordinary narrative/metadata updates; state changes
|
|
184
|
+
and final results are emitted immediately.
|
|
185
|
+
These are latest-progress snapshots, not a lossless replay of every message.
|
|
186
|
+
|
|
187
|
+
The host agent relays the supplied text, forwards questions, and executes the next
|
|
188
|
+
command; it need not reason over logs or infer development phases. Relay new progress
|
|
189
|
+
proactively, suppress repeated revisions/heartbeats, and answer user questions from
|
|
190
|
+
the last observation before refreshing. Background hosts with only completion notifications fall back to bounded `wait`: batched progress ends the job and triggers that notification. Merely being able to read running stdout is insufficient for automatic `watch` delivery. For background jobs, read output nonblockingly
|
|
191
|
+
or with a short bounded wait; a five-minute blocking job-output call defeats background
|
|
192
|
+
execution. The CLI cannot schedule the host agent or interrupt its tool calls.
|
|
193
|
+
|
|
194
|
+
Both `wait` and `watch` use HTTP by default. The CLI performs the polling;
|
|
195
|
+
do not generate `sleep && enter-cli` or a loop of `thread status` commands.
|
|
196
|
+
|
|
197
|
+
For hosts that return shell output only after the command exits, use `wait`.
|
|
198
|
+
For the current Helix integration, use a short 10-second observation window
|
|
199
|
+
to return control for incoming messages:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
103
202
|
enter-cli --output json thread wait PROJECT_ID --task-id TASK_ID --timeout 10
|
|
104
203
|
```
|
|
105
204
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
`
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
`
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
| --- | --- |
|
|
152
|
-
| `idle` | No turn exists yet |
|
|
153
|
-
| `queued` | The selected task is in the backend queue |
|
|
154
|
-
| `unknown` | The selected submission cannot be correlated; this is not completion |
|
|
155
|
-
| `running` | Selected turn has not terminated and has no pending actions |
|
|
156
|
-
| `blocked` | Inspect `actions` for questions or approval requirements |
|
|
157
|
-
| `completed` | Turn completed; build and deployment remain separate |
|
|
158
|
-
| `failed` | Turn failed, errored or was cancelled |
|
|
159
|
-
| `unknown` | Explicit turn missing or status unavailable |
|
|
160
|
-
|
|
161
|
-
The existing `status` and `wait` commands retain full metadata by default; use
|
|
162
|
-
`--compact` for monitoring fields only. `watch` defaults to compact snapshots
|
|
163
|
-
and accepts `--full`. `build_matches_turn` only becomes true when the build commit matches
|
|
164
|
-
the selected turn's commit; an older successful preview does not prove the new
|
|
165
|
-
turn built successfully. Known credential fields are redacted in monitoring, approval and event output,
|
|
166
|
-
including known JSON-encoded tool argument fields. Ordinary command output and
|
|
167
|
-
arbitrary JSON strings are not rewritten by this monitoring feature.
|
|
168
|
-
|
|
169
|
-
Exit 0 means a successful observation (including `blocked`), not build success.
|
|
170
|
-
Exit 2 means the observation deadline elapsed while work remained pending:
|
|
171
|
-
`wait_timed_out: true`. Exit 1 means failure, unknown state or a failed query.
|
|
172
|
-
A query timeout includes `query_timed_out: true`; retained state is only the last
|
|
173
|
-
complete snapshot. SIGINT/SIGTERM during status/wait/watch returns
|
|
174
|
-
`interrupted: true` with exit 130/143, without labelling it a timeout or cancelling
|
|
175
|
-
remote work.
|
|
176
|
-
|
|
177
|
-
For raw events rather than state snapshots:
|
|
178
|
-
|
|
179
|
-
```sh
|
|
180
|
-
enter-cli thread messages PROJECT_ID --follow --timeout 60 --cursor EVENT_ID
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
This emits NDJSON on stdout, transport diagnostics on stderr, and supports
|
|
184
|
-
`--max-events N`. It continues across turn boundaries until its deadline or limit.
|
|
185
|
-
Unlike `watch`, raw follow does not synthesize HTTP state when the socket is down.
|
|
186
|
-
|
|
187
|
-
### Skill and host responsibilities
|
|
188
|
-
|
|
189
|
-
The Skill should direct agents to submit once, follow the returned task ID, and
|
|
190
|
-
use bounded `thread watch` or `thread wait` calls. Forward questions with their
|
|
191
|
-
options and selection mode, and approve only within the user's authorization.
|
|
192
|
-
Provide secrets through Enter's secure form or direct CLI stdin.
|
|
193
|
-
|
|
194
|
-
The host remains responsible for scheduling observation and handling incoming
|
|
195
|
-
user messages concurrently. CLI events cannot wake an agent
|
|
196
|
-
whose host has stopped the task. Continue bounded watch calls while work remains
|
|
197
|
-
pending; if the host supports background tasks, schedule a watcher there. Forward
|
|
198
|
-
questions without silently selecting answers, and continue with the task ID
|
|
199
|
-
returned by approval. Do not auto-approve based only on `input_kind: none`.
|
|
200
|
-
|
|
201
|
-
### Errors and retry boundaries
|
|
202
|
-
|
|
203
|
-
`--request-timeout SECONDS` optionally bounds JSON API requests, including
|
|
204
|
-
response bodies. Without this explicit option, ordinary operations retain their
|
|
205
|
-
existing request lifetime; monitoring commands always enforce their own total
|
|
206
|
-
`--timeout`. Transient network errors and HTTP 502/503/504 retry once only for
|
|
207
|
-
thread observation reads (turns, actions, messages and queued tasks).
|
|
208
|
-
Mutations are never automatically replayed. A timed-out write can already have
|
|
209
|
-
reached Enter: `error.outcome_unknown: true` tells the caller to inspect task or
|
|
210
|
-
action state before resubmitting.
|
|
211
|
-
|
|
212
|
-
In JSON mode, command execution errors are JSON on stderr. Status/watch query
|
|
213
|
-
errors accompany their last observed snapshot on stdout. Error fields include
|
|
214
|
-
`code`, `message`, `retryable`, and `outcome_unknown`. Never infer success from a
|
|
215
|
-
missing stderr message.
|
|
216
|
-
|
|
217
|
-
`test/agent-ux.test.mjs` uses local HTTP/WebSocket fixtures to cover event-triggered
|
|
218
|
-
cards, delayed persistence, reconnect and cursor replay, queue tracking, polling
|
|
219
|
-
fallback, deadlines, safe read retries and redaction. These are protocol/CLI integration tests,
|
|
220
|
-
not production cloud-provisioning or a host UI acceptance test.
|
|
221
|
-
|
|
222
|
-
## Approval inputs
|
|
223
|
-
|
|
224
|
-
Feature enable cards use the dedicated feature endpoint and verify that the
|
|
225
|
-
selected action becomes approved. A failed enable request, subscription refusal,
|
|
226
|
-
or failed action must remain an error. Secret cards require both a name and a
|
|
227
|
-
nonempty value; question cards require `--answers` or an explicit `--skip-answers`.
|
|
228
|
-
|
|
229
|
-
OAuth cards (`supabase_configure_auth_provider`) resolve their provider from the
|
|
230
|
-
matching tool call. If the provider was already configured through Enter's secure
|
|
231
|
-
form, ordinary `thread approve PROJECT_ID ACTION_ID` verifies it and submits only
|
|
232
|
-
`auth_provider_result: {provider}`. Otherwise, `--auth-config-stdin` accepts a JSON
|
|
233
|
-
object, saves it through the auth configuration endpoint, then requests approval.
|
|
234
|
-
The backend verifies the stored configuration before accepting the action.
|
|
235
|
-
|
|
236
|
-
| Provider | Configuration fields |
|
|
237
|
-
| --- | --- |
|
|
238
|
-
| `google` | `client_ids`, `client_secret`, optional `skip_nonce_checks` |
|
|
239
|
-
| `wechat` | `client_id`, `client_secret` |
|
|
240
|
-
| `alipay` | `app_id`, `private_key` |
|
|
241
|
-
| `feishu` | `app_id`, `app_secret` |
|
|
242
|
-
|
|
243
|
-
Use a secure input mechanism to supply stdin, or complete the secure form in
|
|
244
|
-
Enter. Do not request credentials in ordinary agent chat or place them in process
|
|
245
|
-
arguments. Verbose request logging redacts credential fields. Approval payloads
|
|
246
|
-
contain no OAuth credentials. The host needs a secure input path or a link to Enter's
|
|
247
|
-
form to make these cards usable from its UI.
|
|
248
|
-
|
|
249
|
-
`test/auth-provider-approve.test.mjs` exercises all four provider contracts against
|
|
250
|
-
local HTTP fixtures, including save failures, backend verification failures,
|
|
251
|
-
incorrect provider binding, malformed inputs, and verbose output redaction.
|
|
252
|
-
These tests do not validate a live OAuth login or cloud provisioning.
|
|
205
|
+
The CLI and Helix guidance both default to 10 seconds. A shell deadline alone
|
|
206
|
+
does not guarantee responsiveness. On exit 2, handle incoming messages, report
|
|
207
|
+
the returned Enter stage/status and execute the returned `wait_command` to keep observing.
|
|
208
|
+
It preserves the task or pinned turn, chat, build requirement and observation
|
|
209
|
+
timeout. Do not ask the user whether to continue, recreate the project or
|
|
210
|
+
resubmit the build. A timeout ends observation, not the remote task. On
|
|
211
|
+
`blocked`, forward the action and follow the continuation after answering;
|
|
212
|
+
on `completed` or `failed`, consume the result.
|
|
213
|
+
|
|
214
|
+
For hosts that notify the agent when incremental stdout arrives without blocking user interaction,
|
|
215
|
+
use `watch_command` (60 seconds by default). Only explicitly select `--timeout 0`
|
|
216
|
+
when the host can manage an unlimited background observer.
|
|
217
|
+
`snapshot` contains actual Enter task/turn state, `heartbeat` confirms a
|
|
218
|
+
successful observation without a state change, and `result` ends observation.
|
|
219
|
+
This is stage/status progress, not generated percentages or a raw model-token
|
|
220
|
+
log. Each snapshot query has a 30-second deadline. SIGINT/SIGTERM stop only
|
|
221
|
+
the observer (130/143); the host owns background execution and reconnection.
|
|
222
|
+
|
|
223
|
+
The current Helix sandbox adapter buffers stdout until command completion;
|
|
224
|
+
therefore an indefinitely running `watch` is not its default integration path.
|
|
225
|
+
Helix colleagues only need to update the actual sandbox CLI and the agent's
|
|
226
|
+
usage instructions for the bounded `wait` path, then verify it in a fresh
|
|
227
|
+
sandbox. Continuous in-flight display requires host-side incremental output
|
|
228
|
+
support; local CLI tests do not prove that integration.
|
|
229
|
+
|
|
230
|
+
State and pending-action reads run concurrently to avoid spending the observation
|
|
231
|
+
budget on sequential independent requests. Failed turns do not wait for pending
|
|
232
|
+
action reads. `QUERY_TIMEOUT` before the first snapshot means the observation
|
|
233
|
+
deadline expired without a known task state; it does not locate the cause in the
|
|
234
|
+
network or server. The overall deadline remains bounded. Usage changes are not
|
|
235
|
+
progress; continue quietly when there is no new narrative or actionable state.
|
|
236
|
+
|
|
237
|
+
### One background observer
|
|
238
|
+
|
|
239
|
+
Use `enter-cli thread wait PROJECT_ID --task-id TASK_ID --stream --timeout 0`
|
|
240
|
+
with a host that supports managed background execution and incremental output delivery.
|
|
241
|
+
The CLI polls HTTP internally and emits batched progress until an action, terminal state,
|
|
242
|
+
interruption or explicit deadline. Ordinary progress does not end this process.
|
|
243
|
+
Consume events without blocking user interaction; do not restart it on report_progress.
|
|
244
|
+
The returned `stream_argv` / `stream_command` starts this observer.
|
|
245
|
+
`watch` remains a compatible streaming entry point.
|
|
246
|
+
|
|
247
|
+
Without `--stream`, wait retains its bounded single-JSON contract for buffered hosts.
|
|
248
|
+
This fallback can require repeated calls; CLI cannot enable host background scheduling.
|
|
249
|
+
Do not detach with shell `&` or assume buffered Bash output is delivered incrementally.
|
package/dist/auth.d.ts
CHANGED
|
@@ -11,3 +11,6 @@ export declare function loadCredentials(): Credentials | null;
|
|
|
11
11
|
export declare function clearCredentials(): void;
|
|
12
12
|
export declare function getToken(): string;
|
|
13
13
|
export declare function isAuthenticated(): boolean;
|
|
14
|
+
export declare const OAUTH_TOKEN_URL = "https://auth.converge.ai/oauth/token";
|
|
15
|
+
export declare const OAUTH_CLIENT_ID = "anCisSaaIA36fTZ2DUMiTMro3bYuptrf";
|
|
16
|
+
export declare function getValidToken(signal?: AbortSignal): Promise<string>;
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, unlinkSync, mkdirSync } from "fs";
|
|
1
|
+
import { readFileSync, writeFileSync, unlinkSync, mkdirSync, renameSync, rmdirSync } from "fs";
|
|
2
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
2
4
|
import { join } from "path";
|
|
3
5
|
import { baseURL, configDir } from "./config.js";
|
|
4
6
|
const CREDENTIALS_FILE = "credentials.json";
|
|
@@ -7,9 +9,17 @@ function credentialsPath() {
|
|
|
7
9
|
}
|
|
8
10
|
export function saveCredentials(creds) {
|
|
9
11
|
mkdirSync(configDir(), { recursive: true, mode: 0o700 });
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
const temporary = `${credentialsPath()}.${randomUUID()}.tmp`;
|
|
13
|
+
try {
|
|
14
|
+
writeFileSync(temporary, JSON.stringify(creds, null, 2), { mode: 0o600 });
|
|
15
|
+
renameSync(temporary, credentialsPath());
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
try {
|
|
19
|
+
unlinkSync(temporary);
|
|
20
|
+
}
|
|
21
|
+
catch { /* already renamed */ }
|
|
22
|
+
}
|
|
13
23
|
}
|
|
14
24
|
// Validate the newly issued token, not an older saved token or ENTER_API_KEY.
|
|
15
25
|
// Do not persist it or claim login success until the configured API accepts it.
|
|
@@ -41,8 +51,9 @@ export function clearCredentials() {
|
|
|
41
51
|
try {
|
|
42
52
|
unlinkSync(credentialsPath());
|
|
43
53
|
}
|
|
44
|
-
catch {
|
|
45
|
-
|
|
54
|
+
catch (error) {
|
|
55
|
+
if (error.code !== "ENOENT")
|
|
56
|
+
throw error;
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
export function getToken() {
|
|
@@ -55,3 +66,100 @@ export function getToken() {
|
|
|
55
66
|
export function isAuthenticated() {
|
|
56
67
|
return getToken() !== "";
|
|
57
68
|
}
|
|
69
|
+
// Same OAuth client as interactive login. Environment keys (including Work's
|
|
70
|
+
// transparent placeholder) must never fall back to a personal OAuth identity.
|
|
71
|
+
export const OAUTH_TOKEN_URL = "https://auth.converge.ai/oauth/token";
|
|
72
|
+
export const OAUTH_CLIENT_ID = "anCisSaaIA36fTZ2DUMiTMro3bYuptrf";
|
|
73
|
+
let refreshing;
|
|
74
|
+
function needsRefresh(creds) {
|
|
75
|
+
return !!creds.expires_at && Date.parse(creds.expires_at) <= Date.now() + 60000;
|
|
76
|
+
}
|
|
77
|
+
async function refreshCredentials(signal) {
|
|
78
|
+
const lock = join(configDir(), "credentials-refresh.lock");
|
|
79
|
+
const deadline = Date.now() + 20000;
|
|
80
|
+
// Serialize separate CLI processes too: rotating refresh tokens are single use.
|
|
81
|
+
while (true) {
|
|
82
|
+
signal?.throwIfAborted();
|
|
83
|
+
try {
|
|
84
|
+
mkdirSync(lock, { mode: 0o700 });
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
if (error.code !== "EEXIST")
|
|
89
|
+
throw error;
|
|
90
|
+
if (Date.now() >= deadline)
|
|
91
|
+
throw new Error("Another CLI is refreshing login. Retry after it finishes; if it crashed, remove ~/.enter/credentials-refresh.lock.");
|
|
92
|
+
await delay(100, undefined, { signal });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try {
|
|
96
|
+
const creds = loadCredentials();
|
|
97
|
+
if (!creds?.access_token)
|
|
98
|
+
return "";
|
|
99
|
+
if (!needsRefresh(creds))
|
|
100
|
+
return creds.access_token;
|
|
101
|
+
if (!creds.refresh_token)
|
|
102
|
+
throw new Error("Login expired. Run `enter-cli login` to sign in again.");
|
|
103
|
+
let response;
|
|
104
|
+
try {
|
|
105
|
+
response = await fetch(OAUTH_TOKEN_URL, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify({ grant_type: "refresh_token", client_id: OAUTH_CLIENT_ID, refresh_token: creds.refresh_token }),
|
|
109
|
+
signal: AbortSignal.timeout(15000),
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw new Error("Could not refresh login. Check connectivity and retry; saved credentials were preserved.");
|
|
114
|
+
}
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
if ([400, 401, 403].includes(response.status))
|
|
117
|
+
throw new Error("Login renewal was rejected. Run `enter-cli login` to sign in again.");
|
|
118
|
+
throw new Error(`Login renewal failed (HTTP ${response.status}). Retry later.`);
|
|
119
|
+
}
|
|
120
|
+
let tokens;
|
|
121
|
+
try {
|
|
122
|
+
tokens = await response.json();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
throw new Error("Login renewal returned an invalid response.");
|
|
126
|
+
}
|
|
127
|
+
if (typeof tokens?.access_token !== "string" || !tokens.access_token ||
|
|
128
|
+
typeof tokens.expires_in !== "number" || !Number.isFinite(tokens.expires_in) || tokens.expires_in <= 0 ||
|
|
129
|
+
(tokens.refresh_token !== undefined && (typeof tokens.refresh_token !== "string" || !tokens.refresh_token))) {
|
|
130
|
+
throw new Error("Login renewal returned an invalid response.");
|
|
131
|
+
}
|
|
132
|
+
// Persist rotation immediately; an API outage after renewal must not discard
|
|
133
|
+
// the new refresh token and strand the next invocation with the consumed one.
|
|
134
|
+
saveCredentials({
|
|
135
|
+
...creds, access_token: tokens.access_token,
|
|
136
|
+
refresh_token: tokens.refresh_token ?? creds.refresh_token,
|
|
137
|
+
token_type: typeof tokens.token_type === "string" ? tokens.token_type : creds.token_type,
|
|
138
|
+
expires_at: new Date(Date.now() + tokens.expires_in * 1000).toISOString(),
|
|
139
|
+
});
|
|
140
|
+
return tokens.access_token;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
rmdirSync(lock);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export async function getValidToken(signal) {
|
|
147
|
+
signal?.throwIfAborted();
|
|
148
|
+
if (process.env.ENTER_API_KEY)
|
|
149
|
+
return process.env.ENTER_API_KEY;
|
|
150
|
+
const creds = loadCredentials();
|
|
151
|
+
if (!creds?.access_token)
|
|
152
|
+
return "";
|
|
153
|
+
if (!needsRefresh(creds))
|
|
154
|
+
return creds.access_token;
|
|
155
|
+
// Cancelable callers use the same filesystem lock without canceling another
|
|
156
|
+
// caller's renewal. Once sent, renewal must finish and persist token rotation.
|
|
157
|
+
if (signal) {
|
|
158
|
+
const token = await refreshCredentials(signal);
|
|
159
|
+
signal.throwIfAborted();
|
|
160
|
+
return token;
|
|
161
|
+
}
|
|
162
|
+
if (!refreshing)
|
|
163
|
+
refreshing = refreshCredentials().finally(() => { refreshing = undefined; });
|
|
164
|
+
return refreshing;
|
|
165
|
+
}
|