@aglit-ai/cli 1.0.17197 → 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 +24 -31
- package/bin/aglit.cjs +50 -69
- package/package.json +1 -1
- package/src/browser-setup.cjs +27 -0
- package/src/command-groups.cjs +18 -0
- package/src/installation.cjs +19 -3
- 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
|
-
name and AI provider, requests the required OS permissions,
|
|
47
|
-
the Aglit AI Chrome extension
|
|
44
|
+
name and AI provider, requests the required OS permissions, enables Anywhere
|
|
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
|
|
@@ -130,27 +128,22 @@ Google currently rejects this proposed callback with `redirect_uri_mismatch`.
|
|
|
130
128
|
No port change has shipped; provider registration and native acceptance remain
|
|
131
129
|
required before qualifying coexistence.
|
|
132
130
|
|
|
133
|
-
Anywhere Access
|
|
131
|
+
Anywhere Access defaults to no two-factor security code; an explicit on preference is preserved. When enabled, incoming connection requests display the code in a native window without opening the full app. This requires a logged-in graphical desktop session; an SSH-only/server environment cannot display the prompt. Codes are not printed to the terminal.
|
|
134
132
|
|
|
135
|
-
After computer-use permissions, setup
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
After computer-use permissions, setup enables Anywhere Access before offering
|
|
134
|
+
the Aglit AI Chrome extension. Browser setup failures do not undo computer
|
|
135
|
+
registration. Run `aglit browser setup` to retry, or `aglit browser status --json`
|
|
136
|
+
to inspect it separately. Interactive browser setup waits for installation;
|
|
137
|
+
unattended setup returns the current browser status without waiting for a person.
|
|
138
|
+
It never changes a disabled extension setting or browser policy. Installation
|
|
139
|
+
detection is separate from browser-control connectivity.
|
|
140
140
|
|
|
141
141
|
This release supports Apple Silicon Macs. Windows publication is deferred until its signed native runtime is available.
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
when autoplay is blocked.
|
|
151
|
-
|
|
152
|
-
When settings already exist, interactive `aglit init` shows the saved computer name,
|
|
153
|
-
account and provider and offers to reuse them. Choosing reuse preserves the saved
|
|
154
|
-
model connection without repeating discovery or selection. Explicit `--name` and
|
|
155
|
-
`--provider` options override the corresponding saved values. Unattended init reuses
|
|
156
|
-
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,11 +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
|
|
34
|
-
aglit
|
|
35
|
-
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]
|
|
35
|
+
aglit browser setup|status [--json]
|
|
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
|
|
36
40
|
aglit models login copilot [--reauth] [--json]
|
|
37
41
|
aglit models login-status copilot --attempt ATTEMPT_ID [--json]
|
|
38
42
|
aglit models publish | unpublish --connection CONNECTION_ID --model MODEL_ID
|
|
@@ -42,11 +46,10 @@ const help = `Aglit Computer Use
|
|
|
42
46
|
aglit models pool POOL_ID --name NAME --connections local:ACCOUNT_A,local:ACCOUNT_B
|
|
43
47
|
aglit models test --model MODEL_ID [--connection CONNECTION_ID] [--harness]
|
|
44
48
|
aglit models unserve
|
|
45
|
-
aglit invite EMAIL --team TEAM_ID
|
|
46
49
|
aglit uninstall
|
|
47
50
|
|
|
48
51
|
Aglit runs in the background. Use aglit ui to open the Desktop window.
|
|
49
|
-
Use --json with install, init,
|
|
52
|
+
Use --json with install, init, ui or status for non-interactive JSON output.
|
|
50
53
|
Uninstall revokes access and purges only this installation. Mini Computer,
|
|
51
54
|
VMs, shared browser profiles and original provider logins remain separate.
|
|
52
55
|
Setup registers a Personal computer. Team invitations are separate commands.
|
|
@@ -71,20 +74,21 @@ async function main() {
|
|
|
71
74
|
'two-factor': { type: 'string' }, connections: { type: 'string' }, attempt: { type: 'string' }, reauth: {type:'boolean'},
|
|
72
75
|
harness: { type: 'boolean' },
|
|
73
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' },
|
|
74
|
-
|
|
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' },
|
|
75
78
|
}, allowPositionals: true, strict: true });
|
|
76
79
|
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY && !values.json && !process.env.CI);
|
|
77
80
|
if (values.help || !positionals.length && !interactive) { process.stdout.write(help); return; }
|
|
78
|
-
const [command = 'init', email, connectionId] = positionals;
|
|
81
|
+
const [command = 'init', email, connectionId] = require('../src/command-groups.cjs').commandGroups(positionals);
|
|
79
82
|
const options = {
|
|
80
|
-
models: ['device','fingerprint','name','json','challenge','code','connection','connections','model','harness','attempt','reauth'],
|
|
81
|
-
install: ['json', 'channel'], init: ['name', 'provider', 'account', 'json', 'channel'
|
|
82
|
-
|
|
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: [],
|
|
83
86
|
};
|
|
84
|
-
if (!Object.hasOwn(options, command) || Object.keys(values).some(key => !options[command].includes(key)) || positionals.length > (command === 'models' ? 3 : ['invite', 'updates'].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.');
|
|
85
88
|
if (values['two-factor'] !== undefined && !['on', 'off'].includes(values['two-factor']))
|
|
86
89
|
throw Error('Choose --two-factor on or off.');
|
|
87
|
-
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');
|
|
91
|
+
if (command === 'browser' && !['setup','status'].includes(email)) throw Error('Use aglit browser setup|status');
|
|
88
92
|
if (command === 'updates' && !['on', 'off', 'status'].includes(email)) throw Error('Use aglit updates on|off|status');
|
|
89
93
|
if (values.channel !== undefined && !['latest', 'canary', 'embedded'].includes(values.channel)) throw Error('Update channel must be latest, canary or embedded');
|
|
90
94
|
if (values.team && !/^[A-Za-z0-9._:-]{1,128}$/.test(values.team)) throw Error('Invalid team ID');
|
|
@@ -131,7 +135,7 @@ async function main() {
|
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
137
|
let name = values.name;
|
|
134
|
-
const mode =
|
|
138
|
+
const mode = ['init','ui'].includes(command) ? 'headed' : 'headless';
|
|
135
139
|
if (command === 'init') {
|
|
136
140
|
if (!installed()) {
|
|
137
141
|
if (!interactive && !name) throw Error('Provide --name for unattended setup');
|
|
@@ -140,11 +144,11 @@ async function main() {
|
|
|
140
144
|
}
|
|
141
145
|
if (!installed()) throw Error('Run aglit init to install and register this computer first.');
|
|
142
146
|
installation.readInstallation();
|
|
143
|
-
if (!installation.development && ['
|
|
147
|
+
if (!installation.development && ['ui', 'init'].includes(command)) {
|
|
144
148
|
const recovered = await updates.recoverActivation();
|
|
145
149
|
if (recovered.updating) { ui.result(recovered); ui.finish('Update in progress'); return; }
|
|
146
150
|
}
|
|
147
|
-
if (!installation.development && ['
|
|
151
|
+
if (!installation.development && ['ui', 'init'].includes(command)) updates.initialize(values.channel);
|
|
148
152
|
const configPath = path.join(installation.profile, 'ipc/sdk-ipc.json');
|
|
149
153
|
const control = (action, fields = {}) => require('../src/control.cjs').control(configPath, action, fields);
|
|
150
154
|
let state;
|
|
@@ -156,7 +160,7 @@ async function main() {
|
|
|
156
160
|
if (updates.state().automatic && updates.runtimeStopped(installation.readInstallation())) {
|
|
157
161
|
try { await updates.update(); } catch {
|
|
158
162
|
if (fs.existsSync(path.join(installation.root, 'update-transaction.json')) || fs.existsSync(path.join(installation.root, 'update-activation.json'))) {
|
|
159
|
-
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');
|
|
160
164
|
}
|
|
161
165
|
ui.info('Automatic update deferred; starting the installed Desktop.');
|
|
162
166
|
}
|
|
@@ -171,17 +175,22 @@ async function main() {
|
|
|
171
175
|
throw Error('Computer Use did not become ready. See runtime.log in its installation directory.');
|
|
172
176
|
});
|
|
173
177
|
}
|
|
174
|
-
if (!installation.development && state.running && ['status', '
|
|
175
|
-
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);
|
|
176
180
|
const modelQuestion = interactive ? async text => {
|
|
177
181
|
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
178
182
|
try { return await prompt.question(text); } finally { prompt.close(); }
|
|
179
183
|
} : undefined;
|
|
180
184
|
if (command === 'models') {
|
|
181
|
-
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));
|
|
182
186
|
ui.finish('Model connections'); return;
|
|
183
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; }
|
|
184
189
|
if (command === 'status') { ui.result(state); ui.finish('Computer status'); return; }
|
|
190
|
+
if (command === 'browser') {
|
|
191
|
+
ui.result(email === 'status' ? await control('browser-status') : await require('../src/browser-setup.cjs').setupBrowser(control, ui, interactive));
|
|
192
|
+
ui.finish('Browser extension'); return;
|
|
193
|
+
}
|
|
185
194
|
if (command === 'console') { ui.result({ url: state.consoleUrl }); ui.finish('Console'); return; }
|
|
186
195
|
if (command === 'rename') {
|
|
187
196
|
if (!values.name) throw Error('Provide --name to rename this computer');
|
|
@@ -193,83 +202,55 @@ async function main() {
|
|
|
193
202
|
name: values.name, ...(values.team ? { teamId: values.team } : {}),
|
|
194
203
|
...(values.workspace ? { workspaceId: values.workspace } : {}),
|
|
195
204
|
})));
|
|
196
|
-
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;
|
|
197
215
|
}
|
|
198
216
|
if (command === 'teams' || command === 'pipelines') {
|
|
199
217
|
const result = await ui.step(command === 'teams' ? 'Load your teams' : 'Load authorized pipeline devices', () => control(command));
|
|
200
218
|
if (interactive) ui[command](result); else ui.result(result);
|
|
201
|
-
if (command === 'teams' && values.open) await control('open-teams');
|
|
202
|
-
if (command === 'pipelines' && values.open) await control('open-pipelines');
|
|
203
219
|
ui.finish(command === 'teams' ? 'Your teams' : 'Your pipelines'); return;
|
|
204
220
|
}
|
|
205
|
-
if (command === '
|
|
221
|
+
if (command === 'ui') {
|
|
206
222
|
const window = await control('window', { mode });
|
|
207
223
|
state = { ...state, ...window };
|
|
208
224
|
ui.result(state); ui.finish(mode === 'headed' ? 'Desktop window opened' : 'Running in the background'); return;
|
|
209
225
|
}
|
|
210
226
|
if (command === 'init') {
|
|
211
227
|
assertSetupRuntime(state);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
await control('login');
|
|
215
|
-
const deadline = Date.now() + 5 * 60000;
|
|
216
|
-
while (Date.now() < deadline) {
|
|
217
|
-
await delay(1500); const current = await control('status'); if (current.email) return current;
|
|
218
|
-
}
|
|
219
|
-
throw Error('Sign-in timed out. Run aglit init to resume.');
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
if (values.account && state.email?.toLowerCase() !== values.account.toLowerCase())
|
|
223
|
-
throw Error(`Signed in as ${state.email}; expected ${values.account}. Switch the browser sign-in account and rerun aglit init.`);
|
|
224
|
-
const reuseSavedSetup = Boolean(state.name) && (!interactive || await ui.reuseSetup(state));
|
|
225
|
-
if (interactive && !reuseSavedSetup && !values.account && !await ui.confirmAccount(state.email))
|
|
226
|
-
throw Object.assign(Error('Setup cancelled before enabling remote access.'), { cancelled: true });
|
|
227
|
-
ui.info(`Signed in as ${state.email}`);
|
|
228
|
-
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());
|
|
229
230
|
name ||= state.name;
|
|
230
231
|
if (!name) throw Error('Provide --name for unattended setup');
|
|
231
|
-
const selected =
|
|
232
|
-
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.');
|
|
233
235
|
for (const warning of selected?.view.warnings || []) ui.info(warning);
|
|
234
236
|
await ui.step('Save computer settings', () => control('configure', { name,
|
|
235
|
-
...(selected?.connectionId ? {
|
|
237
|
+
...(selected?.connectionId ? { model_connection_id: selected.connectionId } : values.provider && values.provider !== 'auto' ? { provider: values.provider } : {}) }));
|
|
236
238
|
if (process.platform === 'darwin') {
|
|
237
|
-
try {
|
|
238
|
-
const audio = require('../src/mac-audio.cjs').setup({ interactive });
|
|
239
|
-
if (audio.actionRequired) ui.info(audio.actionRequired);
|
|
240
|
-
if (audio.restartRequired) ui.info('BlackHole is installed. Restart this Mac when convenient to enable the audio cable.');
|
|
241
|
-
if (!interactive) ui.result({ audio });
|
|
242
|
-
} catch (error) {
|
|
243
|
-
ui.info(`${error.message} Rerun aglit init to retry audio setup.`);
|
|
244
|
-
}
|
|
245
239
|
ui.permissions();
|
|
246
240
|
}
|
|
247
241
|
const permissions = await ui.step('Waiting for screen recording and input permissions', () => control('permissions'));
|
|
248
242
|
if (!interactive) ui.result(permissions);
|
|
249
243
|
if (permissions.permissions?.supported && (permissions.permissions.screenRecording !== 'granted' || permissions.permissions.accessibility !== 'granted'))
|
|
250
244
|
throw Error('Grant Screen Recording and Accessibility in System Settings, then rerun aglit init. Anywhere Access has not been enabled.');
|
|
251
|
-
let browser = await ui.step('Check Aglit AI browser extension', () => control('browser-status'));
|
|
252
|
-
if (!browser.enabled) {
|
|
253
|
-
const prepared = await ui.step('Set up Aglit AI browser extension', () => control('browser-setup'));
|
|
254
|
-
ui.browserExtension(prepared);
|
|
255
|
-
browser = await ui.step('Waiting for Chrome extension installation', async () => {
|
|
256
|
-
const deadline = Date.now() + 180000;
|
|
257
|
-
do {
|
|
258
|
-
const current = await control('browser-status');
|
|
259
|
-
if (current.installed && current.enabled) return current;
|
|
260
|
-
await delay(1500);
|
|
261
|
-
} while (Date.now() < deadline);
|
|
262
|
-
throw Error('Add or enable the Aglit AI Chrome extension, then rerun aglit init.');
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
245
|
state = await ui.step('Connect this computer to your account', async () => {
|
|
266
246
|
await control('enable');
|
|
267
247
|
return control('status');
|
|
268
248
|
});
|
|
269
249
|
if (!installation.development) updates.rememberStatus(state);
|
|
270
250
|
if (typeof state.deviceId !== 'string' || !state.deviceId) throw Error('Registration returned no device ID. Check aglit status before retrying.');
|
|
251
|
+
const browser = await require('../src/browser-setup.cjs').setupOptionalBrowser(control, ui, interactive);
|
|
271
252
|
state = { ...state, ...await control('window', { mode }) };
|
|
272
|
-
state = { ...state, employeeUrl: new URL(`/employees/${encodeURIComponent(state.deviceId)}`, state.consoleUrl).href };
|
|
253
|
+
state = { ...state, browser, employeeUrl: new URL(`/employees/${encodeURIComponent(state.deviceId)}`, state.consoleUrl).href };
|
|
273
254
|
ui.result(state); ui.finish('Anywhere Access enabled'); return;
|
|
274
255
|
}
|
|
275
256
|
if (command === 'invite') { ui.result(await control('invite', { teamId: values.team, email })); ui.finish('Invitation sent'); return; }
|
package/package.json
CHANGED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
3
|
+
async function setupBrowser(control, ui, interactive) {
|
|
4
|
+
let browser = await ui.step('Check Aglit AI browser extension', () => control('browser-status'));
|
|
5
|
+
if (browser.enabled) return browser;
|
|
6
|
+
const prepared = await ui.step('Set up Aglit AI browser extension', () => control('browser-setup'));
|
|
7
|
+
ui.browserExtension(prepared);
|
|
8
|
+
if (!interactive) return control('browser-status');
|
|
9
|
+
return ui.step('Waiting for Chrome extension installation', async () => {
|
|
10
|
+
const deadline = Date.now() + 180000;
|
|
11
|
+
do {
|
|
12
|
+
browser = await control('browser-status');
|
|
13
|
+
if (browser.installed && browser.enabled) return browser;
|
|
14
|
+
await delay(1500);
|
|
15
|
+
} while (Date.now() < deadline);
|
|
16
|
+
throw Error('Add or enable the Aglit AI Chrome extension, then run aglit browser setup.');
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async function setupOptionalBrowser(control, ui, interactive) {
|
|
20
|
+
try { return await setupBrowser(control, ui, interactive); }
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.cancelled) throw error;
|
|
23
|
+
ui.info('Browser setup is incomplete: ' + error.message + '\nRetry with aglit browser setup. Anywhere Access remains enabled.');
|
|
24
|
+
return { installed: false, enabled: false, setupRequired: true };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
module.exports = {setupBrowser, setupOptionalBrowser};
|
|
@@ -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 };
|
package/src/installation.cjs
CHANGED
|
@@ -195,6 +195,21 @@ async function install(channel) {
|
|
|
195
195
|
if (bootstrap) require('./updates.cjs').adoptBootstrap(bootstrap);
|
|
196
196
|
return { installed: true, ...receipt };
|
|
197
197
|
}
|
|
198
|
+
function runtimePorts() {
|
|
199
|
+
const file = path.join(root, 'runtime-ports.json');
|
|
200
|
+
if (fs.existsSync(file)) {
|
|
201
|
+
const stat = fs.lstatSync(file);
|
|
202
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 1024 ||
|
|
203
|
+
process.platform !== 'win32' && (stat.uid !== process.getuid() || stat.mode & 0o077)) throw Error('Private runtime ports required');
|
|
204
|
+
const ports = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
205
|
+
if (!Array.isArray(ports) || ports.length !== 5 || new Set(ports).size !== 5 || ports.some(port => !Number.isInteger(port) || port < 1024 || port > 65535)) throw Error('Runtime ports differ');
|
|
206
|
+
return ports;
|
|
207
|
+
}
|
|
208
|
+
const probe = `const net=require('node:net');(async()=>{for(let base=49117;base<50117;base+=10){const servers=[];try{for(let i=0;i<5;i++){const server=net.createServer();servers.push(server);await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(base+i,'127.0.0.1',resolve);});}console.log(JSON.stringify(servers.map(server=>server.address().port)));return;}catch{}finally{for(const server of servers)server.close();}}process.exitCode=1;})()`;
|
|
209
|
+
const ports = JSON.parse(run(process.execPath, ['-e', probe]));
|
|
210
|
+
fs.writeFileSync(file, JSON.stringify(ports) + '\n', {flag:'wx',mode:0o600});
|
|
211
|
+
return ports;
|
|
212
|
+
}
|
|
198
213
|
function start() {
|
|
199
214
|
if (development) throw Error('Start the source runtime with scripts/dev/run-computer-use.cjs before invoking this CLI');
|
|
200
215
|
if (fs.existsSync(path.join(root, 'uninstalling.json'))) throw Error('Computer Use is uninstalling. Run aglit uninstall to finish cleanup.');
|
|
@@ -202,6 +217,7 @@ function start() {
|
|
|
202
217
|
'$p=[Security.Principal.WindowsPrincipal]::new([Security.Principal.WindowsIdentity]::GetCurrent()); if($p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)){throw "Run aglit init from a non-elevated interactive user terminal"}']);
|
|
203
218
|
const installed = readInstallation();
|
|
204
219
|
verifyPublisher(installed.executable);
|
|
220
|
+
runtimePorts();
|
|
205
221
|
const log = fs.openSync(path.join(root, 'runtime.log'), 'a', 0o600);
|
|
206
222
|
const env = { ...process.env };
|
|
207
223
|
// Do not let an operator's guest/development environment adopt another identity.
|
|
@@ -222,10 +238,10 @@ function start() {
|
|
|
222
238
|
// Bind the result to the exact installed executable and current OS user.
|
|
223
239
|
const deadline = Date.now() + 10000;
|
|
224
240
|
do {
|
|
225
|
-
const rows = execFileSync('/bin/ps', ['-axo', 'pid=,uid=,
|
|
241
|
+
const rows = execFileSync('/bin/ps', ['-axo', 'pid=,uid=,args='], {encoding: 'utf8', timeout: 15000, maxBuffer: 8 * 1024 ** 2});
|
|
226
242
|
const matches = rows.split('\n').flatMap(line => {
|
|
227
243
|
const row = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
228
|
-
return row && Number(row[2]) === process.getuid() && row[3].trim() === installed.executable ? [Number(row[1])] : [];
|
|
244
|
+
return row && Number(row[2]) === process.getuid() && row[3].trim() === installed.executable + ' --computer-use' ? [Number(row[1])] : [];
|
|
229
245
|
});
|
|
230
246
|
if (matches.length > 1) throw Error('Multiple installed Desktop processes; activation remains pending');
|
|
231
247
|
if (matches.length === 1) return { pid: matches[0] };
|
|
@@ -278,4 +294,4 @@ function readUninstall() {
|
|
|
278
294
|
!Number.isSafeInteger(receipt.pid) || receipt.pid <= 0) throw Error('Invalid uninstall receipt');
|
|
279
295
|
return receipt;
|
|
280
296
|
}
|
|
281
|
-
module.exports = { development, root, profile, readInstallation, install, start, purge, purgeIncomplete, readUninstall, privateRoot, canonical, verifyPublisher, embeddedRelease, copyArchive, stageRuntime, verifyRuntime };
|
|
297
|
+
module.exports = { runtimePorts, development, root, profile, readInstallation, install, start, purge, purgeIncomplete, readUninstall, privateRoot, canonical, verifyPublisher, embeddedRelease, copyArchive, stageRuntime, verifyRuntime };
|
|
@@ -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; }
|