@facilio/cli 0.3.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/LICENSE +21 -0
- package/README.md +120 -0
- package/dist/commands/login.js +75 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.js +41 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/whoami.js +26 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/core/api.js +158 -0
- package/dist/core/api.js.map +1 -0
- package/dist/core/constants.js +53 -0
- package/dist/core/constants.js.map +1 -0
- package/dist/core/credentials.js +209 -0
- package/dist/core/credentials.js.map +1 -0
- package/dist/core/identity.js +162 -0
- package/dist/core/identity.js.map +1 -0
- package/dist/core/logger.js +9 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/prompt.js +29 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/regions.js +45 -0
- package/dist/core/regions.js.map +1 -0
- package/dist/core/secretStore.js +251 -0
- package/dist/core/secretStore.js.map +1 -0
- package/dist/core/wrap.js +17 -0
- package/dist/core/wrap.js.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/products/connections/client.js +125 -0
- package/dist/products/connections/client.js.map +1 -0
- package/dist/products/connections/commands.js +324 -0
- package/dist/products/connections/commands.js.map +1 -0
- package/dist/products/connections/index.js +63 -0
- package/dist/products/connections/index.js.map +1 -0
- package/dist/products/connections/state.js +44 -0
- package/dist/products/connections/state.js.map +1 -0
- package/dist/products/types.js +2 -0
- package/dist/products/types.js.map +1 -0
- package/dist/products/vibe/app.js +97 -0
- package/dist/products/vibe/app.js.map +1 -0
- package/dist/products/vibe/config.js +57 -0
- package/dist/products/vibe/config.js.map +1 -0
- package/dist/products/vibe/db.js +155 -0
- package/dist/products/vibe/db.js.map +1 -0
- package/dist/products/vibe/deploy.js +92 -0
- package/dist/products/vibe/deploy.js.map +1 -0
- package/dist/products/vibe/function.js +240 -0
- package/dist/products/vibe/function.js.map +1 -0
- package/dist/products/vibe/index.js +112 -0
- package/dist/products/vibe/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { closeSync, openSync, statSync, unlinkSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { request } from 'undici';
|
|
4
|
+
import { env, vibeServerEnv } from './constants.js';
|
|
5
|
+
import { setSecret, getSecret, deleteSecret, getLegacySecret, deleteLegacySecret, fallbackFilePath, stateDir, } from './secretStore.js';
|
|
6
|
+
/**
|
|
7
|
+
* Persist credentials to the OS secret store (Keychain / Secret Service /
|
|
8
|
+
* Credential Manager-DPAPI) — or to ~/.facilio/credentials.json (mode 0600) as
|
|
9
|
+
* a fallback when no OS store is available. Returns a human-readable label of
|
|
10
|
+
* where the secret actually landed, so callers can show "saved to macOS
|
|
11
|
+
* Keychain" instead of guessing.
|
|
12
|
+
*/
|
|
13
|
+
export async function saveCredentials(creds) {
|
|
14
|
+
// Single-line: macOS `security -w` switches to hex output if the password
|
|
15
|
+
// contains control chars (e.g. \n from pretty-printing), which makes
|
|
16
|
+
// `security ... -w | jq` inscrutable. The file fallback isn't really meant
|
|
17
|
+
// for hand-editing either, so compact JSON everywhere.
|
|
18
|
+
return setSecret(JSON.stringify(creds));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve credentials, preferring the `FACILIO_API_KEY` env var (legacy
|
|
22
|
+
* `VIBE_API_KEY` still honoured) over the on-disk store. The env-var path is
|
|
23
|
+
* the zero-config agent flow: `export FACILIO_API_KEY=…` then any command
|
|
24
|
+
* works with no prior `facilio login`. Env wins over the store so CI jobs
|
|
25
|
+
* don't accidentally inherit a developer's interactive session.
|
|
26
|
+
*
|
|
27
|
+
* A session saved by the pre-rename vibe CLI (old keychain entry / ~/.vibe
|
|
28
|
+
* file) is migrated into the new store on first read, then the legacy entry
|
|
29
|
+
* is removed so the two locations can't diverge.
|
|
30
|
+
*/
|
|
31
|
+
export async function loadCredentials() {
|
|
32
|
+
const envKey = env('API_KEY');
|
|
33
|
+
if (envKey) {
|
|
34
|
+
// Region (default US) drives the vibe/MCP URLs via the region table; an
|
|
35
|
+
// explicit FACILIO_SERVER_URL still pins it. Empty serverUrl → resolve from
|
|
36
|
+
// region, matching device-flow / `login --api-key` sessions.
|
|
37
|
+
return {
|
|
38
|
+
apiKey: envKey,
|
|
39
|
+
serverUrl: vibeServerEnv() || '',
|
|
40
|
+
region: (env('REGION') || 'US').trim().toUpperCase(),
|
|
41
|
+
source: 'env',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
let raw = getSecret();
|
|
45
|
+
if (!raw) {
|
|
46
|
+
raw = getLegacySecret();
|
|
47
|
+
if (!raw)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
const migrated = JSON.parse(raw);
|
|
51
|
+
await saveCredentials(migrated);
|
|
52
|
+
deleteLegacySecret();
|
|
53
|
+
return migrated;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export async function clearCredentials() {
|
|
67
|
+
deleteSecret();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Thrown when refresh is impossible — no refresh token on file, or the
|
|
71
|
+
* refresh_token grant was definitively rejected (4xx from token endpoint).
|
|
72
|
+
* In both cases the right user action is `facilio login`, so the message is
|
|
73
|
+
* already phrased that way.
|
|
74
|
+
*/
|
|
75
|
+
export class SessionExpiredError extends Error {
|
|
76
|
+
constructor(message) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.name = 'SessionExpiredError';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
const LOCK_STALE_MS = 30_000;
|
|
83
|
+
const LOCK_WAIT_MS = 30_000;
|
|
84
|
+
/**
|
|
85
|
+
* Cross-process single-flight guard around token refresh. Identity-service
|
|
86
|
+
* rotates the refresh token on every exchange, so two concurrent `facilio`
|
|
87
|
+
* invocations refreshing the same grant would invalidate each other — the
|
|
88
|
+
* loser's (now-burned) refresh token 4xxes and nukes the session. The lock is
|
|
89
|
+
* a `wx`-created file in ~/.facilio; a crashed holder is broken after 30s.
|
|
90
|
+
*/
|
|
91
|
+
async function withRefreshLock(fn) {
|
|
92
|
+
const lockPath = path.join(stateDir(), 'refresh.lock');
|
|
93
|
+
const start = Date.now();
|
|
94
|
+
while (true) {
|
|
95
|
+
let fd;
|
|
96
|
+
try {
|
|
97
|
+
fd = openSync(lockPath, 'wx');
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
if (err.code !== 'EEXIST')
|
|
101
|
+
throw err;
|
|
102
|
+
try {
|
|
103
|
+
if (Date.now() - statSync(lockPath).mtimeMs > LOCK_STALE_MS) {
|
|
104
|
+
unlinkSync(lockPath);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
continue; // lock vanished between stat/unlink — retry immediately
|
|
110
|
+
}
|
|
111
|
+
if (Date.now() - start > LOCK_WAIT_MS) {
|
|
112
|
+
throw new Error('Timed out waiting for another facilio process to finish refreshing the session.');
|
|
113
|
+
}
|
|
114
|
+
await sleep(250);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
return await fn();
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
closeSync(fd);
|
|
122
|
+
try {
|
|
123
|
+
unlinkSync(lockPath);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* best effort */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Exchange `refresh_token` for a fresh access_token at the home-DC token
|
|
133
|
+
* endpoint (RFC 6749 §6). On success, persists the rotated tokens via
|
|
134
|
+
* `saveCredentials` and returns the updated record.
|
|
135
|
+
*
|
|
136
|
+
* Runs under a cross-process lock; after acquiring it, re-reads the store
|
|
137
|
+
* first — if a concurrent invocation already refreshed, its rotated tokens
|
|
138
|
+
* are adopted instead of burning our stale refresh token.
|
|
139
|
+
*
|
|
140
|
+
* Failure handling distinguishes definitive from transient:
|
|
141
|
+
* - 4xx (invalid_grant / refresh token revoked or expired): clears local
|
|
142
|
+
* credentials and throws SessionExpiredError, so the next command starts
|
|
143
|
+
* clean and the user is told to re-run `facilio login`.
|
|
144
|
+
* - Network errors / 5xx: leaves credentials in place and throws a plain
|
|
145
|
+
* Error, so a transient outage doesn't force the user to redo browser
|
|
146
|
+
* sign-in.
|
|
147
|
+
*/
|
|
148
|
+
export async function refreshCredentials(creds) {
|
|
149
|
+
if (!creds.refreshToken || !creds.tokenUrl || !creds.clientId) {
|
|
150
|
+
throw new SessionExpiredError('No refresh token on file. Run `facilio login` to sign in again.');
|
|
151
|
+
}
|
|
152
|
+
return withRefreshLock(async () => {
|
|
153
|
+
// Another process may have rotated the tokens while we waited.
|
|
154
|
+
const stored = await loadCredentials();
|
|
155
|
+
if (stored?.accessToken &&
|
|
156
|
+
stored.accessToken !== creds.accessToken &&
|
|
157
|
+
(stored.expiresAt ?? 0) > Date.now()) {
|
|
158
|
+
return stored;
|
|
159
|
+
}
|
|
160
|
+
const effective = stored?.refreshToken ? stored : creds;
|
|
161
|
+
return doRefresh(effective);
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
async function doRefresh(creds) {
|
|
165
|
+
let res;
|
|
166
|
+
try {
|
|
167
|
+
res = await request(creds.tokenUrl, {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: {
|
|
170
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
171
|
+
accept: 'application/json',
|
|
172
|
+
},
|
|
173
|
+
body: new URLSearchParams({
|
|
174
|
+
grant_type: 'refresh_token',
|
|
175
|
+
refresh_token: creds.refreshToken,
|
|
176
|
+
client_id: creds.clientId,
|
|
177
|
+
}).toString(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
182
|
+
throw new Error(`Could not reach token endpoint at ${creds.tokenUrl}: ${msg}`);
|
|
183
|
+
}
|
|
184
|
+
const parsed = (await res.body.json().catch(() => ({})));
|
|
185
|
+
if (res.statusCode === 200 && parsed.access_token) {
|
|
186
|
+
const updated = {
|
|
187
|
+
...creds,
|
|
188
|
+
accessToken: parsed.access_token,
|
|
189
|
+
// Identity-service rotates the refresh token on every exchange; fall
|
|
190
|
+
// back to the prior value if a deployment ever stops doing that.
|
|
191
|
+
refreshToken: parsed.refresh_token ?? creds.refreshToken,
|
|
192
|
+
expiresAt: Date.now() + (parsed.expires_in ?? 3600) * 1000,
|
|
193
|
+
};
|
|
194
|
+
await saveCredentials(updated);
|
|
195
|
+
return updated;
|
|
196
|
+
}
|
|
197
|
+
if (res.statusCode >= 400 && res.statusCode < 500) {
|
|
198
|
+
await clearCredentials();
|
|
199
|
+
const detail = parsed.error_description || parsed.error || `HTTP ${res.statusCode}`;
|
|
200
|
+
throw new SessionExpiredError(`Session expired (${detail}). Run \`facilio login\` to sign in again.`);
|
|
201
|
+
}
|
|
202
|
+
const detail = parsed.error_description || parsed.error || `HTTP ${res.statusCode}`;
|
|
203
|
+
throw new Error(`Token endpoint returned ${res.statusCode}: ${detail}`);
|
|
204
|
+
}
|
|
205
|
+
/** Path of the file-fallback location (only used if the OS keychain is unavailable). */
|
|
206
|
+
export function credentialsPath() {
|
|
207
|
+
return fallbackFilePath();
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../../src/core/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity-service auth flows shared by every product namespace:
|
|
3
|
+
* RFC 8628 device-authorization login, `--api-key` resolution, and profile
|
|
4
|
+
* lookup. Extracted from the old vibe-cli login command so `facilio login`
|
|
5
|
+
* is product-agnostic — one session covers all Facilio services.
|
|
6
|
+
*/
|
|
7
|
+
import { request } from 'undici';
|
|
8
|
+
import open from 'open';
|
|
9
|
+
import { logger } from './logger.js';
|
|
10
|
+
/**
|
|
11
|
+
* Resolve `--api-key` argument. Treats `-` as "read from stdin" so the key
|
|
12
|
+
* doesn't have to appear on the command line (and thus in shell history).
|
|
13
|
+
* echo "$KEY" | facilio login --api-key -
|
|
14
|
+
*/
|
|
15
|
+
export async function resolveApiKey(arg) {
|
|
16
|
+
const trimmed = arg.trim();
|
|
17
|
+
if (trimmed !== '-' && trimmed !== '')
|
|
18
|
+
return trimmed;
|
|
19
|
+
if (process.stdin.isTTY) {
|
|
20
|
+
throw new Error('`--api-key -` requested but stdin is a TTY. Pipe the key in or pass it inline.');
|
|
21
|
+
}
|
|
22
|
+
const chunks = [];
|
|
23
|
+
for await (const chunk of process.stdin)
|
|
24
|
+
chunks.push(chunk);
|
|
25
|
+
const key = Buffer.concat(chunks).toString('utf8').trim();
|
|
26
|
+
if (!key)
|
|
27
|
+
throw new Error('No API key on stdin.');
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
async function postForm(url, form) {
|
|
31
|
+
const body = new URLSearchParams(form).toString();
|
|
32
|
+
const res = await request(url, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
36
|
+
accept: 'application/json',
|
|
37
|
+
},
|
|
38
|
+
body,
|
|
39
|
+
});
|
|
40
|
+
const parsed = (await res.body.json().catch(() => ({})));
|
|
41
|
+
return { status: res.statusCode, body: parsed };
|
|
42
|
+
}
|
|
43
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
/**
|
|
45
|
+
* Fetch the connected user's name/email so `whoami` has something to show. Uses
|
|
46
|
+
* `Authorization: Bearer oauth2 <token>` because identity-service's IdentityFilter
|
|
47
|
+
* routes Bearer tokens with the `oauth2` marker through `validateOAuthToken` —
|
|
48
|
+
* product servers use plain `Bearer <token>`, but this call goes to
|
|
49
|
+
* identity-service directly.
|
|
50
|
+
*/
|
|
51
|
+
export async function fetchProfile(identityUrl, accessToken) {
|
|
52
|
+
try {
|
|
53
|
+
const res = await request(`${identityUrl.replace(/\/+$/, '')}/oauth2/profile`, {
|
|
54
|
+
method: 'GET',
|
|
55
|
+
headers: {
|
|
56
|
+
authorization: `Bearer oauth2 ${accessToken}`,
|
|
57
|
+
accept: 'application/json',
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
if (res.statusCode !== 200)
|
|
61
|
+
return undefined;
|
|
62
|
+
const body = (await res.body.json().catch(() => null));
|
|
63
|
+
const profile = body?.data?.profile;
|
|
64
|
+
if (!profile)
|
|
65
|
+
return undefined;
|
|
66
|
+
// orgId comes from the token claims (fetchProfile's caller has the token);
|
|
67
|
+
// identity's /oauth2/profile doesn't carry it.
|
|
68
|
+
return {
|
|
69
|
+
name: profile.name,
|
|
70
|
+
email: profile.email,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* RFC 8628 device authorization grant.
|
|
79
|
+
*
|
|
80
|
+
* 1. POST /oauth2/device_authorization → device_code + user_code.
|
|
81
|
+
* 2. Show user_code + verification URL; auto-open the pre-filled URL in a browser.
|
|
82
|
+
* User authenticates against identity-service's normal login UI on whichever
|
|
83
|
+
* device has a browser, then approves the code.
|
|
84
|
+
* 3. Poll /oauth2/token with grant_type=device_code until APPROVED/DENIED/EXPIRED.
|
|
85
|
+
* Respects RFC 8628 §3.5 polling errors (slow_down adds +5s to the interval).
|
|
86
|
+
* 4. On success, fetch the profile and persist homing URLs so refresh/revoke
|
|
87
|
+
* hit the user's home DC directly (cross-DC).
|
|
88
|
+
*/
|
|
89
|
+
export async function deviceLogin(params) {
|
|
90
|
+
const da = await postForm(`${params.identityUrl}/oauth2/device_authorization`, { client_id: params.clientId });
|
|
91
|
+
if (da.status !== 200 || da.body.error || !da.body.device_code || !da.body.user_code) {
|
|
92
|
+
const detail = da.body.error_description || da.body.error || `HTTP ${da.status}`;
|
|
93
|
+
throw new Error(`Device authorization request failed: ${detail}`);
|
|
94
|
+
}
|
|
95
|
+
const deviceCode = da.body.device_code;
|
|
96
|
+
const userCode = da.body.user_code;
|
|
97
|
+
const verifyUri = da.body.verification_uri_complete || da.body.verification_uri || '';
|
|
98
|
+
const expiresIn = da.body.expires_in ?? 600;
|
|
99
|
+
let interval = (da.body.interval ?? 5) * 1000;
|
|
100
|
+
logger.info('To sign in, open this URL in a browser:');
|
|
101
|
+
logger.step(verifyUri);
|
|
102
|
+
logger.info('Then confirm this code matches:');
|
|
103
|
+
logger.step(userCode);
|
|
104
|
+
logger.step(`(code expires in ${Math.round(expiresIn / 60)} min)`);
|
|
105
|
+
if (verifyUri) {
|
|
106
|
+
await open(verifyUri).catch(() => {
|
|
107
|
+
logger.warn('Could not auto-open browser. Open the URL above manually.');
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
process.stdout.write('Waiting for approval');
|
|
111
|
+
while (true) {
|
|
112
|
+
await sleep(interval);
|
|
113
|
+
const poll = await postForm(`${params.identityUrl}/oauth2/token`, {
|
|
114
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
115
|
+
device_code: deviceCode,
|
|
116
|
+
client_id: params.clientId,
|
|
117
|
+
});
|
|
118
|
+
const accessToken = poll.body.access_token;
|
|
119
|
+
if (poll.status === 200 && accessToken) {
|
|
120
|
+
process.stdout.write('\n');
|
|
121
|
+
const t = poll.body;
|
|
122
|
+
const region = (t.region || t.region_key || '').trim().toUpperCase() || undefined;
|
|
123
|
+
const homedIdentity = (t.identity_url || params.identityUrl).replace(/\/+$/, '');
|
|
124
|
+
const profile = await fetchProfile(homedIdentity, accessToken);
|
|
125
|
+
return {
|
|
126
|
+
accessToken,
|
|
127
|
+
refreshToken: t.refresh_token,
|
|
128
|
+
expiresAt: Date.now() + (t.expires_in ?? 3600) * 1000,
|
|
129
|
+
region,
|
|
130
|
+
// Product URLs resolve from the region table (core/regions.ts) at
|
|
131
|
+
// call time — an explicit `--server` at login overwrites this after.
|
|
132
|
+
serverUrl: '',
|
|
133
|
+
identityUrl: homedIdentity,
|
|
134
|
+
tokenUrl: t.token_url || `${homedIdentity}/oauth2/token`,
|
|
135
|
+
revokeUrl: t.revoke_url || `${homedIdentity}/oauth2/revoke`,
|
|
136
|
+
clientId: params.clientId,
|
|
137
|
+
user: profile,
|
|
138
|
+
source: 'device',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const err = poll.body.error;
|
|
142
|
+
if (err === 'authorization_pending') {
|
|
143
|
+
process.stdout.write('.');
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (err === 'slow_down') {
|
|
147
|
+
interval += 5000;
|
|
148
|
+
process.stdout.write('.');
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write('\n');
|
|
152
|
+
if (err === 'access_denied') {
|
|
153
|
+
throw new Error('Approval was denied in the browser.');
|
|
154
|
+
}
|
|
155
|
+
if (err === 'expired_token') {
|
|
156
|
+
throw new Error('The code expired before it was approved. Run `facilio login` again.');
|
|
157
|
+
}
|
|
158
|
+
const detail = poll.body.error_description || err || `HTTP ${poll.status}`;
|
|
159
|
+
throw new Error(`Unexpected response from token endpoint: ${detail}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../../src/core/identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
export const logger = {
|
|
3
|
+
info: (msg) => console.log(pc.cyan('→'), msg),
|
|
4
|
+
success: (msg) => console.log(pc.green('✓'), msg),
|
|
5
|
+
warn: (msg) => console.log(pc.yellow('!'), msg),
|
|
6
|
+
error: (msg) => console.error(pc.red('✗'), msg),
|
|
7
|
+
step: (msg) => console.log(pc.dim(' ' + msg)),
|
|
8
|
+
};
|
|
9
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACrD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACzD,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACvD,KAAK,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC;IACvD,IAAI,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;CACvD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import { stdin, stdout } from 'node:process';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal readline-based prompt. Returns the trimmed user input, or undefined
|
|
5
|
+
* when {@code optional} is true and the user just pressed Enter.
|
|
6
|
+
*/
|
|
7
|
+
export async function prompt(question, opts = {}) {
|
|
8
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
9
|
+
try {
|
|
10
|
+
const suffix = opts.default ? ` (${opts.default})` : opts.optional ? ' (optional)' : '';
|
|
11
|
+
const answer = (await rl.question(`? ${question}${suffix}: `)).trim();
|
|
12
|
+
if (!answer) {
|
|
13
|
+
if (opts.default)
|
|
14
|
+
return opts.default;
|
|
15
|
+
if (opts.optional)
|
|
16
|
+
return undefined;
|
|
17
|
+
// Required field — re-ask once
|
|
18
|
+
const retry = (await rl.question(`? ${question} (required): `)).trim();
|
|
19
|
+
if (!retry)
|
|
20
|
+
throw new Error(`${question} is required.`);
|
|
21
|
+
return retry;
|
|
22
|
+
}
|
|
23
|
+
return answer;
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
rl.close();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAO7C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgB,EAAE,OAAmB,EAAE;IAClE,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QACxF,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAC,OAAO,CAAC;YACtC,IAAI,IAAI,CAAC,QAAQ;gBAAE,OAAO,SAAS,CAAC;YACpC,+BAA+B;YAC/B,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,KAAK,QAAQ,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YACvE,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,GAAG,QAAQ,eAAe,CAAC,CAAC;YACxD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { vibeServerEnv, connectionsServerEnv, DEFAULT_SERVER_URL, DEFAULT_MCP_URL } from './constants.js';
|
|
2
|
+
export const REGIONS = {
|
|
3
|
+
US: { vibeUrl: 'https://us.vibes.facilio.studio', mcpUrl: 'https://mcp.facilio.com/mcp' },
|
|
4
|
+
UK: { vibeUrl: 'https://uk.vibes.facilio.studio', mcpUrl: 'https://mcp.facilio.co.uk/mcp' },
|
|
5
|
+
AE: { vibeUrl: 'https://ae.vibes.facilio.studio', mcpUrl: 'https://mcp.ae.facilio.ae/mcp' },
|
|
6
|
+
AU: { vibeUrl: 'https://au.vibes.facilio.studio', mcpUrl: 'https://mcp.facilio.com.au/mcp' },
|
|
7
|
+
AZURE: { vibeUrl: 'https://us2.vibes.facilio.studio', mcpUrl: 'https://mcp.facilio.us/mcp' },
|
|
8
|
+
'AZURE-AE': { vibeUrl: 'https://ae2.vibes.facilio.studio', mcpUrl: 'https://mcp.facilio.co.ae/mcp' },
|
|
9
|
+
ORACLE: { vibeUrl: 'https://sa.vibes.facilio.studio', mcpUrl: 'https://sa.mcp.facilio.com/mcp' },
|
|
10
|
+
};
|
|
11
|
+
function regionConfig(region) {
|
|
12
|
+
if (!region)
|
|
13
|
+
return undefined;
|
|
14
|
+
return REGIONS[region.trim().toUpperCase()];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a user-supplied region (case-insensitive) against the region
|
|
18
|
+
* table, applying {@code fallback} when none is given. `known` is false for a
|
|
19
|
+
* non-empty region that isn't in the table, so callers can reject it.
|
|
20
|
+
*/
|
|
21
|
+
export function normalizeRegion(input, fallback = 'US') {
|
|
22
|
+
const key = (input ?? fallback).trim().toUpperCase();
|
|
23
|
+
return { key, known: key in REGIONS };
|
|
24
|
+
}
|
|
25
|
+
/** Region keys the CLI knows about, for help text and error messages. */
|
|
26
|
+
export function regionKeys() {
|
|
27
|
+
return Object.keys(REGIONS);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* vibe-server URL. Precedence: env override → explicitly stored serverUrl
|
|
31
|
+
* (`facilio login --server …`, api-key sessions, or pre-region legacy
|
|
32
|
+
* sessions) → region table → default. New device-flow sessions store an
|
|
33
|
+
* empty serverUrl, so the region table is their effective source.
|
|
34
|
+
*/
|
|
35
|
+
export function vibeUrlFor(region, storedServerUrl) {
|
|
36
|
+
return (vibeServerEnv() ||
|
|
37
|
+
storedServerUrl ||
|
|
38
|
+
regionConfig(region)?.vibeUrl ||
|
|
39
|
+
DEFAULT_SERVER_URL).replace(/\/+$/, '');
|
|
40
|
+
}
|
|
41
|
+
/** Connections MCP endpoint: explicit env override → region table → default. */
|
|
42
|
+
export function mcpUrlFor(region) {
|
|
43
|
+
return (connectionsServerEnv() || regionConfig(region)?.mcpUrl || DEFAULT_MCP_URL).replace(/\/+$/, '');
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=regions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regions.js","sourceRoot":"","sources":["../../src/core/regions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAkB1G,MAAM,CAAC,MAAM,OAAO,GAAiC;IACnD,EAAE,EAAE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,6BAA6B,EAAE;IACzF,EAAE,EAAE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,+BAA+B,EAAE;IAC3F,EAAE,EAAE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,+BAA+B,EAAE;IAC3F,EAAE,EAAE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,gCAAgC,EAAE;IAC5F,KAAK,EAAE,EAAE,OAAO,EAAE,kCAAkC,EAAE,MAAM,EAAE,4BAA4B,EAAE;IAC5F,UAAU,EAAE,EAAE,OAAO,EAAE,kCAAkC,EAAE,MAAM,EAAE,+BAA+B,EAAE;IACpG,MAAM,EAAE,EAAE,OAAO,EAAE,iCAAiC,EAAE,MAAM,EAAE,gCAAgC,EAAE;CACjG,CAAC;AAEF,SAAS,YAAY,CAAC,MAA0B;IAC9C,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAyB,EACzB,QAAQ,GAAG,IAAI;IAEf,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrD,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;AACxC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,UAAU;IACxB,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC9B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,MAA0B,EAAE,eAAwB;IAC7E,OAAO,CACL,aAAa,EAAE;QACf,eAAe;QACf,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO;QAC7B,kBAAkB,CACnB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxB,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,SAAS,CAAC,MAA0B;IAClD,OAAO,CAAC,oBAAoB,EAAE,IAAI,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACzG,CAAC"}
|