@aglit-ai/cli 0.1.9 → 0.1.10
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 +26 -15
- package/bin/aglit.cjs +92 -53
- package/package.json +7 -1
- package/src/control.cjs +1 -1
- package/src/installation.cjs +6 -4
- package/src/ui.cjs +7 -19
package/README.md
CHANGED
|
@@ -8,8 +8,8 @@ After the CLI and a compatible signed desktop release have been published:
|
|
|
8
8
|
```sh
|
|
9
9
|
pnpm add -g @aglit-ai/cli
|
|
10
10
|
aglit
|
|
11
|
-
aglit
|
|
12
|
-
aglit start
|
|
11
|
+
aglit ui
|
|
12
|
+
aglit start
|
|
13
13
|
aglit status
|
|
14
14
|
aglit disable
|
|
15
15
|
aglit enable
|
|
@@ -18,10 +18,9 @@ aglit uninstall
|
|
|
18
18
|
|
|
19
19
|
Run `aglit` in an interactive terminal to open the guided setup. `aglit init`
|
|
20
20
|
also runs this flow and installs the signed runtime if it is missing. Setup asks
|
|
21
|
-
for a computer name and
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
--headless` hides its window without disabling its connection.
|
|
21
|
+
for a computer name and AI provider, then enables Anywhere Access. It runs in
|
|
22
|
+
the background without a window-mode prompt. After setup, `aglit ui`
|
|
23
|
+
opens the Desktop window using the same app and profile. `aglit start` hides its window without disabling its connection.
|
|
25
24
|
|
|
26
25
|
Setup opens Aglit's Google login in the browser,
|
|
27
26
|
detects existing Codex/Claude logins, and uses a configured authenticated provider
|
|
@@ -32,21 +31,25 @@ accounts remain owned by the user; uninstall does not delete their original logi
|
|
|
32
31
|
```sh
|
|
33
32
|
aglit init --name "Office Mac" --provider codex
|
|
34
33
|
aglit providers
|
|
35
|
-
aglit
|
|
34
|
+
aglit rename --name "Office Mac"
|
|
35
|
+
aglit console
|
|
36
|
+
aglit team --name "My team"
|
|
37
|
+
aglit teams --open
|
|
36
38
|
aglit pipelines
|
|
37
39
|
aglit pipelines --open
|
|
38
|
-
aglit init --name "Office Mac" --provider auto --team TEAM_ID
|
|
39
40
|
aglit invite USER_EMAIL --team TEAM_ID
|
|
40
41
|
```
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
Setup registers a Personal computer with your signed-in account. It asks for its
|
|
44
|
+
name and AI provider, requests the required OS permissions, guides installation of
|
|
45
|
+
the Aglit AI Chrome extension, and enables Anywhere Access. Team selection and
|
|
46
|
+
pipeline discovery are separate from setup.
|
|
47
|
+
Use `aglit team --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` to inspect memberships and `aglit pipelines --open` to open the
|
|
49
|
+
encrypted pipeline viewer. A listed device does not prove its pipelines are connected.
|
|
47
50
|
|
|
48
51
|
For unattended setup, use `aglit init --json --name "Office Mac" --provider codex
|
|
49
|
-
--account YOUR_EMAIL
|
|
52
|
+
--account YOUR_EMAIL`. `--account` checks the actual signed-in
|
|
50
53
|
account before configuration or remote access is enabled; it never selects or
|
|
51
54
|
impersonates an account. JSON mode and redirected input skip interactive prompts.
|
|
52
55
|
Provide an explicit `--provider` when detection
|
|
@@ -65,7 +68,7 @@ Successful registration prints the direct employee link at
|
|
|
65
68
|
`https://console.aglit.ai/employees/<device-id>` and includes it as `employeeUrl`
|
|
66
69
|
in JSON output.
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
`aglit invite EMAIL --team TEAM_ID` uses the existing backend invitation scope. It does not grant team
|
|
69
72
|
members access to a separately registered personal computer. The backend requires
|
|
70
73
|
current accepted membership/admin authority for its operations; a pending
|
|
71
74
|
invitation is not access. Shared computer enrollment needs an authoritative team
|
|
@@ -126,3 +129,11 @@ No port change has shipped; provider registration and native acceptance remain
|
|
|
126
129
|
required before qualifying coexistence.
|
|
127
130
|
|
|
128
131
|
Anywhere Access requires a two-factor security code by default; an explicit off preference is preserved. In CLI headless mode, 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.
|
|
132
|
+
|
|
133
|
+
After computer-use permissions, setup opens the published Aglit AI Chrome Web Store
|
|
134
|
+
listing. Choose Add to Chrome and approve Chrome’s prompt. Setup waits until the
|
|
135
|
+
Store extension is installed and enabled in Chrome’s selected profile; it never
|
|
136
|
+
changes a disabled extension setting or browser policy. Installation detection is
|
|
137
|
+
separate from browser-control connectivity.
|
|
138
|
+
|
|
139
|
+
This release supports Apple Silicon Macs. Windows publication is deferred until its signed native runtime is available.
|
package/bin/aglit.cjs
CHANGED
|
@@ -4,34 +4,50 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const { parseArgs } = require('node:util');
|
|
7
|
+
const { createInterface } = require('node:readline/promises');
|
|
7
8
|
const installation = require('../src/installation.cjs');
|
|
8
9
|
const updates = require('../src/updates.cjs');
|
|
9
|
-
const { createUi
|
|
10
|
+
const { createUi } = require('../src/ui.cjs');
|
|
10
11
|
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
12
|
+
function assertSetupRuntime(state) {
|
|
13
|
+
if (!Number.isSafeInteger(state.setupVersion) || state.setupVersion < 2)
|
|
14
|
+
throw Error('This CLI requires a newer Aglit Computer Use runtime. Run aglit update, then rerun aglit init.');
|
|
15
|
+
}
|
|
11
16
|
const help = `Aglit Computer Use
|
|
12
17
|
|
|
13
18
|
aglit Interactive setup
|
|
14
19
|
aglit init Install and register this computer
|
|
15
20
|
[--name "Computer name"] [--provider codex|claude|auto]
|
|
16
|
-
[--account EMAIL] [--
|
|
17
|
-
[--team TEAM_ID] [--invite EMAIL]
|
|
21
|
+
[--account EMAIL] [--channel latest|canary|embedded]
|
|
18
22
|
aglit install Install the signed Desktop runtime
|
|
19
|
-
aglit start
|
|
23
|
+
aglit start
|
|
24
|
+
aglit ui
|
|
20
25
|
aglit status [--json]
|
|
26
|
+
aglit rename --name "Computer name"
|
|
27
|
+
aglit console
|
|
21
28
|
aglit update [--check] [--channel latest|canary|embedded] [--json]
|
|
22
29
|
aglit updates on|off|status [--json]
|
|
23
30
|
aglit enable | disable
|
|
24
|
-
aglit teams
|
|
31
|
+
aglit teams [--open]
|
|
32
|
+
aglit team --name "Team name" [--team TEAM_ID] [--workspace WORKSPACE_ID]
|
|
25
33
|
aglit pipelines [--open]
|
|
26
34
|
aglit providers
|
|
35
|
+
aglit models list | choose
|
|
36
|
+
aglit models publish | unpublish --connection CONNECTION_ID --model MODEL_ID
|
|
37
|
+
aglit models register --device DEVICE_ID --fingerprint SHA256 [--name NAME]
|
|
38
|
+
aglit models prefer CONNECTION_ID|auto
|
|
39
|
+
aglit models remove CONNECTION_ID
|
|
40
|
+
aglit models pool POOL_ID --name NAME --connections local:ACCOUNT_A,local:ACCOUNT_B
|
|
41
|
+
aglit models test --model MODEL_ID [--connection CONNECTION_ID] [--harness]
|
|
42
|
+
aglit models unserve
|
|
27
43
|
aglit invite EMAIL --team TEAM_ID
|
|
28
44
|
aglit uninstall
|
|
29
45
|
|
|
30
|
-
Aglit runs in the background
|
|
46
|
+
Aglit runs in the background. Use aglit ui to open the Desktop window.
|
|
31
47
|
Use --json with install, init, start or status for non-interactive JSON output.
|
|
32
48
|
Uninstall revokes access and purges only this installation. Mini Computer,
|
|
33
49
|
VMs, shared browser profiles and original provider logins remain separate.
|
|
34
|
-
|
|
50
|
+
Setup registers a Personal computer. Team invitations are separate commands.
|
|
35
51
|
`;
|
|
36
52
|
|
|
37
53
|
async function finishUninstall(receipt) {
|
|
@@ -48,30 +64,33 @@ async function finishUninstall(receipt) {
|
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
async function main() {
|
|
51
|
-
const delegated = updates.delegate(__filename); if (delegated !== null) { process.exitCode = delegated; return; }
|
|
67
|
+
const delegated = installation.development ? null : updates.delegate(__filename); if (delegated !== null) { process.exitCode = delegated; return; }
|
|
52
68
|
const { values, positionals } = parseArgs({ options: {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
69
|
+
connections: { type: 'string' },
|
|
70
|
+
harness: { type: 'boolean' },
|
|
71
|
+
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' },
|
|
72
|
+
team: { type: 'string' }, workspace: { type: 'string' }, check: { type: 'boolean' }, channel: { type: 'string' }, open: { type: 'boolean' }, json: { type: 'boolean' }, help: { type: 'boolean', short: 'h' },
|
|
56
73
|
}, allowPositionals: true, strict: true });
|
|
57
74
|
const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY && !values.json && !process.env.CI);
|
|
58
75
|
if (values.help || !positionals.length && !interactive) { process.stdout.write(help); return; }
|
|
59
|
-
const [command = 'init', email] = positionals;
|
|
76
|
+
const [command = 'init', email, connectionId] = positionals;
|
|
60
77
|
const options = {
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
models: ['device','fingerprint','name','json','challenge','code','connection','connections','model','harness'],
|
|
79
|
+
install: ['json', 'channel'], init: ['name', 'provider', 'account', 'json', 'channel'],
|
|
80
|
+
start: ['json', 'channel'], ui: ['json'], status: ['json'], update: ['check', 'channel', 'json'], updates: ['json'], enable: [], disable: [], rename: ['name', 'json'], console: ['json'], teams: ['json', 'open'], team: ['name', 'team', 'workspace', 'json'], pipelines: ['open', 'json'], providers: ['json'], invite: ['team'], uninstall: [],
|
|
63
81
|
};
|
|
64
|
-
if (!Object.hasOwn(options, command) || Object.keys(values).some(key => !options[command].includes(key)) || positionals.length > (['invite', 'updates'].includes(command) ? 2 : 1)) throw Error('Invalid command or options. Run aglit --help.');
|
|
82
|
+
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.');
|
|
83
|
+
if (installation.development && !['models','status','init','providers','enable','disable','ui','start'].includes(command)) throw Error('Source profile supports model/setup, access and window commands; installed runtime management is separate');
|
|
65
84
|
if (command === 'updates' && !['on', 'off', 'status'].includes(email)) throw Error('Use aglit updates on|off|status');
|
|
66
85
|
if (values.channel !== undefined && !['latest', 'canary', 'embedded'].includes(values.channel)) throw Error('Update channel must be latest, canary or embedded');
|
|
67
|
-
if (values.headed && values.headless) throw Error('Choose either --headed or --headless');
|
|
68
86
|
if (values.team && !/^[A-Za-z0-9._:-]{1,128}$/.test(values.team)) throw Error('Invalid team ID');
|
|
69
|
-
if (values.provider &&
|
|
87
|
+
if (values.provider && !/^[a-z0-9._-]{1,64}$/.test(values.provider)) throw Error('Invalid provider');
|
|
88
|
+
if (command === 'models') require('../src/model-connections.cjs').validateModelCommand(email,connectionId,values);
|
|
70
89
|
const validEmail = value => value.length <= 254 && /^[^\s@*]+@[^\s@*]+\.[^\s@*]+$/.test(value);
|
|
71
|
-
const recipient = command === 'invite' ? email :
|
|
90
|
+
const recipient = command === 'invite' ? email : undefined;
|
|
72
91
|
if (recipient && !validEmail(recipient)) throw Error('An individual invitation email address is required');
|
|
73
92
|
if (values.account !== undefined && !validEmail(values.account)) throw Error('An individual account email address is required');
|
|
74
|
-
if (
|
|
93
|
+
if (command === 'invite' && (!recipient || !values.team)) throw Error('Invitations require an email address and --team TEAM_ID');
|
|
75
94
|
if (values.name !== undefined && (!values.name.trim() || values.name.length > 100 || /[\x00-\x1f\x7f-\x9f]/.test(values.name))) throw Error('Computer name must contain 1–100 printable characters');
|
|
76
95
|
const ui = await createUi(interactive);
|
|
77
96
|
const installed = () => fs.existsSync(path.join(installation.root, 'installation.json'));
|
|
@@ -108,10 +127,8 @@ async function main() {
|
|
|
108
127
|
}
|
|
109
128
|
}
|
|
110
129
|
let name = values.name;
|
|
111
|
-
|
|
130
|
+
const mode = command === 'ui' ? 'headed' : 'headless';
|
|
112
131
|
if (command === 'init') {
|
|
113
|
-
if (!name && interactive && !installed()) name = await ui.name(os.hostname());
|
|
114
|
-
if (interactive && values.headed === undefined && values.headless === undefined) mode = await ui.mode();
|
|
115
132
|
if (!installed()) {
|
|
116
133
|
if (!interactive && !name) throw Error('Provide --name for unattended setup');
|
|
117
134
|
await ui.step('Download and verify Desktop', () => installation.install(values.channel));
|
|
@@ -119,11 +136,11 @@ async function main() {
|
|
|
119
136
|
}
|
|
120
137
|
if (!installed()) throw Error('Run aglit init to install and register this computer first.');
|
|
121
138
|
installation.readInstallation();
|
|
122
|
-
if (['start', 'init'].includes(command)) {
|
|
139
|
+
if (!installation.development && ['start', 'ui', 'init'].includes(command)) {
|
|
123
140
|
const recovered = await updates.recoverActivation();
|
|
124
141
|
if (recovered.updating) { ui.result(recovered); ui.finish('Update in progress'); return; }
|
|
125
142
|
}
|
|
126
|
-
if (['start', 'init'].includes(command)) updates.initialize(values.channel);
|
|
143
|
+
if (!installation.development && ['start', 'ui', 'init'].includes(command)) updates.initialize(values.channel);
|
|
127
144
|
const configPath = path.join(installation.profile, 'ipc/sdk-ipc.json');
|
|
128
145
|
const control = (action, fields = {}) => require('../src/control.cjs').control(configPath, action, fields);
|
|
129
146
|
let state;
|
|
@@ -150,21 +167,44 @@ async function main() {
|
|
|
150
167
|
throw Error('Computer Use did not become ready. See runtime.log in its installation directory.');
|
|
151
168
|
});
|
|
152
169
|
}
|
|
153
|
-
if (state.running && ['status', 'start', 'init'].includes(command)) updates.rememberStatus(state);
|
|
154
|
-
if (['start', 'init'].includes(command) && state.running) updates.ensureWorker(state.pid);
|
|
170
|
+
if (!installation.development && state.running && ['status', 'start', 'ui', 'init'].includes(command)) updates.rememberStatus(state);
|
|
171
|
+
if (!installation.development && ['start', 'ui', 'init'].includes(command) && state.running) updates.ensureWorker(state.pid);
|
|
172
|
+
const modelQuestion = interactive ? async text => {
|
|
173
|
+
const prompt = createInterface({ input: process.stdin, output: process.stderr });
|
|
174
|
+
try { return await prompt.question(text); } finally { prompt.close(); }
|
|
175
|
+
} : undefined;
|
|
176
|
+
if (command === 'models') {
|
|
177
|
+
ui.result(await require('../src/model-connections.cjs').modelCommand(control,email,connectionId,values,modelQuestion));
|
|
178
|
+
ui.finish('Model connections'); return;
|
|
179
|
+
}
|
|
155
180
|
if (command === 'status') { ui.result(state); ui.finish('Computer status'); return; }
|
|
181
|
+
if (command === 'console') { ui.result({ url: state.consoleUrl }); ui.finish('Console'); return; }
|
|
182
|
+
if (command === 'rename') {
|
|
183
|
+
if (!values.name) throw Error('Provide --name to rename this computer');
|
|
184
|
+
ui.result(await control('configure', { name: values.name })); ui.finish('Computer renamed'); return;
|
|
185
|
+
}
|
|
186
|
+
if (command === 'team') {
|
|
187
|
+
if (!values.name) throw Error('Provide --name for the team');
|
|
188
|
+
ui.result(await ui.step('Set up your team', () => control('setup-team', {
|
|
189
|
+
name: values.name, ...(values.team ? { teamId: values.team } : {}),
|
|
190
|
+
...(values.workspace ? { workspaceId: values.workspace } : {}),
|
|
191
|
+
})));
|
|
192
|
+
ui.finish('Manage your team at https://teams.aglit.ai/'); return;
|
|
193
|
+
}
|
|
156
194
|
if (command === 'teams' || command === 'pipelines') {
|
|
157
195
|
const result = await ui.step(command === 'teams' ? 'Load your teams' : 'Load authorized pipeline devices', () => control(command));
|
|
158
196
|
if (interactive) ui[command](result); else ui.result(result);
|
|
197
|
+
if (command === 'teams' && values.open) await control('open-teams');
|
|
159
198
|
if (command === 'pipelines' && values.open) await control('open-pipelines');
|
|
160
199
|
ui.finish(command === 'teams' ? 'Your teams' : 'Your pipelines'); return;
|
|
161
200
|
}
|
|
162
|
-
if (command === 'start' || command === '
|
|
201
|
+
if (command === 'start' || command === 'ui') {
|
|
163
202
|
const window = await control('window', { mode });
|
|
164
203
|
state = { ...state, ...window };
|
|
165
|
-
|
|
204
|
+
ui.result(state); ui.finish(mode === 'headed' ? 'Desktop window opened' : 'Running in the background'); return;
|
|
166
205
|
}
|
|
167
206
|
if (command === 'init') {
|
|
207
|
+
assertSetupRuntime(state);
|
|
168
208
|
if (!state.email) {
|
|
169
209
|
state = await ui.step('Complete sign-in in your browser', async () => {
|
|
170
210
|
await control('login');
|
|
@@ -180,33 +220,32 @@ async function main() {
|
|
|
180
220
|
if (interactive && !values.account && !await ui.confirmAccount(state.email))
|
|
181
221
|
throw Object.assign(Error('Setup cancelled before enabling remote access.'), { cancelled: true });
|
|
182
222
|
ui.info(`Signed in as ${state.email}`);
|
|
183
|
-
|
|
184
|
-
if (interactive) {
|
|
185
|
-
const discovery = await ui.step('Load your teams and pipelines', () => Promise.allSettled([control('teams'), control('pipelines')]));
|
|
186
|
-
let teams = [];
|
|
187
|
-
if (discovery[0].status === 'fulfilled') { ui.teams(discovery[0].value); teams = teamsFromRoles(discovery[0].value); }
|
|
188
|
-
else ui.info('Teams are unavailable. You can retry with aglit teams.');
|
|
189
|
-
if (discovery[1].status === 'fulfilled') ui.pipelines(discovery[1].value);
|
|
190
|
-
else ui.info('Pipeline devices are unavailable. You can retry with aglit pipelines.');
|
|
191
|
-
if (!teamId && teams.length) teamId = await ui.team(teams);
|
|
192
|
-
}
|
|
223
|
+
if (!values.name && interactive) name = await ui.name(state.name || os.hostname());
|
|
193
224
|
name ||= state.name;
|
|
194
|
-
if (!name && interactive) name = await ui.name(os.hostname());
|
|
195
225
|
if (!name) throw Error('Provide --name for unattended setup');
|
|
196
|
-
const
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!provider && available.length && interactive) provider = await ui.provider(available, detected.preferred);
|
|
202
|
-
if (!provider || !available.includes(provider)) throw Error('Sign in to Codex or Claude on this computer, then rerun aglit init --provider codex|claude.');
|
|
203
|
-
ui.info(`AI provider: ${provider}`);
|
|
204
|
-
await ui.step('Save computer settings', () => control('configure', { name, provider, ...(teamId ? { teamId } : {}) }));
|
|
205
|
-
if (process.platform === 'darwin') ui.info('Approve the macOS screen recording and input-control requests. Setup waits for each permission before continuing. Previously denied permissions must be enabled in System Settings.');
|
|
226
|
+
const selected = await require('../src/model-connections.cjs').chooseModelConnection(control, { provider: values.provider, question: modelQuestion });
|
|
227
|
+
for (const warning of selected.view.warnings || []) ui.info(warning);
|
|
228
|
+
await ui.step('Save computer settings', () => control('configure', { name,
|
|
229
|
+
...(selected.connectionId ? { provider: 'aglit', model_connection_id: selected.connectionId } : values.provider && values.provider !== 'auto' ? { provider: values.provider } : {}) }));
|
|
230
|
+
if (process.platform === 'darwin') ui.permissions();
|
|
206
231
|
const permissions = await ui.step('Waiting for screen recording and input permissions', () => control('permissions'));
|
|
207
232
|
if (!interactive) ui.result(permissions);
|
|
208
233
|
if (permissions.permissions?.supported && (permissions.permissions.screenRecording !== 'granted' || permissions.permissions.accessibility !== 'granted'))
|
|
209
234
|
throw Error('Grant Screen Recording and Accessibility in System Settings, then rerun aglit init. Anywhere Access has not been enabled.');
|
|
235
|
+
let browser = await ui.step('Check Aglit AI browser extension', () => control('browser-status'));
|
|
236
|
+
if (!browser.enabled) {
|
|
237
|
+
const prepared = await ui.step('Set up Aglit AI browser extension', () => control('browser-setup'));
|
|
238
|
+
ui.browserExtension(prepared);
|
|
239
|
+
browser = await ui.step('Waiting for Chrome extension installation', async () => {
|
|
240
|
+
const deadline = Date.now() + 180000;
|
|
241
|
+
do {
|
|
242
|
+
const current = await control('browser-status');
|
|
243
|
+
if (current.installed && current.enabled) return current;
|
|
244
|
+
await delay(1500);
|
|
245
|
+
} while (Date.now() < deadline);
|
|
246
|
+
throw Error('Add or enable the Aglit AI Chrome extension, then rerun aglit init.');
|
|
247
|
+
});
|
|
248
|
+
}
|
|
210
249
|
state = await ui.step('Connect this computer to your account', async () => {
|
|
211
250
|
await control('enable');
|
|
212
251
|
const deadline = Date.now() + 30000;
|
|
@@ -217,11 +256,10 @@ async function main() {
|
|
|
217
256
|
} while (Date.now() < deadline);
|
|
218
257
|
throw Error('Anywhere Access is enabled but its connection is not ready. Check aglit status before retrying.');
|
|
219
258
|
});
|
|
220
|
-
if (
|
|
221
|
-
updates.rememberStatus(state);
|
|
259
|
+
if (!installation.development) updates.rememberStatus(state);
|
|
222
260
|
if (typeof state.deviceId !== 'string' || !state.deviceId) throw Error('Registration returned no device ID. Check aglit status before retrying.');
|
|
223
261
|
state = { ...state, employeeUrl: `https://console.aglit.ai/employees/${encodeURIComponent(state.deviceId)}` };
|
|
224
|
-
ui.result(state); ui.finish(
|
|
262
|
+
ui.result(state); ui.finish('Anywhere Access enabled'); return;
|
|
225
263
|
}
|
|
226
264
|
if (command === 'invite') { ui.result(await control('invite', { teamId: values.team, email })); ui.finish('Invitation sent'); return; }
|
|
227
265
|
if (command === 'uninstall') {
|
|
@@ -236,7 +274,8 @@ async function main() {
|
|
|
236
274
|
ui.result(await control(command)); ui.finish('Done');
|
|
237
275
|
}
|
|
238
276
|
|
|
239
|
-
|
|
277
|
+
module.exports = {assertSetupRuntime};
|
|
278
|
+
if (require.main === module) main().catch(error => {
|
|
240
279
|
if (!error.cancelled) process.stderr.write(`aglit: ${error.message}\n`);
|
|
241
280
|
process.exitCode = error.cancelled ? 130 : 1;
|
|
242
281
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglit-ai/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Install and manage Aglit Computer Use",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"src",
|
|
13
13
|
"README.md"
|
|
14
14
|
],
|
|
15
|
+
"os": [
|
|
16
|
+
"darwin"
|
|
17
|
+
],
|
|
18
|
+
"cpu": [
|
|
19
|
+
"arm64"
|
|
20
|
+
],
|
|
15
21
|
"engines": {
|
|
16
22
|
"node": ">=22"
|
|
17
23
|
},
|
package/src/control.cjs
CHANGED
|
@@ -22,7 +22,7 @@ function control(configPath, action, fields = {}) {
|
|
|
22
22
|
const id = crypto.randomUUID();
|
|
23
23
|
const socket = net.createConnection(config.endpoint);
|
|
24
24
|
let buffer = '', authenticated = false, settled = false;
|
|
25
|
-
const timeout = setTimeout(() => finish(Error('Native command timed out; its outcome may be uncertain. Check aglit status before retrying.')), action === 'permissions' ? 5 * 60000 : 30000);
|
|
25
|
+
const timeout = setTimeout(() => finish(Error('Native command timed out; its outcome may be uncertain. Check aglit status before retrying.')), action === 'permissions' ? 5 * 60000 : ['enable','models-test'].includes(action) ? 120000 : 30000);
|
|
26
26
|
const finish = (error, result) => {
|
|
27
27
|
if (settled) return;
|
|
28
28
|
settled = true; clearTimeout(timeout); socket.destroy();
|
package/src/installation.cjs
CHANGED
|
@@ -8,7 +8,8 @@ const { pipeline } = require('node:stream/promises');
|
|
|
8
8
|
const { once } = require('node:events');
|
|
9
9
|
const { createRequire } = require('node:module');
|
|
10
10
|
|
|
11
|
-
const
|
|
11
|
+
const development = process.env.AGLIT_DEV_COMPUTER_USE_ROOT ? require('../../desktop-app/shared/computer-use-development.cjs').readComputerUseDevelopment() : null;
|
|
12
|
+
const root = development?.root || path.join(process.platform === 'darwin' ? path.join(os.homedir(), 'Library/Application Support') :
|
|
12
13
|
process.platform === 'win32' ? process.env.APPDATA || path.join(os.homedir(), 'AppData/Roaming') : path.join(os.homedir(), '.config'), 'Aglit Computer Use');
|
|
13
14
|
const profile = path.join(root, 'profile');
|
|
14
15
|
const receiptPath = path.join(root, 'installation.json');
|
|
@@ -51,9 +52,9 @@ function readInstallation(allowPartialCleanup = false) {
|
|
|
51
52
|
canonical(receiptPath);
|
|
52
53
|
if (fs.statSync(receiptPath).size > 65536) throw Error('Installation receipt too large');
|
|
53
54
|
const value = JSON.parse(fs.readFileSync(receiptPath, 'utf8'));
|
|
54
|
-
if (value.kind !== 'aglit-computer-use-installation-v1' || value.root !== root ||
|
|
55
|
+
if (value.kind !== (development ? 'aglit-computer-use-development-v1' : 'aglit-computer-use-installation-v1') || value.root !== root ||
|
|
55
56
|
value.profile !== profile || typeof value.executable !== 'string' ||
|
|
56
|
-
!value.executable.startsWith(root + path.sep) || !/^[a-f0-9]{64}$/.test(value.sha256)) throw Error('Installation receipt differs');
|
|
57
|
+
(!development && !value.executable.startsWith(root + path.sep)) || !/^[a-f0-9]{64}$/.test(value.sha256)) throw Error('Installation receipt differs');
|
|
57
58
|
if (!allowPartialCleanup) { canonical(value.executable); canonical(value.profile); }
|
|
58
59
|
return value;
|
|
59
60
|
}
|
|
@@ -195,6 +196,7 @@ async function install(channel) {
|
|
|
195
196
|
return { installed: true, ...receipt };
|
|
196
197
|
}
|
|
197
198
|
function start() {
|
|
199
|
+
if (development) throw Error('Start the source runtime with scripts/dev/run-computer-use.cjs before invoking this CLI');
|
|
198
200
|
if (fs.existsSync(path.join(root, 'uninstalling.json'))) throw Error('Computer Use is uninstalling. Run aglit uninstall to finish cleanup.');
|
|
199
201
|
if (process.platform === 'win32') run(powershell, ['-NoLogo', '-NoProfile', '-NonInteractive', '-WindowStyle', 'Hidden', '-Command',
|
|
200
202
|
'$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"}']);
|
|
@@ -262,4 +264,4 @@ function readUninstall() {
|
|
|
262
264
|
!Number.isSafeInteger(receipt.pid) || receipt.pid <= 0) throw Error('Invalid uninstall receipt');
|
|
263
265
|
return receipt;
|
|
264
266
|
}
|
|
265
|
-
module.exports = { root, profile, readInstallation, install, start, purge, purgeIncomplete, readUninstall, privateRoot, canonical, verifyPublisher, embeddedRelease, copyArchive, stageRuntime, verifyRuntime };
|
|
267
|
+
module.exports = { development, root, profile, readInstallation, install, start, purge, purgeIncomplete, readUninstall, privateRoot, canonical, verifyPublisher, embeddedRelease, copyArchive, stageRuntime, verifyRuntime };
|
package/src/ui.cjs
CHANGED
|
@@ -2,17 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const clean = value => String(value ?? '').replace(/[\x00-\x1f\x7f-\x9f]/g, '');
|
|
4
4
|
function teamsFromRoles(value) {
|
|
5
|
-
|
|
6
|
-
for (const company of value?.companies || []) {
|
|
7
|
-
for (const team of [...(company.teams || []), ...(company.organizations || []).flatMap(organization => organization.teams || [])]) {
|
|
8
|
-
if (typeof team.teamId === 'string') teams.set(team.teamId, { id: team.teamId, roles: team.roles || [] });
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
for (const role of value?.roles || []) {
|
|
12
|
-
const id = role.teamId || (role.scopeType === 'team' ? role.scopeId : null);
|
|
13
|
-
if (typeof id === 'string' && !teams.has(id)) teams.set(id, { id, roles: [role.roleLabel || role.role].filter(Boolean) });
|
|
14
|
-
}
|
|
15
|
-
return [...teams.values()];
|
|
5
|
+
return (value?.teams || []).map(team => ({ id: team.teamId, roles: [team.canManage ? 'Administrator' : 'Member'] }));
|
|
16
6
|
}
|
|
17
7
|
async function createUi(interactive) {
|
|
18
8
|
if (!interactive) return {
|
|
@@ -20,6 +10,9 @@ async function createUi(interactive) {
|
|
|
20
10
|
step: (_label, action) => action(),
|
|
21
11
|
result: value => process.stdout.write(JSON.stringify(value, null, 2) + '\n'),
|
|
22
12
|
info: message => process.stderr.write(message + '\n'),
|
|
13
|
+
selection: (label, value) => process.stderr.write(label + ': ' + clean(value) + '\n'),
|
|
14
|
+
permissions: () => process.stderr.write('In System Settings → Privacy & Security, enable Aglit Computer Use in Screen Recording and Accessibility. Keep each pane open until its permission is granted. Previously denied permissions must be enabled there manually.\n'),
|
|
15
|
+
browserExtension: value => process.stderr.write('In Chrome, open '+value.storeUrl+' and choose Add to Chrome. If installed but disabled, enable Aglit AI in chrome://extensions.\n'),
|
|
23
16
|
finish() {},
|
|
24
17
|
};
|
|
25
18
|
const p = await import('@clack/prompts');
|
|
@@ -36,16 +29,11 @@ async function createUi(interactive) {
|
|
|
36
29
|
interactive: true,
|
|
37
30
|
name: initialValue => answer(p.text({ message: 'Name this computer', placeholder: initialValue, defaultValue: initialValue,
|
|
38
31
|
validate: value => value && (!value.trim() || value.length > 100 || /[\x00-\x1f\x7f-\x9f]/.test(value)) ? 'Use 1–100 printable characters.' : undefined })),
|
|
39
|
-
mode: () => answer(p.select({ message: 'How should Aglit run?', initialValue: 'headless', options: [
|
|
40
|
-
{ value: 'headless', label: 'Headless', hint: 'default · background' },
|
|
41
|
-
{ value: 'headed', label: 'Open Desktop window', hint: 'headed' },
|
|
42
|
-
] })),
|
|
43
32
|
provider: (providers, preferred) => answer(p.select({ message: 'Choose your AI provider', initialValue: preferred,
|
|
44
33
|
options: providers.map(value => ({ value, label: value === 'codex' ? 'Codex' : 'Claude' })) })),
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
] })),
|
|
34
|
+
selection: (label, value) => p.log.step(clean(label) + '\n' + clean(value)),
|
|
35
|
+
permissions: () => p.note('Enable Aglit Computer Use in System Settings → Privacy & Security:\n1. Screen Recording (Screen & System Audio Recording on newer macOS)\n2. Accessibility\n\nSetup waits for each grant before opening the next pane.\nPreviously denied permissions must be enabled there manually.\nIf macOS asks you to quit and reopen Aglit Computer Use, do so and rerun aglit init.', 'Allow screen sharing and input'),
|
|
36
|
+
browserExtension: value => p.note('Open this listing in Chrome:\n' + clean(value.storeUrl) + '\n\nChoose Add to Chrome and approve the browser prompt.\nIf already installed but disabled, enable Aglit AI in chrome://extensions.\nSetup continues when the extension is installed and enabled.\nInstallation alone does not verify browser-control connectivity.', 'Add Aglit AI to Chrome'),
|
|
49
37
|
teams(value) {
|
|
50
38
|
const teams = teamsFromRoles(value);
|
|
51
39
|
p.note(teams.length ? teams.map(team => `${clean(team.id)} ${team.roles.map(clean).join(', ')}`).join('\n') : 'No team memberships.', 'Your teams');
|