@dmsdc-ai/aterm 0.1.3 → 0.1.4
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/bin/aterm.js +71 -1
- package/lib/aigentry.js +4 -0
- package/package.json +2 -2
- package/scripts/postinstall.js +5 -115
- package/scripts/tui-installer.js +45 -43
package/bin/aterm.js
CHANGED
|
@@ -3,7 +3,11 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
ensureUserLayout,
|
|
8
|
+
getUserAtermConfigPath,
|
|
9
|
+
resolveAigentryConfig,
|
|
10
|
+
} from '../lib/aigentry.js';
|
|
7
11
|
|
|
8
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
13
|
const __dirname = path.dirname(__filename);
|
|
@@ -11,6 +15,72 @@ const packageRoot = path.resolve(__dirname, '..');
|
|
|
11
15
|
const appRoot = path.join(packageRoot, 'dist', 'aterm.app');
|
|
12
16
|
const executable = path.join(appRoot, 'Contents', 'MacOS', 'aterm');
|
|
13
17
|
const frameworksDir = path.join(appRoot, 'Contents', 'Frameworks');
|
|
18
|
+
const packageJson = JSON.parse(
|
|
19
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
function printVersionAndExit() {
|
|
23
|
+
console.log(packageJson.version);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readUserSetupState() {
|
|
28
|
+
const configPath = getUserAtermConfigPath();
|
|
29
|
+
if (!fs.existsSync(configPath)) {
|
|
30
|
+
return {
|
|
31
|
+
configPath,
|
|
32
|
+
setupCompleted: false,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
38
|
+
return {
|
|
39
|
+
configPath,
|
|
40
|
+
setupCompleted: config.setupCompleted === true,
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.warn(`[aterm] invalid user config, rerunning setup: ${error.message}`);
|
|
44
|
+
return {
|
|
45
|
+
configPath,
|
|
46
|
+
setupCompleted: false,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function maybeRunFirstRunWizard() {
|
|
52
|
+
ensureUserLayout({ version: packageJson.version });
|
|
53
|
+
const setupState = readUserSetupState();
|
|
54
|
+
if (setupState.setupCompleted) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let wizardModule;
|
|
59
|
+
try {
|
|
60
|
+
wizardModule = await import('../scripts/tui-installer.js');
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.warn(`[aterm] setup wizard unavailable, launching with defaults (${error.message})`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!wizardModule.shouldRunFirstRunWizard()) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const context = wizardModule.buildFirstRunWizardContext(packageJson.version, process.cwd());
|
|
71
|
+
const wizardResult = await wizardModule.runFirstRunWizard(context);
|
|
72
|
+
if (!wizardResult) {
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
await wizardModule.completeFirstRunWizard(context, wizardResult);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (process.argv.includes('--version') || process.argv.includes('-v')) {
|
|
80
|
+
printVersionAndExit();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await maybeRunFirstRunWizard();
|
|
14
84
|
const resolvedConfig = resolveAigentryConfig({ cwd: process.cwd() });
|
|
15
85
|
|
|
16
86
|
if (!fs.existsSync(executable)) {
|
package/lib/aigentry.js
CHANGED
|
@@ -246,6 +246,10 @@ export function getLegacyTeleptyShared(homeDir = os.homedir()) {
|
|
|
246
246
|
return path.join(homeDir, '.telepty', 'shared');
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
export function getUserAtermConfigPath(homeDir = os.homedir()) {
|
|
250
|
+
return path.join(getUserAigentryRoot(homeDir), 'config', 'aterm.json');
|
|
251
|
+
}
|
|
252
|
+
|
|
249
253
|
export function resolveInstallHomeDir() {
|
|
250
254
|
return resolveUserHomeFromSudoUser() ?? os.homedir();
|
|
251
255
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aterm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Native aterm launcher package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/aterm.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"package.json"
|
|
18
18
|
],
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@dmsdc-ai/aterm-darwin-arm64": "0.1.
|
|
20
|
+
"@dmsdc-ai/aterm-darwin-arm64": "0.1.4"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@dmsdc-ai/aigentry-brain": "latest",
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { spawnSync } from 'node:child_process';
|
|
5
4
|
import { fileURLToPath } from 'node:url';
|
|
6
5
|
import { createRequire } from 'node:module';
|
|
7
6
|
import {
|
|
@@ -21,9 +20,6 @@ const require = createRequire(import.meta.url);
|
|
|
21
20
|
const packageJson = JSON.parse(
|
|
22
21
|
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
23
22
|
);
|
|
24
|
-
const TTY_REATTACH_FLAG = 'ATERM_POSTINSTALL_TTY_ATTACHED';
|
|
25
|
-
|
|
26
|
-
console.log(`[aterm] postinstall start (${packageJson.version})`);
|
|
27
23
|
|
|
28
24
|
const PLATFORM_PACKAGES = {
|
|
29
25
|
darwin: {
|
|
@@ -35,69 +31,12 @@ function resolvePlatformPackage() {
|
|
|
35
31
|
return PLATFORM_PACKAGES[process.platform]?.[process.arch] ?? null;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
function
|
|
39
|
-
if (process.env.CI) {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
if (process.env.npm_config_yes === 'true') {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function maybeReattachTTY() {
|
|
49
|
-
if (!canUseInteractiveInstaller()) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (process.env[TTY_REATTACH_FLAG] === '1') {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (process.platform === 'win32') {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
let ttyIn;
|
|
63
|
-
let ttyOut;
|
|
64
|
-
let ttyErr;
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
ttyIn = fs.openSync('/dev/tty', 'r');
|
|
68
|
-
ttyOut = fs.openSync('/dev/tty', 'w');
|
|
69
|
-
ttyErr = fs.openSync('/dev/tty', 'w');
|
|
70
|
-
} catch {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
console.log('[aterm] reattaching postinstall to /dev/tty for interactive installer');
|
|
75
|
-
const result = spawnSync(process.execPath, [__filename], {
|
|
76
|
-
cwd: process.cwd(),
|
|
77
|
-
env: {
|
|
78
|
-
...process.env,
|
|
79
|
-
[TTY_REATTACH_FLAG]: '1',
|
|
80
|
-
},
|
|
81
|
-
stdio: [ttyIn, ttyOut, ttyErr],
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
fs.closeSync(ttyIn);
|
|
85
|
-
fs.closeSync(ttyOut);
|
|
86
|
-
fs.closeSync(ttyErr);
|
|
87
|
-
|
|
88
|
-
if (result.status !== 0) {
|
|
89
|
-
process.exit(result.status ?? 1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
process.exit(0);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function collectInstallerPlan() {
|
|
34
|
+
function buildDefaultPlan() {
|
|
96
35
|
const mode = isGlobalInstall() ? 'global' : 'local';
|
|
97
36
|
const installRoot = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : process.cwd();
|
|
98
37
|
const projectRoot = mode === 'local' ? resolveProjectRoot(installRoot) : null;
|
|
99
38
|
|
|
100
|
-
|
|
39
|
+
return {
|
|
101
40
|
installLevels: mode === 'global' ? ['system', 'user'] : ['project'],
|
|
102
41
|
configPatch: {
|
|
103
42
|
shell: { default: 'zsh' },
|
|
@@ -119,25 +58,6 @@ async function collectInstallerPlan() {
|
|
|
119
58
|
mode,
|
|
120
59
|
projectRoot,
|
|
121
60
|
};
|
|
122
|
-
|
|
123
|
-
let tuiModule;
|
|
124
|
-
try {
|
|
125
|
-
tuiModule = await import('./tui-installer.js');
|
|
126
|
-
} catch (error) {
|
|
127
|
-
console.warn(`[aterm] installer TUI unavailable, falling back to defaults (${error.message})`);
|
|
128
|
-
return defaultPlan;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!tuiModule.shouldRunInstallerTui()) {
|
|
132
|
-
return defaultPlan;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const context = tuiModule.buildInstallerContext(mode, projectRoot);
|
|
136
|
-
const plan = await tuiModule.runInstallerTui(context);
|
|
137
|
-
return {
|
|
138
|
-
...defaultPlan,
|
|
139
|
-
...plan,
|
|
140
|
-
};
|
|
141
61
|
}
|
|
142
62
|
|
|
143
63
|
function applyInstallPlan(plan, progress) {
|
|
@@ -224,42 +144,12 @@ function installNativeBundle() {
|
|
|
224
144
|
fs.mkdirSync(path.dirname(targetApp), { recursive: true });
|
|
225
145
|
fs.cpSync(sourceApp, targetApp, { recursive: true });
|
|
226
146
|
|
|
227
|
-
console.log(`[aterm] installed native bundle from ${platformPackage}`);
|
|
228
147
|
}
|
|
229
148
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const plan = await collectInstallerPlan();
|
|
233
|
-
|
|
234
|
-
let tuiModule = null;
|
|
235
|
-
try {
|
|
236
|
-
tuiModule = await import('./tui-installer.js');
|
|
237
|
-
} catch {
|
|
238
|
-
tuiModule = null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (tuiModule?.shouldRunInstallerTui()) {
|
|
242
|
-
await tuiModule.showProgressScreen(async (progress) => {
|
|
243
|
-
applyInstallPlan(plan, progress);
|
|
244
|
-
progress('{bold}Staging native bundle{/bold}');
|
|
245
|
-
installNativeBundle();
|
|
246
|
-
progress('{green-fg}ok{/} native bundle ready');
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
await tuiModule.showDoneScreen([
|
|
250
|
-
'aterm setup is complete.',
|
|
251
|
-
'',
|
|
252
|
-
`Mode: ${plan.mode}`,
|
|
253
|
-
`Levels: ${plan.installLevels.join(', ')}`,
|
|
254
|
-
`Shell: ${plan.configPatch.shell?.default ?? 'zsh'}`,
|
|
255
|
-
`Workspace: ${plan.configPatch.workspace?.default ?? 'home'}`,
|
|
256
|
-
`Tailscale connect: ${plan.configPatch.tailscale?.connect_on_launch ? 'yes' : 'no'}`,
|
|
257
|
-
]);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
|
|
149
|
+
function main() {
|
|
150
|
+
const plan = buildDefaultPlan();
|
|
261
151
|
applyInstallPlan(plan);
|
|
262
152
|
installNativeBundle();
|
|
263
153
|
}
|
|
264
154
|
|
|
265
|
-
|
|
155
|
+
main();
|
package/scripts/tui-installer.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
detectAiCliStatus,
|
|
5
|
+
resolveInstallHomeDir,
|
|
6
|
+
resolveProjectRoot,
|
|
7
|
+
updateConfigFile,
|
|
8
|
+
} from '../lib/aigentry.js';
|
|
5
9
|
|
|
6
10
|
const require = createRequire(import.meta.url);
|
|
7
11
|
|
|
@@ -56,7 +60,7 @@ function buildWorkspaceChoices(context) {
|
|
|
56
60
|
});
|
|
57
61
|
}
|
|
58
62
|
|
|
59
|
-
if (context.
|
|
63
|
+
if (context.projectRoot) {
|
|
60
64
|
choices.push({
|
|
61
65
|
title: 'current project',
|
|
62
66
|
value: 'project',
|
|
@@ -101,7 +105,7 @@ function buildConfigPatch(context, answers) {
|
|
|
101
105
|
};
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
export async function
|
|
108
|
+
export async function runFirstRunWizard(context) {
|
|
105
109
|
const blessed = require('blessed');
|
|
106
110
|
const prompts = (await import('prompts')).default;
|
|
107
111
|
|
|
@@ -111,10 +115,10 @@ export async function runInstallerTui(context) {
|
|
|
111
115
|
[
|
|
112
116
|
'{center}{bold}Aigentry Terminal Installer{/bold}{/center}',
|
|
113
117
|
'',
|
|
114
|
-
'This
|
|
115
|
-
'
|
|
118
|
+
'This wizard finishes your aterm setup before the native app launches.',
|
|
119
|
+
'It will save your preferred shell, workspace, and Tailscale defaults.',
|
|
116
120
|
'',
|
|
117
|
-
`
|
|
121
|
+
`Package version: ${context.version}`,
|
|
118
122
|
],
|
|
119
123
|
'{gray-fg}Press Enter to continue{/}',
|
|
120
124
|
);
|
|
@@ -136,23 +140,14 @@ export async function runInstallerTui(context) {
|
|
|
136
140
|
|
|
137
141
|
const workspaceChoices = buildWorkspaceChoices(context);
|
|
138
142
|
const defaultResponses = {
|
|
139
|
-
installLevels: context.availableLevels.map(level => level.value),
|
|
140
143
|
defaultShell: 'zsh',
|
|
141
144
|
defaultWorkspace: workspaceChoices[0]?.value ?? 'home',
|
|
142
145
|
tailscaleConnect: false,
|
|
143
146
|
};
|
|
147
|
+
let cancelled = false;
|
|
144
148
|
|
|
145
149
|
const responses = await prompts(
|
|
146
150
|
[
|
|
147
|
-
{
|
|
148
|
-
type: 'multiselect',
|
|
149
|
-
name: 'installLevels',
|
|
150
|
-
message: 'Install level selection',
|
|
151
|
-
choices: context.availableLevels,
|
|
152
|
-
initial: 0,
|
|
153
|
-
instructions: false,
|
|
154
|
-
min: 1,
|
|
155
|
-
},
|
|
156
151
|
{
|
|
157
152
|
type: 'select',
|
|
158
153
|
name: 'defaultShell',
|
|
@@ -181,23 +176,29 @@ export async function runInstallerTui(context) {
|
|
|
181
176
|
},
|
|
182
177
|
],
|
|
183
178
|
{
|
|
184
|
-
onCancel: () =>
|
|
179
|
+
onCancel: () => {
|
|
180
|
+
cancelled = true;
|
|
181
|
+
return false;
|
|
182
|
+
},
|
|
185
183
|
},
|
|
186
184
|
);
|
|
187
185
|
|
|
186
|
+
if (cancelled) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
188
190
|
const mergedAnswers = {
|
|
189
191
|
...defaultResponses,
|
|
190
192
|
...responses,
|
|
191
193
|
};
|
|
192
194
|
|
|
193
195
|
return {
|
|
194
|
-
installLevels: mergedAnswers.installLevels,
|
|
195
196
|
configPatch: buildConfigPatch(context, mergedAnswers),
|
|
196
197
|
summary: mergedAnswers,
|
|
197
198
|
};
|
|
198
199
|
}
|
|
199
200
|
|
|
200
|
-
export function
|
|
201
|
+
export function shouldRunFirstRunWizard() {
|
|
201
202
|
if (process.env.CI) {
|
|
202
203
|
return false;
|
|
203
204
|
}
|
|
@@ -207,38 +208,19 @@ export function shouldRunInstallerTui() {
|
|
|
207
208
|
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
208
209
|
}
|
|
209
210
|
|
|
210
|
-
export function
|
|
211
|
+
export function buildFirstRunWizardContext(version, cwd = process.cwd()) {
|
|
211
212
|
const installHome = resolveInstallHomeDir();
|
|
213
|
+
const projectRoot = resolveProjectRoot(cwd);
|
|
212
214
|
const orchestratorPath = path.join(installHome, 'projects', 'aigentry-orchestrator');
|
|
213
215
|
const cliStatus = detectAiCliStatus(installHome);
|
|
214
|
-
const availableLevels = mode === 'global'
|
|
215
|
-
? [
|
|
216
|
-
{
|
|
217
|
-
title: 'system',
|
|
218
|
-
value: 'system',
|
|
219
|
-
description: 'Create /etc/aigentry/config/aterm.json when writable',
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
title: 'user',
|
|
223
|
-
value: 'user',
|
|
224
|
-
description: `Create ${path.join(installHome, '.aigentry')}`,
|
|
225
|
-
},
|
|
226
|
-
]
|
|
227
|
-
: [
|
|
228
|
-
{
|
|
229
|
-
title: 'project',
|
|
230
|
-
value: 'project',
|
|
231
|
-
description: `Create ${path.join(projectRoot ?? process.cwd(), '.aigentry')}`,
|
|
232
|
-
},
|
|
233
|
-
];
|
|
234
216
|
|
|
235
217
|
return {
|
|
236
|
-
|
|
218
|
+
version,
|
|
237
219
|
projectRoot,
|
|
238
220
|
cliStatus,
|
|
239
221
|
orchestratorPath,
|
|
240
222
|
orchestratorAvailable: require('node:fs').existsSync(orchestratorPath),
|
|
241
|
-
|
|
223
|
+
userConfigPath: path.join(installHome, '.aigentry', 'config', 'aterm.json'),
|
|
242
224
|
};
|
|
243
225
|
}
|
|
244
226
|
|
|
@@ -288,3 +270,23 @@ export async function showDoneScreen(summaryLines) {
|
|
|
288
270
|
'{green-fg}Run: aterm{/}',
|
|
289
271
|
);
|
|
290
272
|
}
|
|
273
|
+
|
|
274
|
+
export async function completeFirstRunWizard(context, wizardResult) {
|
|
275
|
+
const blessed = require('blessed');
|
|
276
|
+
await showProgressScreen(async (progress) => {
|
|
277
|
+
progress('{bold}Saving setup{/bold}');
|
|
278
|
+
updateConfigFile(context.userConfigPath, {
|
|
279
|
+
...wizardResult.configPatch,
|
|
280
|
+
setupCompleted: true,
|
|
281
|
+
});
|
|
282
|
+
progress(`{green-fg}ok{/} ${context.userConfigPath}`);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await showDoneScreen([
|
|
286
|
+
'aterm setup is complete.',
|
|
287
|
+
'',
|
|
288
|
+
`Shell: ${wizardResult.summary.defaultShell}`,
|
|
289
|
+
`Workspace: ${wizardResult.summary.defaultWorkspace}`,
|
|
290
|
+
`Tailscale connect: ${wizardResult.summary.tailscaleConnect ? 'yes' : 'no'}`,
|
|
291
|
+
]);
|
|
292
|
+
}
|