@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 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 { resolveAigentryConfig } from '../lib/aigentry.js';
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",
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.3"
20
+ "@dmsdc-ai/aterm-darwin-arm64": "0.1.4"
21
21
  },
22
22
  "dependencies": {
23
23
  "@dmsdc-ai/aigentry-brain": "latest",
@@ -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 canUseInteractiveInstaller() {
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
- const defaultPlan = {
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
- async function main() {
231
- maybeReattachTTY();
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
- await main();
155
+ main();
@@ -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 { detectAiCliStatus, resolveInstallHomeDir } from '../lib/aigentry.js';
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.mode === 'local') {
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 runInstallerTui(context) {
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 installer will prepare the requested aigentry config level(s),',
115
- 'write default settings, and stage the native aterm bundle.',
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
- `Detected install mode: ${context.mode}`,
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: () => true,
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 shouldRunInstallerTui() {
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 buildInstallerContext(mode, projectRoot = null) {
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
- mode,
218
+ version,
237
219
  projectRoot,
238
220
  cliStatus,
239
221
  orchestratorPath,
240
222
  orchestratorAvailable: require('node:fs').existsSync(orchestratorPath),
241
- availableLevels,
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
+ }