@aglit-ai/cli 1.0.17300 → 1.0.17343
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 +14 -23
- package/bin/aglit.cjs +42 -54
- package/package.json +1 -1
- package/src/command-groups.cjs +18 -0
- package/src/model-connections.cjs +23 -5
- package/src/setup-account.cjs +24 -0
- package/src/ui.cjs +5 -8
package/README.md
CHANGED
|
@@ -9,7 +9,6 @@ After the CLI and a compatible signed desktop release have been published:
|
|
|
9
9
|
pnpm add -g @aglit-ai/cli
|
|
10
10
|
aglit
|
|
11
11
|
aglit ui
|
|
12
|
-
aglit start
|
|
13
12
|
aglit status
|
|
14
13
|
aglit disable
|
|
15
14
|
aglit enable
|
|
@@ -20,9 +19,8 @@ Run `aglit` in an interactive terminal to open the guided setup. `aglit init`
|
|
|
20
19
|
also runs this flow and installs the signed runtime if it is missing. Setup asks
|
|
21
20
|
for a computer name and AI provider, then enables Anywhere Access. It runs in
|
|
22
21
|
the background during setup, then opens the Desktop window when setup succeeds.
|
|
23
|
-
`aglit
|
|
24
|
-
|
|
25
|
-
without disabling its connection. `aglit --help` (or `aglit -h`) lists commands.
|
|
22
|
+
`aglit ui` opens the window using the same app and profile.
|
|
23
|
+
Close the window to leave Aglit running in the background; quit explicitly from its tray/menu icon. `aglit --help` (or `aglit -h`) lists commands.
|
|
26
24
|
|
|
27
25
|
Setup opens Aglit's Google login in the browser,
|
|
28
26
|
detects existing Codex/Claude logins, and uses a configured authenticated provider
|
|
@@ -32,22 +30,22 @@ accounts remain owned by the user; uninstall does not delete their original logi
|
|
|
32
30
|
|
|
33
31
|
```sh
|
|
34
32
|
aglit init --name "Office Mac" --provider codex
|
|
35
|
-
aglit
|
|
33
|
+
aglit models accounts
|
|
36
34
|
aglit rename --name "Office Mac"
|
|
37
35
|
aglit console
|
|
38
|
-
aglit
|
|
36
|
+
aglit teams init --name "My team"
|
|
39
37
|
aglit teams --open
|
|
40
38
|
aglit pipelines
|
|
41
39
|
aglit pipelines --open
|
|
42
|
-
aglit invite USER_EMAIL --team TEAM_ID
|
|
40
|
+
aglit teams invite USER_EMAIL --team TEAM_ID
|
|
43
41
|
```
|
|
44
42
|
|
|
45
43
|
Setup registers a Personal computer with your signed-in account. It asks for its
|
|
46
44
|
name and AI provider, requests the required OS permissions, enables Anywhere
|
|
47
45
|
Access, then offers the Aglit AI Chrome extension. Team selection and
|
|
48
46
|
pipeline discovery are separate from setup.
|
|
49
|
-
Use `aglit
|
|
50
|
-
Use `aglit teams` to inspect memberships and `aglit pipelines
|
|
47
|
+
Use `aglit teams init --name "My team"` to associate this computer with a team on your account. The CLI derives its installed workspace; no server-side workspace record or private-key provisioning is required. Manage invitations at teams.aglit.ai.
|
|
48
|
+
Use `aglit teams list` to inspect memberships and `aglit pipelines list` to inspect the
|
|
51
49
|
encrypted pipeline viewer. A listed device does not prove its pipelines are connected.
|
|
52
50
|
|
|
53
51
|
For unattended setup, use `aglit init --json --name "Office Mac" --provider codex
|
|
@@ -142,17 +140,10 @@ detection is separate from browser-control connectivity.
|
|
|
142
140
|
|
|
143
141
|
This release supports Apple Silicon Macs. Windows publication is deferred until its signed native runtime is available.
|
|
144
142
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
when autoplay is blocked.
|
|
153
|
-
|
|
154
|
-
When settings already exist, interactive `aglit init` shows the saved computer name,
|
|
155
|
-
account and provider and offers to reuse them. Choosing reuse preserves the saved
|
|
156
|
-
model connection without repeating discovery or selection. Explicit `--name` and
|
|
157
|
-
`--provider` options override the corresponding saved values. Unattended init reuses
|
|
158
|
-
saved settings by default. Permission and connection checks still run.
|
|
143
|
+
Run `aglit init` again to edit setup with the saved account, computer name, and AI provider preselected. Choose a different account in the account step to switch; cloud access is revoked before the previous account signs out.
|
|
144
|
+
|
|
145
|
+
Manage repositories with `aglit pipelines init --repo GIT_URL --branch master`, inspect local definitions with `aglit pipelines list`, and list accessible computers with `aglit pipelines devices`. Teams use `aglit teams list`, `aglit teams init`, and `aglit teams invite`. These commands do not open browser pages.
|
|
146
|
+
|
|
147
|
+
Use `aglit models setup` to choose a connection and enable model proxying for the root assistant. `aglit models choose` changes only the saved connection preference; `aglit models proxying on|off` changes the global proxy setting. `aglit models accounts` lists connected local accounts.
|
|
148
|
+
|
|
149
|
+
Connect an existing private Codex home explicitly with `aglit models import codex --home /absolute/path/to/codex-home --name Brian`. Importing another account preserves connected accounts and pools. The selected home remains the credential and refresh owner; passive discovery never scans homes or starts login. Create or update a pool with `aglit models pool POOL_ID --name NAME --connections local:ACCOUNT_A,local:ACCOUNT_B`. Native account setup and repository operations require a compatible updated runtime.
|
package/bin/aglit.cjs
CHANGED
|
@@ -20,7 +20,6 @@ const help = `Aglit Computer Use
|
|
|
20
20
|
[--name "Computer name"] [--provider codex|claude|auto]
|
|
21
21
|
[--account EMAIL] [--channel latest|canary|embedded]
|
|
22
22
|
aglit install Install the signed Desktop runtime
|
|
23
|
-
aglit start
|
|
24
23
|
aglit ui
|
|
25
24
|
aglit status [--json]
|
|
26
25
|
aglit rename --name "Computer name"
|
|
@@ -28,12 +27,16 @@ const help = `Aglit Computer Use
|
|
|
28
27
|
aglit update [--check] [--channel latest|canary|embedded] [--json]
|
|
29
28
|
aglit updates on|off|status [--json]
|
|
30
29
|
aglit enable [--two-factor on|off] | disable
|
|
31
|
-
aglit teams
|
|
32
|
-
aglit
|
|
33
|
-
aglit
|
|
30
|
+
aglit teams list
|
|
31
|
+
aglit teams init --name "Team name" [--team TEAM_ID] [--workspace WORKSPACE_ID]
|
|
32
|
+
aglit teams invite EMAIL --team TEAM_ID
|
|
33
|
+
aglit pipelines list | devices
|
|
34
|
+
aglit pipelines init [--repo GIT_URL] [--branch BRANCH]
|
|
34
35
|
aglit browser setup|status [--json]
|
|
35
|
-
aglit
|
|
36
|
-
aglit models
|
|
36
|
+
aglit models import codex --home ABSOLUTE_CODEX_HOME --name NAME
|
|
37
|
+
aglit models proxying on|off
|
|
38
|
+
aglit models accounts
|
|
39
|
+
aglit models list | setup | choose
|
|
37
40
|
aglit models login copilot [--reauth] [--json]
|
|
38
41
|
aglit models login-status copilot --attempt ATTEMPT_ID [--json]
|
|
39
42
|
aglit models publish | unpublish --connection CONNECTION_ID --model MODEL_ID
|
|
@@ -43,11 +46,10 @@ const help = `Aglit Computer Use
|
|
|
43
46
|
aglit models pool POOL_ID --name NAME --connections local:ACCOUNT_A,local:ACCOUNT_B
|
|
44
47
|
aglit models test --model MODEL_ID [--connection CONNECTION_ID] [--harness]
|
|
45
48
|
aglit models unserve
|
|
46
|
-
aglit invite EMAIL --team TEAM_ID
|
|
47
49
|
aglit uninstall
|
|
48
50
|
|
|
49
51
|
Aglit runs in the background. Use aglit ui to open the Desktop window.
|
|
50
|
-
Use --json with install, init,
|
|
52
|
+
Use --json with install, init, ui or status for non-interactive JSON output.
|
|
51
53
|
Uninstall revokes access and purges only this installation. Mini Computer,
|
|
52
54
|
VMs, shared browser profiles and original provider logins remain separate.
|
|
53
55
|
Setup registers a Personal computer. Team invitations are separate commands.
|
|
@@ -72,20 +74,20 @@ async function main() {
|
|
|
72
74
|
'two-factor': { type: 'string' }, connections: { type: 'string' }, attempt: { type: 'string' }, reauth: {type:'boolean'},
|
|
73
75
|
harness: { type: 'boolean' },
|
|
74
76
|
device: { type: 'string' }, fingerprint: { type: 'string' }, challenge: { type: 'string' }, code: { type: 'string' }, connection: { type: 'string' }, model: { type: 'string' }, name: { type: 'string' }, provider: { type: 'string' }, account: { type: 'string' },
|
|
75
|
-
|
|
77
|
+
home: { type: 'string' }, repo: { type: 'string' }, branch: { type: 'string' }, team: { type: 'string' }, workspace: { type: 'string' }, check: { type: 'boolean' }, channel: { type: 'string' }, json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
76
78
|
}, allowPositionals: true, strict: true });
|
|
77
79
|
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY && !values.json && !process.env.CI);
|
|
78
80
|
if (values.help || !positionals.length && !interactive) { process.stdout.write(help); return; }
|
|
79
|
-
const [command = 'init', email, connectionId] = positionals;
|
|
81
|
+
const [command = 'init', email, connectionId] = require('../src/command-groups.cjs').commandGroups(positionals);
|
|
80
82
|
const options = {
|
|
81
|
-
models: ['device','fingerprint','name','json','challenge','code','connection','connections','model','harness','attempt','reauth'],
|
|
82
|
-
install: ['json', 'channel'], init: ['name', 'provider', 'account', 'json', 'channel'
|
|
83
|
-
|
|
83
|
+
models: ['home','device','fingerprint','name','json','challenge','code','connection','connections','model','harness','attempt','reauth'],
|
|
84
|
+
install: ['json', 'channel'], init: ['name', 'provider', 'account', 'json', 'channel'],
|
|
85
|
+
ui: ['json', 'channel'], status: ['json'], update: ['check', 'channel', 'json'], updates: ['json'], enable: ['two-factor'], disable: [], rename: ['name', 'json'], console: ['json'], teams: ['json'], team: ['name', 'team', 'workspace', 'json'], pipelines: ['json'], 'pipelines-list': ['json'], 'pipelines-init': ['repo','branch','json'], 'models-accounts': ['json'], browser: ['json'], invite: ['team'], uninstall: [],
|
|
84
86
|
};
|
|
85
|
-
if (!Object.hasOwn(options, command) || Object.keys(values).some(key => !options[command].includes(key)) || positionals.length > (command === 'models' ? 3 : ['invite', 'updates', 'browser'].includes(command) ? 2 : 1)) throw Error('Invalid command or options. Run aglit --help.');
|
|
87
|
+
if (!Object.hasOwn(options, command) || Object.keys(values).some(key => !options[command].includes(key)) || require('../src/command-groups.cjs').commandGroups(positionals).length > (command === 'models' ? 3 : ['invite', 'updates', 'browser'].includes(command) ? 2 : 1)) throw Error('Invalid command or options. Run aglit --help.');
|
|
86
88
|
if (values['two-factor'] !== undefined && !['on', 'off'].includes(values['two-factor']))
|
|
87
89
|
throw Error('Choose --two-factor on or off.');
|
|
88
|
-
if (installation.development && !['models','status','init','
|
|
90
|
+
if (installation.development && !['models','status','init','models-accounts','enable','disable','ui','browser'].includes(command)) throw Error('Source profile supports model/setup, access and window commands; installed runtime management is separate');
|
|
89
91
|
if (command === 'browser' && !['setup','status'].includes(email)) throw Error('Use aglit browser setup|status');
|
|
90
92
|
if (command === 'updates' && !['on', 'off', 'status'].includes(email)) throw Error('Use aglit updates on|off|status');
|
|
91
93
|
if (values.channel !== undefined && !['latest', 'canary', 'embedded'].includes(values.channel)) throw Error('Update channel must be latest, canary or embedded');
|
|
@@ -133,7 +135,7 @@ async function main() {
|
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
let name = values.name;
|
|
136
|
-
const mode =
|
|
138
|
+
const mode = ['init','ui'].includes(command) ? 'headed' : 'headless';
|
|
137
139
|
if (command === 'init') {
|
|
138
140
|
if (!installed()) {
|
|
139
141
|
if (!interactive && !name) throw Error('Provide --name for unattended setup');
|
|
@@ -142,11 +144,11 @@ async function main() {
|
|
|
142
144
|
}
|
|
143
145
|
if (!installed()) throw Error('Run aglit init to install and register this computer first.');
|
|
144
146
|
installation.readInstallation();
|
|
145
|
-
if (!installation.development && ['
|
|
147
|
+
if (!installation.development && ['ui', 'init'].includes(command)) {
|
|
146
148
|
const recovered = await updates.recoverActivation();
|
|
147
149
|
if (recovered.updating) { ui.result(recovered); ui.finish('Update in progress'); return; }
|
|
148
150
|
}
|
|
149
|
-
if (!installation.development && ['
|
|
151
|
+
if (!installation.development && ['ui', 'init'].includes(command)) updates.initialize(values.channel);
|
|
150
152
|
const configPath = path.join(installation.profile, 'ipc/sdk-ipc.json');
|
|
151
153
|
const control = (action, fields = {}) => require('../src/control.cjs').control(configPath, action, fields);
|
|
152
154
|
let state;
|
|
@@ -158,7 +160,7 @@ async function main() {
|
|
|
158
160
|
if (updates.state().automatic && updates.runtimeStopped(installation.readInstallation())) {
|
|
159
161
|
try { await updates.update(); } catch {
|
|
160
162
|
if (fs.existsSync(path.join(installation.root, 'update-transaction.json')) || fs.existsSync(path.join(installation.root, 'update-activation.json'))) {
|
|
161
|
-
const recovered = await updates.recoverActivation(); if (recovered.updating) throw Error('An update is still active; retry aglit
|
|
163
|
+
const recovered = await updates.recoverActivation(); if (recovered.updating) throw Error('An update is still active; retry aglit ui after it finishes');
|
|
162
164
|
}
|
|
163
165
|
ui.info('Automatic update deferred; starting the installed Desktop.');
|
|
164
166
|
}
|
|
@@ -173,16 +175,17 @@ async function main() {
|
|
|
173
175
|
throw Error('Computer Use did not become ready. See runtime.log in its installation directory.');
|
|
174
176
|
});
|
|
175
177
|
}
|
|
176
|
-
if (!installation.development && state.running && ['status', '
|
|
177
|
-
if (!installation.development && ['
|
|
178
|
+
if (!installation.development && state.running && ['status', 'ui', 'init'].includes(command)) updates.rememberStatus(state);
|
|
179
|
+
if (!installation.development && ['ui', 'init'].includes(command) && state.running) updates.ensureWorker(state.pid);
|
|
178
180
|
const modelQuestion = interactive ? async text => {
|
|
179
181
|
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
180
182
|
try { return await prompt.question(text); } finally { prompt.close(); }
|
|
181
183
|
} : undefined;
|
|
182
184
|
if (command === 'models') {
|
|
183
|
-
ui.result(await require('../src/model-connections.cjs').modelCommand(control,email,connectionId,values,modelQuestion));
|
|
185
|
+
ui.result(await require('../src/model-connections.cjs').modelCommand(control,email,connectionId,values,modelQuestion,interactive ? ui.modelConnection : undefined));
|
|
184
186
|
ui.finish('Model connections'); return;
|
|
185
187
|
}
|
|
188
|
+
if (command === 'models-accounts') { const view=await require('../src/model-connections.cjs').listModelConnections(control);ui.result({accounts:view.connections.filter(value=>value.kind==='local'&&!value.id.startsWith('local:pool:')),warnings:view.warnings||[]});ui.finish('Model accounts');return; }
|
|
186
189
|
if (command === 'status') { ui.result(state); ui.finish('Computer status'); return; }
|
|
187
190
|
if (command === 'browser') {
|
|
188
191
|
ui.result(email === 'status' ? await control('browser-status') : await require('../src/browser-setup.cjs').setupBrowser(control, ui, interactive));
|
|
@@ -199,55 +202,40 @@ async function main() {
|
|
|
199
202
|
name: values.name, ...(values.team ? { teamId: values.team } : {}),
|
|
200
203
|
...(values.workspace ? { workspaceId: values.workspace } : {}),
|
|
201
204
|
})));
|
|
202
|
-
ui.finish('Manage your team
|
|
205
|
+
ui.finish('Manage your team in Console settings: https://console.aglit.ai/?settings=teams'); return;
|
|
206
|
+
}
|
|
207
|
+
if (command === 'pipelines-list' || command === 'pipelines-init') {
|
|
208
|
+
let fields = {};
|
|
209
|
+
if (command === 'pipelines-init') {
|
|
210
|
+
const repositoryUrl = values.repo || require('node:child_process').execFileSync('git', ['remote','get-url','origin'], {encoding:'utf8',stdio:['ignore','pipe','ignore']}).trim();
|
|
211
|
+
fields = { repository_url: repositoryUrl, branch: values.branch || 'master' };
|
|
212
|
+
}
|
|
213
|
+
const result = await control(command, fields);
|
|
214
|
+
ui.result(result); ui.finish(command === 'pipelines-init' ? 'Repository polling configured' : 'Pipelines'); return;
|
|
203
215
|
}
|
|
204
216
|
if (command === 'teams' || command === 'pipelines') {
|
|
205
217
|
const result = await ui.step(command === 'teams' ? 'Load your teams' : 'Load authorized pipeline devices', () => control(command));
|
|
206
218
|
if (interactive) ui[command](result); else ui.result(result);
|
|
207
|
-
if (command === 'teams' && values.open) await control('open-teams');
|
|
208
|
-
if (command === 'pipelines' && values.open) await control('open-pipelines');
|
|
209
219
|
ui.finish(command === 'teams' ? 'Your teams' : 'Your pipelines'); return;
|
|
210
220
|
}
|
|
211
|
-
if (command === '
|
|
221
|
+
if (command === 'ui') {
|
|
212
222
|
const window = await control('window', { mode });
|
|
213
223
|
state = { ...state, ...window };
|
|
214
224
|
ui.result(state); ui.finish(mode === 'headed' ? 'Desktop window opened' : 'Running in the background'); return;
|
|
215
225
|
}
|
|
216
226
|
if (command === 'init') {
|
|
217
227
|
assertSetupRuntime(state);
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
await control('login');
|
|
221
|
-
const deadline = Date.now() + 5 * 60000;
|
|
222
|
-
while (Date.now() < deadline) {
|
|
223
|
-
await delay(1500); const current = await control('status'); if (current.email) return current;
|
|
224
|
-
}
|
|
225
|
-
throw Error('Sign-in timed out. Run aglit init to resume.');
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
if (values.account && state.email?.toLowerCase() !== values.account.toLowerCase())
|
|
229
|
-
throw Error(`Signed in as ${state.email}; expected ${values.account}. Switch the browser sign-in account and rerun aglit init.`);
|
|
230
|
-
const reuseSavedSetup = Boolean(state.name) && (!interactive || await ui.reuseSetup(state));
|
|
231
|
-
if (interactive && !reuseSavedSetup && !values.account && !await ui.confirmAccount(state.email))
|
|
232
|
-
throw Object.assign(Error('Setup cancelled before enabling remote access.'), { cancelled: true });
|
|
233
|
-
ui.info(`Signed in as ${state.email}`);
|
|
234
|
-
if (!values.name && interactive && !reuseSavedSetup) name = await ui.name(state.name || os.hostname());
|
|
228
|
+
state = await require('../src/setup-account.cjs').setupAccount(control, ui, state, { interactive, expectedEmail: values.account });
|
|
229
|
+
if (!values.name && interactive) name = await ui.name(state.name || os.hostname());
|
|
235
230
|
name ||= state.name;
|
|
236
231
|
if (!name) throw Error('Provide --name for unattended setup');
|
|
237
|
-
const selected =
|
|
238
|
-
await require('../src/model-connections.cjs').chooseModelConnection(control, { provider: values.provider, question: modelQuestion });
|
|
232
|
+
const selected = !interactive && state.name && !values.provider ? null :
|
|
233
|
+
await require('../src/model-connections.cjs').chooseModelConnection(control, { provider: values.provider, question: modelQuestion, ...(interactive ? { selectChoice: ui.modelConnection } : {}) });
|
|
234
|
+
if(selected?.unavailable)throw Error('Update the Aglit runtime to restore model discovery, then rerun aglit init.');
|
|
239
235
|
for (const warning of selected?.view.warnings || []) ui.info(warning);
|
|
240
236
|
await ui.step('Save computer settings', () => control('configure', { name,
|
|
241
|
-
...(selected?.connectionId ? {
|
|
237
|
+
...(selected?.connectionId ? { model_connection_id: selected.connectionId } : values.provider && values.provider !== 'auto' ? { provider: values.provider } : {}) }));
|
|
242
238
|
if (process.platform === 'darwin') {
|
|
243
|
-
try {
|
|
244
|
-
const audio = require('../src/mac-audio.cjs').setup({ interactive });
|
|
245
|
-
if (audio.actionRequired) ui.info(audio.actionRequired);
|
|
246
|
-
if (audio.restartRequired) ui.info('BlackHole is installed. Restart this Mac when convenient to enable the audio cable.');
|
|
247
|
-
if (!interactive) ui.result({ audio });
|
|
248
|
-
} catch (error) {
|
|
249
|
-
ui.info(`${error.message} Rerun aglit init to retry audio setup.`);
|
|
250
|
-
}
|
|
251
239
|
ui.permissions();
|
|
252
240
|
}
|
|
253
241
|
const permissions = await ui.step('Waiting for screen recording and input permissions', () => control('permissions'));
|
package/package.json
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
function commandGroups(positionals) {
|
|
3
|
+
const [group, action, argument] = positionals;
|
|
4
|
+
if (['start','providers','team','invite'].includes(group)) throw Error('Use aglit ui, aglit models, or aglit teams. Run aglit --help.');
|
|
5
|
+
if (group === 'teams') {
|
|
6
|
+
if (action === 'list' && positionals.length === 2) return ['teams'];
|
|
7
|
+
if (action === 'init' && positionals.length === 2) return ['team'];
|
|
8
|
+
if (action === 'invite' && argument && positionals.length === 3) return ['invite',argument];
|
|
9
|
+
throw Error('Use aglit teams list, teams init, or teams invite EMAIL.');
|
|
10
|
+
}
|
|
11
|
+
if (group === 'pipelines') {
|
|
12
|
+
if (positionals.length === 2 && ['list','init','devices'].includes(action)) return [action === 'devices' ? 'pipelines' : 'pipelines-'+action];
|
|
13
|
+
throw Error('Use aglit pipelines list, pipelines init, or pipelines devices.');
|
|
14
|
+
}
|
|
15
|
+
if (group === 'models' && action === 'accounts' && positionals.length === 2) return ['models-accounts'];
|
|
16
|
+
return positionals;
|
|
17
|
+
}
|
|
18
|
+
module.exports = { commandGroups };
|
|
@@ -11,9 +11,21 @@ async function listModelConnections(control) {
|
|
|
11
11
|
warnings: ['Model endpoint discovery is unavailable. Existing provider settings are unchanged; retry aglit models list later.'] };
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
async function chooseModelConnection(control, { provider, question } = {}) {
|
|
14
|
+
async function chooseModelConnection(control, { provider, question, selectChoice } = {}) {
|
|
15
15
|
let view = await listModelConnections(control);
|
|
16
16
|
if (view.selection.status === 'discovery-unavailable') return { view, connectionId: null, unavailable: true };
|
|
17
|
+
if (selectChoice) {
|
|
18
|
+
const choices = view.connections.filter(value => value.models.length && (value.available || value.kind === 'remote') &&
|
|
19
|
+
(!provider || ['auto','aglit'].includes(provider) || value.models.some(model=>model.provider===provider)));
|
|
20
|
+
if (!choices.length) throw Error('No model connection is ready. Connect an account with aglit models.');
|
|
21
|
+
const id = await selectChoice(choices, view.preferred_connection_id || view.selection.connection_id || choices[0].id);
|
|
22
|
+
const selected = choices.find(value=>value.id===id); if (!selected) throw Error('Choose a listed model connection');
|
|
23
|
+
if (selected.kind === 'remote' && !selected.available)
|
|
24
|
+
await modelCommand(control,'register',undefined,{device:selected.device_id,fingerprint:selected.identity_fingerprint,name:selected.name},question);
|
|
25
|
+
view = await control('models-prefer',{connection_id:id});
|
|
26
|
+
if (view.selection.status !== 'selected' || view.selection.connection_id !== id) throw Error('Selected model connection is unavailable');
|
|
27
|
+
return {view,connectionId:id};
|
|
28
|
+
}
|
|
17
29
|
// Init/choose is an explicit connection action. Passive models-list never
|
|
18
30
|
// prompts; reconnect only the saved/chosen endpoint, never every known host.
|
|
19
31
|
if (['preferred-unavailable','setup-required'].includes(view.selection.status)) {
|
|
@@ -51,9 +63,11 @@ async function chooseModelConnection(control, { provider, question } = {}) {
|
|
|
51
63
|
return { view, connectionId: selection.connection_id };
|
|
52
64
|
}
|
|
53
65
|
function validateModelCommand(command, id, values) {
|
|
54
|
-
const allowed = {list:['json'],login:['json','reauth'],'login-status':['attempt','json'],test:['connection','model','harness'],choose:[],pool:['name','connections'],publish:['connection','model'],unpublish:['connection','model'],unserve:[],register:['device','fingerprint','name','challenge','code'],prefer:[],remove:[]};
|
|
66
|
+
const allowed = {list:['json'],import:['home','name','json'],proxying:[],login:['json','reauth'],'login-status':['attempt','json'],test:['connection','model','harness'],setup:[],choose:[],pool:['name','connections'],publish:['connection','model'],unpublish:['connection','model'],unserve:[],register:['device','fingerprint','name','challenge','code'],prefer:[],remove:[]};
|
|
55
67
|
if (!Object.hasOwn(allowed,command) || Object.keys(values).some(key=>!allowed[command].includes(key)) ||
|
|
56
|
-
(!['prefer','remove','pool','login','login-status'].includes(command) && id) || (['prefer','remove','pool','login','login-status'].includes(command) && !id)) throw Error('Invalid model command or options');
|
|
68
|
+
(!['prefer','remove','pool','login','login-status','import','proxying'].includes(command) && id) || (['prefer','remove','pool','login','login-status','import','proxying'].includes(command) && !id)) throw Error('Invalid model command or options');
|
|
69
|
+
if(command==='import'&&(id!=='codex'||typeof values.home!=='string'||!require('node:path').isAbsolute(values.home)||values.home.length>4096||/[\x00-\x1f\x7f]/.test(values.home)||typeof values.name!=='string'||!values.name.trim()||values.name.length>100||/[\x00-\x1f\x7f]/.test(values.name)))throw Error('Use aglit models import codex --home ABSOLUTE_CODEX_HOME --name NAME');
|
|
70
|
+
if(command==='proxying'&&!['on','off'].includes(id))throw Error('Use aglit models proxying on|off');
|
|
57
71
|
if(['login','login-status'].includes(command)&&(id!=='copilot'||command==='login-status'&&!/^[a-f0-9-]{36}$/.test(values.attempt??'')))throw Error('Use aglit models login copilot or aglit models login-status copilot --attempt ATTEMPT_ID');
|
|
58
72
|
if(values.reauth!==undefined&&values.reauth!==true)throw Error('Use --reauth only to explicitly replace the Copilot login');
|
|
59
73
|
if (command === 'pool' && (!/^[A-Za-z0-9._-]{1,80}$/.test(id) || !values.name?.trim() || values.name.length > 100 ||
|
|
@@ -67,8 +81,10 @@ function validateModelCommand(command, id, values) {
|
|
|
67
81
|
if (['publish','unpublish'].includes(command) && (!/^local:[A-Za-z0-9._:-]{1,180}$/.test(values.connection||'') || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,255}$/.test(values.model||''))) throw Error('Choose a local --connection and --model');
|
|
68
82
|
if (command === 'remove' && !id.startsWith('remote:')) throw Error('A registered remote connection ID is required');
|
|
69
83
|
}
|
|
70
|
-
async function modelCommand(control, command, id, values, question) {
|
|
84
|
+
async function modelCommand(control, command, id, values, question, selectChoice) {
|
|
71
85
|
validateModelCommand(command,id,values);
|
|
86
|
+
if(command==='import')return control('models-import',{provider_id:id,home:values.home,name:values.name});
|
|
87
|
+
if(command==='proxying')return control('models-proxying',{enabled:String(id==='on')});
|
|
72
88
|
if (command === 'list') return listModelConnections(control);
|
|
73
89
|
if(command==='login'||command==='login-status')return control(command==='login'?'models-login':'models-login-status',{provider_id:id,...(command==='login-status'?{attempt_id:values.attempt}:values.reauth?{reauth:'true'}:{})});
|
|
74
90
|
if (command === 'test') {
|
|
@@ -106,7 +122,9 @@ async function modelCommand(control, command, id, values, question) {
|
|
|
106
122
|
}
|
|
107
123
|
if (command === 'publish' || command === 'unpublish') return control('models-publish',{connection_id:values.connection,model_id:values.model,published:String(command==='publish')});
|
|
108
124
|
if (command === 'unserve') return control('models-unpublish-all');
|
|
109
|
-
if (command === 'choose')
|
|
125
|
+
if (command === 'setup' || command === 'choose') { const selected=await chooseModelConnection(control,{question,selectChoice});
|
|
126
|
+
if(selected.unavailable)throw Error('Update the Aglit runtime to restore model discovery before configuring model proxying');
|
|
127
|
+
return command==='setup'?control('models-setup',{connection_id:selected.connectionId}):selected.view; }
|
|
110
128
|
throw Error('Use aglit models list|register|prefer|remove|publish|unpublish|unserve|choose');
|
|
111
129
|
}
|
|
112
130
|
module.exports={chooseModelConnection,listModelConnections,modelCommand,validateModelCommand};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
async function setupAccount(control, ui, state, { interactive, expectedEmail, delay = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
|
|
3
|
+
if (state.email && interactive) {
|
|
4
|
+
const choice = await ui.account(state.email);
|
|
5
|
+
if (choice === 'change') {
|
|
6
|
+
try { await control('account-sign-out'); }
|
|
7
|
+
catch (error) { if (/Unknown Computer Use command/.test(error.message)) throw Error('Update the Aglit runtime before switching accounts. The current account is unchanged.'); throw error; }
|
|
8
|
+
state = await control('status');
|
|
9
|
+
if (state.email) throw Error('The previous account has not signed out. Setup stopped.');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if (!state.email) {
|
|
13
|
+
state = await ui.step('Sign in to Aglit in your browser', async () => {
|
|
14
|
+
await control('login'); const deadline = Date.now() + 5 * 60000;
|
|
15
|
+
while (Date.now() < deadline) { await delay(1500); const current = await control('status'); if (current.email) return current; }
|
|
16
|
+
throw Error('Sign-in timed out. Run aglit init to resume.');
|
|
17
|
+
});
|
|
18
|
+
ui.selection('Aglit account', state.email);
|
|
19
|
+
}
|
|
20
|
+
if (expectedEmail && state.email.toLowerCase() !== expectedEmail.toLowerCase())
|
|
21
|
+
throw Error(`Signed in as ${state.email}; expected ${expectedEmail}. Rerun aglit init and choose a different account.`);
|
|
22
|
+
return state;
|
|
23
|
+
}
|
|
24
|
+
module.exports = { setupAccount };
|
package/src/ui.cjs
CHANGED
|
@@ -40,15 +40,12 @@ async function createUi(interactive) {
|
|
|
40
40
|
return teams;
|
|
41
41
|
},
|
|
42
42
|
pipelines(value) {
|
|
43
|
-
p.note((value.devices.length ? value.devices.map(device => `${clean(device.name)}${device.platform ? ' · ' + clean(device.platform) : ''}`).join('\n') + '\n\n' : 'No authorized pipeline devices.\n\n')
|
|
44
|
-
'https://pipelines.aglit.ai/\nOpen the encrypted viewer with aglit pipelines --open.', 'Pipeline devices');
|
|
43
|
+
p.note((value.devices.length ? value.devices.map(device => `${clean(device.name)}${device.platform ? ' · ' + clean(device.platform) : ''}`).join('\n') + '\n\n' : 'No authorized pipeline devices.\n\n'), 'Pipeline devices');
|
|
45
44
|
},
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
},
|
|
51
|
-
confirmAccount: email => answer(p.confirm({ message: `Register with ${clean(email)}?`, initialValue: true })),
|
|
45
|
+
account: email => answer(p.select({ message: 'Aglit account', initialValue: 'keep',
|
|
46
|
+
options: [{value:'keep',label:clean(email)},{value:'change',label:'Use a different account'}] })),
|
|
47
|
+
modelConnection: (connections, preferred) => answer(p.select({ message:'AI provider', initialValue:preferred,
|
|
48
|
+
options:connections.map(value=>({value:value.id,label:clean(value.name),hint:value.kind==='remote'?'Shared account':'This computer'})) })),
|
|
52
49
|
async step(label, action) {
|
|
53
50
|
const spinner = p.spinner(); spinner.start(label);
|
|
54
51
|
try { const value = await action(); spinner.stop(label); return value; }
|