@enter-pro/enter-cli 0.4.2 → 0.4.3
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 +142 -227
- 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 +74 -41
- package/dist/commands/thread.d.ts +27 -0
- package/dist/commands/thread.js +110 -42
- 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 +12 -2
- package/dist/workflow.d.ts +44 -0
- package/dist/workflow.js +34 -0
- package/package.json +7 -3
- package/scripts/install-hosts.mjs +29 -0
- package/skills/enter/SKILL.md +36 -0
- package/skills/enter/references/configuration.md +19 -0
package/README.md
CHANGED
|
@@ -1,252 +1,167 @@
|
|
|
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.
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Tests start HTTP fixture servers on loopback and execute the built CLI in child
|
|
22
|
-
processes with a dummy API key. They do not start Work, connect to Enter, create
|
|
23
|
-
cloud resources, or prove end-to-end Work behavior.
|
|
24
|
-
|
|
25
|
-
### Interactive local simulator
|
|
26
|
-
|
|
27
|
-
Requires Node 18+ and npm; no Docker, Work server, database, or credentials are
|
|
28
|
-
needed. From this repository, build once and start the fixture server:
|
|
29
|
-
|
|
30
|
-
```sh
|
|
31
|
-
npm run build
|
|
32
|
-
npm run mock:serve
|
|
33
|
-
```
|
|
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.
|
|
34
27
|
|
|
35
|
-
|
|
28
|
+
## Submit, observe and deliver
|
|
36
29
|
|
|
37
30
|
```sh
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
npm run local:cli -- thread wait cloud --timeout 3
|
|
43
|
-
npm run local:cli -- project get cloud
|
|
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 watch PROJECT_ID --task-id TASK_ID --timeout 60
|
|
44
35
|
```
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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.
|
|
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.
|
|
52
40
|
|
|
53
|
-
|
|
|
41
|
+
| Command | Output and default lifetime |
|
|
54
42
|
| --- | --- |
|
|
55
|
-
| `
|
|
56
|
-
| `
|
|
57
|
-
| `
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
npm run local:cli -- thread wait stalled --timeout 1
|
|
73
|
-
```
|
|
43
|
+
| `status` | One JSON/YAML snapshot, 30-second deadline |
|
|
44
|
+
| `wait` | One result on a card, terminal state or timeout; 10 seconds |
|
|
45
|
+
| `watch` | Changed snapshots as NDJSON, then `type: result`; 60 seconds |
|
|
46
|
+
|
|
47
|
+
Use one observer per task. In interactive agents, use the returned short wait,
|
|
48
|
+
handle incoming user messages, then continue the same task. Normal observation
|
|
49
|
+
timeout is not a reason to increase the wait or resubmit. Background watch is
|
|
50
|
+
appropriate only with completion notifications and non-blocking output collection;
|
|
51
|
+
do not immediately block on the job for minutes. CLI lifetime and host job-wait
|
|
52
|
+
limits are separate upper bounds, not measured elapsed time. Hosts own message
|
|
53
|
+
scheduling: CLI events cannot make a blocked host process new user input.
|
|
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.
|
|
74
60
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
or
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
variables. Copy it to ignored `.env.integration.local` and configure a dedicated
|
|
85
|
-
test backend only when ready. The file is not loaded automatically; neither the
|
|
86
|
-
simulator nor the mock CLI wrapper reads it.
|
|
87
|
-
|
|
88
|
-
## Agent integration: CLI + Skill
|
|
89
|
-
|
|
90
|
-
The CLI executes Enter operations and streams structured state. The Skill guides
|
|
91
|
-
agents on command selection, following tasks, presenting questions and handling
|
|
92
|
-
approved actions. This works across agent hosts without host-specific hooks.
|
|
93
|
-
Node 18+ is required. Authenticate with `enter-cli login`, or let the host inject
|
|
94
|
-
`ENTER_API_KEY` through its secret environment mechanism.
|
|
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 |
|
|
95
70
|
|
|
96
|
-
|
|
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` retain full metadata;
|
|
74
|
+
`--compact` selects monitoring fields. `watch` is compact unless `--full` is set.
|
|
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 uses authenticated WebSocket events, reconnects with `last_event_id`,
|
|
94
|
+
deduplicates replay, and re-reads authoritative HTTP state. Healthy streams have a
|
|
95
|
+
30-second safety refresh; unavailable streams fall back to 2-second HTTP checks.
|
|
96
|
+
Text/argument deltas do not trigger full queries; card persistence gets a short
|
|
97
|
+
reconciliation window. `--transport poll` forces HTTP, `--cursor` resumes events,
|
|
98
|
+
and `transport/transport_reason/cursor` identify progress. Stream 401/403 fails explicitly;
|
|
99
|
+
unsupported endpoints fall back without repeated reconnects. WebSockets respect
|
|
100
|
+
proxy variables and `NO_PROXY`; `NODE_USE_ENV_PROXY=0` disables their proxy use.
|
|
101
|
+
|
|
102
|
+
`thread messages PROJECT_ID --follow --timeout 60 --cursor EVENT_ID` emits raw
|
|
103
|
+
NDJSON events with diagnostics on stderr. It supports `--max-events`, crosses turn
|
|
104
|
+
boundaries, and does not synthesize HTTP state when the socket is unavailable.
|
|
105
|
+
|
|
106
|
+
`--request-timeout SECONDS` covers API requests and response bodies, including
|
|
107
|
+
downloads. Monitoring always has its own deadline. `project publish --timeout`
|
|
108
|
+
validates before requests and bounds lookup, submission, observation and URL
|
|
109
|
+
verification. After uncertain publication, query `project publish-status` first.
|
|
110
|
+
Only transient observation reads retry automatically; mutations never do.
|
|
111
|
+
`error.outcome_unknown: true` means inspect actual state before retrying a write.
|
|
112
|
+
|
|
113
|
+
JSON execution errors go to stderr; observation errors accompany the last
|
|
114
|
+
snapshot on stdout. Error fields are `code/message/retryable/outcome_unknown`.
|
|
115
|
+
Keep stdout and stderr separate. JSON/YAML confirmations are structured;
|
|
116
|
+
interactive login instructions use stderr. Known credential fields and encoded
|
|
117
|
+
tool arguments are redacted in monitoring, approval, events and verbose bodies;
|
|
118
|
+
ordinary content is not arbitrarily rewritten.
|
|
119
|
+
|
|
120
|
+
## Approval cards
|
|
121
|
+
|
|
122
|
+
Feature cards call the actual enable endpoint and verify action state; subscription
|
|
123
|
+
refusal or enable failure remains an error. Plans require user approval. Questions
|
|
124
|
+
carry their original text, options and selection mode: use that text as the answer
|
|
125
|
+
key, not a host question ID. `thread approve --help` describes `selected_options`
|
|
126
|
+
and `other_text`; skip only when the user requests it.
|
|
127
|
+
|
|
128
|
+
Configuration cards expose required fields. Reuse authorized input, ask only for
|
|
129
|
+
missing values, and use stdin from a secure input or user-provided file, or Enter's
|
|
130
|
+
form. OAuth configuration is saved before credential-free approval; after form
|
|
131
|
+
submission, check status because the form may already have approved the action.
|
|
132
|
+
Provider fields, Secret/Stripe inputs and examples are in
|
|
133
|
+
[configuration guidance](skills/enter/references/configuration.md).
|
|
134
|
+
Do not echo secrets or embed them in shell text/build prompts: stdin does not
|
|
135
|
+
protect earlier chat or tool logs. User-approved placeholders remain pending
|
|
136
|
+
integrations; mock configuration does not prove real OAuth login or payment.
|
|
137
|
+
|
|
138
|
+
## Development and host installation
|
|
97
139
|
|
|
98
140
|
```sh
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
141
|
+
npm ci
|
|
142
|
+
npm test
|
|
143
|
+
npm run build
|
|
144
|
+
npm pack
|
|
145
|
+
npm run install:hosts -- --package /absolute/path/enter-pro-enter-cli-0.4.3.tgz
|
|
104
146
|
```
|
|
105
147
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
- `status` reads one snapshot, with a 30-second total deadline by default.
|
|
112
|
-
- `wait` waits for a pending card or terminal state and emits one JSON result.
|
|
113
|
-
Its default lifetime is 10 seconds.
|
|
114
|
-
- `watch` emits NDJSON snapshots only when meaningful state changes, followed
|
|
115
|
-
by a final `type: result` record. Its default lifetime is 60 seconds.
|
|
116
|
-
|
|
117
|
-
Waiting uses authenticated WebSocket events to trigger authoritative HTTP state
|
|
118
|
-
reads. It reconnects with `last_event_id`, deduplicates replay, and uses HTTP
|
|
119
|
-
polling if the stream is unavailable. Healthy streams have a 30-second safety
|
|
120
|
-
refresh; fallback checks are every 2 seconds plus reconnect transitions. Text
|
|
121
|
-
and argument deltas do not cause repeated full snapshot queries. After a card
|
|
122
|
-
event, a short reconciliation window accounts for delayed card persistence.
|
|
123
|
-
Use `--transport poll` to force HTTP-only operation; `--cursor EVENT_ID` resumes
|
|
124
|
-
a prior stream. Returned `transport` and `cursor` make the active mode visible.
|
|
125
|
-
`transport_reason` distinguishes temporary connection failure, unavailable
|
|
126
|
-
streams, and invalid events. Unsupported endpoints stop reconnecting for the
|
|
127
|
-
current call and use HTTP polling. HTTP 401/403 on the stream returns
|
|
128
|
-
`STREAM_AUTH_ERROR` instead of silently reconnecting. WebSockets honor proxy environment variables and `NO_PROXY`; set
|
|
129
|
-
`NODE_USE_ENV_PROXY=0` to disable proxy use for the stream.
|
|
130
|
-
|
|
131
|
-
Use `--task-id` for submissions and continuation receipts. It follows the exact
|
|
132
|
-
task through queueing, even if an older turn already completed. `--turn N` selects
|
|
133
|
-
a fixed turn instead; the two selectors are mutually exclusive. With neither,
|
|
134
|
-
waiting pins the first observed turn. `--chat-id` scopes chat submission, lookup,
|
|
135
|
-
and continuation commands.
|
|
136
|
-
|
|
137
|
-
Enter can interject a mid-turn message into the active turn and return an external
|
|
138
|
-
message ID instead of a new task ID. The current API does not map that receipt to
|
|
139
|
-
its owning turn. In that case `status --task-id` returns `unknown`, with separate
|
|
140
|
-
`project_activity` and a `project_status_command`; that activity never claims the
|
|
141
|
-
submission succeeded. Inspect the echoed message and resulting change. Do not
|
|
142
|
-
resubmit blindly or infer correlation from the newest turn.
|
|
143
|
-
|
|
144
|
-
`project create` returns continuation commands. `project create --wait` uses the
|
|
145
|
-
same bounded event monitor, yields on cards, and requires a matching successful
|
|
146
|
-
build. `thread wait/status/watch --require-build` enables that build check for an
|
|
147
|
-
existing project; stale successful builds cannot satisfy it. A matching failed
|
|
148
|
-
build returns failure, and timeout preserves progress without cancelling Enter.
|
|
148
|
+
Tests use loopback HTTP/WebSocket fixtures and dummy credentials, not real cloud
|
|
149
|
+
provisioning or host UI acceptance. The installer puts the same package and Skill
|
|
150
|
+
into Codex/DSH, renders the wrapper, and records its SHA-256. `--host codex|dsh`
|
|
151
|
+
selects one host; `--home` supports isolated installs. No checkout is needed at runtime.
|
|
149
152
|
|
|
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:
|
|
153
|
+
For manual simulation, run `npm run mock:serve`, then in another terminal:
|
|
178
154
|
|
|
179
155
|
```sh
|
|
180
|
-
|
|
156
|
+
npm run local:cli -- thread status cloud
|
|
157
|
+
npm run local:cli -- thread approve cloud cloud-action
|
|
158
|
+
npm run local:cli -- thread wait cloud --timeout 3
|
|
181
159
|
```
|
|
182
160
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
161
|
+
The server binds loopback port 43180 (`MOCK_ENTER_PORT` overrides it). The wrapper
|
|
162
|
+
forces loopback and a dummy key even if production environment variables exist.
|
|
163
|
+
Scenarios: `plan`, `cloud`, `ai`, `secret`, `questions`, `subscription`, `running`,
|
|
164
|
+
`failed`, `http-error`, `stalled`. Card IDs are `<scenario>-action`. Restart to reset;
|
|
165
|
+
unsupported routes fail, and request bodies/secrets are not retained. Only use
|
|
166
|
+
fake values. `.env.integration.example` lists real-test variables; its ignored
|
|
167
|
+
`.env.integration.local` counterpart is never loaded automatically.
|
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
|
+
}
|
package/dist/client.d.ts
CHANGED
|
@@ -3,10 +3,7 @@ export declare function setVerbose(v: boolean): void;
|
|
|
3
3
|
export declare function get(path: string, params?: Record<string, string>, signal?: AbortSignal): Promise<unknown>;
|
|
4
4
|
export declare function post(path: string, body?: unknown, signal?: AbortSignal): Promise<unknown>;
|
|
5
5
|
export declare function del(path: string): Promise<unknown>;
|
|
6
|
-
export declare function put(path: string, body?: unknown): Promise<unknown>;
|
|
7
6
|
export declare function patch(path: string, body?: unknown): Promise<unknown>;
|
|
8
7
|
export declare function workGet(path: string, params?: Record<string, string>): Promise<unknown>;
|
|
9
8
|
export declare function workPost(path: string, body?: unknown): Promise<unknown>;
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function workDel(path: string): Promise<unknown>;
|
|
12
|
-
export declare function getRaw(path: string): Promise<Response>;
|
|
9
|
+
export declare function getRaw(path: string): Promise<ArrayBuffer>;
|