@dmsdc-ai/aterm 0.1.1 → 0.1.2
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 +12 -0
- package/lib/aigentry.js +425 -0
- package/package.json +12 -4
- package/scripts/postinstall.js +203 -0
- package/scripts/tui-installer.js +290 -0
- package/install.js +0 -82
package/bin/aterm.js
CHANGED
|
@@ -3,6 +3,7 @@ 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
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
@@ -10,6 +11,7 @@ const packageRoot = path.resolve(__dirname, '..');
|
|
|
10
11
|
const appRoot = path.join(packageRoot, 'dist', 'aterm.app');
|
|
11
12
|
const executable = path.join(appRoot, 'Contents', 'MacOS', 'aterm');
|
|
12
13
|
const frameworksDir = path.join(appRoot, 'Contents', 'Frameworks');
|
|
14
|
+
const resolvedConfig = resolveAigentryConfig({ cwd: process.cwd() });
|
|
13
15
|
|
|
14
16
|
if (!fs.existsSync(executable)) {
|
|
15
17
|
console.error('[aterm] native app bundle is not installed');
|
|
@@ -17,10 +19,20 @@ if (!fs.existsSync(executable)) {
|
|
|
17
19
|
process.exit(1);
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
for (const warning of resolvedConfig.warnings) {
|
|
23
|
+
console.warn(warning);
|
|
24
|
+
}
|
|
25
|
+
|
|
20
26
|
const env = { ...process.env };
|
|
21
27
|
env.DYLD_LIBRARY_PATH = [frameworksDir, env.DYLD_LIBRARY_PATH]
|
|
22
28
|
.filter(Boolean)
|
|
23
29
|
.join(':');
|
|
30
|
+
env.AIGENTRY_CONFIG_JSON = JSON.stringify(resolvedConfig.config);
|
|
31
|
+
env.AIGENTRY_CONFIG_LAYERS = JSON.stringify(resolvedConfig.layers);
|
|
32
|
+
env.AIGENTRY_SYSTEM_ROOT = resolvedConfig.systemRoot;
|
|
33
|
+
env.AIGENTRY_USER_ROOT = resolvedConfig.userRoot;
|
|
34
|
+
env.AIGENTRY_PROJECT_ROOT = resolvedConfig.projectRoot;
|
|
35
|
+
env.AIGENTRY_PROJECT_AIGENTRY_ROOT = resolvedConfig.projectAigentryRoot;
|
|
24
36
|
|
|
25
37
|
const child = spawn(executable, process.argv.slice(2), {
|
|
26
38
|
stdio: 'inherit',
|
package/lib/aigentry.js
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
const SYSTEM_ROOT = process.env.AIGENTRY_SYSTEM_ROOT || '/etc/aigentry';
|
|
7
|
+
|
|
8
|
+
const TASK_QUEUE_DEFAULT = {
|
|
9
|
+
main_topics: [],
|
|
10
|
+
tasks: [],
|
|
11
|
+
completed: [],
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const LESSONS_DEFAULT = {
|
|
15
|
+
_meta: { version: 1 },
|
|
16
|
+
aterm: {
|
|
17
|
+
invariants: [],
|
|
18
|
+
failed: [],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const PROJECT_AGENTS_TEMPLATE = `# Project AI Instructions
|
|
23
|
+
|
|
24
|
+
- Add project-specific aigentry instructions here.
|
|
25
|
+
- This file is project-scoped and overrides user/system defaults when present.
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
function cloneJsonValue(value) {
|
|
29
|
+
return JSON.parse(JSON.stringify(value));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isPlainObject(value) {
|
|
33
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function mergeJson(base, override) {
|
|
37
|
+
if (!isPlainObject(base)) {
|
|
38
|
+
return cloneJsonValue(override);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const merged = { ...cloneJsonValue(base) };
|
|
42
|
+
for (const [key, value] of Object.entries(override)) {
|
|
43
|
+
if (isPlainObject(value) && isPlainObject(merged[key])) {
|
|
44
|
+
merged[key] = mergeJson(merged[key], value);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
merged[key] = cloneJsonValue(value);
|
|
48
|
+
}
|
|
49
|
+
return merged;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function ensureDirectory(directoryPath) {
|
|
53
|
+
const existed = fs.existsSync(directoryPath);
|
|
54
|
+
fs.mkdirSync(directoryPath, { recursive: true });
|
|
55
|
+
return !existed;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function ensureJsonFile(filePath, defaultValue) {
|
|
59
|
+
ensureDirectory(path.dirname(filePath));
|
|
60
|
+
if (fs.existsSync(filePath)) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
fs.writeFileSync(filePath, `${JSON.stringify(defaultValue, null, 2)}\n`);
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ensureTextFile(filePath, contents) {
|
|
69
|
+
ensureDirectory(path.dirname(filePath));
|
|
70
|
+
if (fs.existsSync(filePath)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(filePath, contents);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readJsonFile(filePath, warnings) {
|
|
79
|
+
if (!fs.existsSync(filePath)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
warnings.push(`[aterm] invalid JSON config skipped: ${filePath} (${error.message})`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveUserHomeFromSudoUser() {
|
|
92
|
+
const sudoUser = process.env.SUDO_USER;
|
|
93
|
+
if (!sudoUser || sudoUser === 'root' || !/^[A-Za-z0-9_.-]+$/.test(sudoUser)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = spawnSync('/bin/sh', ['-lc', `printf %s ~${sudoUser}`], {
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
100
|
+
});
|
|
101
|
+
if (result.status !== 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const resolved = result.stdout.trim();
|
|
106
|
+
if (!resolved || resolved === `~${sudoUser}`) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return resolved;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function commandAvailable(command) {
|
|
114
|
+
const result = spawnSync('/bin/sh', ['-lc', `command -v ${command} >/dev/null 2>&1`], {
|
|
115
|
+
stdio: 'ignore',
|
|
116
|
+
});
|
|
117
|
+
return result.status === 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function findGitRoot(startDir) {
|
|
121
|
+
const resolvedStartDir = path.resolve(startDir);
|
|
122
|
+
const result = spawnSync('git', ['rev-parse', '--show-toplevel'], {
|
|
123
|
+
cwd: resolvedStartDir,
|
|
124
|
+
encoding: 'utf8',
|
|
125
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (result.status !== 0) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const gitRoot = result.stdout.trim();
|
|
133
|
+
return gitRoot ? path.resolve(gitRoot) : null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function buildLevelPaths(aigentryRoot, options = {}) {
|
|
137
|
+
const paths = {
|
|
138
|
+
root: aigentryRoot,
|
|
139
|
+
config: path.join(aigentryRoot, 'config'),
|
|
140
|
+
data: path.join(aigentryRoot, 'data'),
|
|
141
|
+
brain_local: path.join(aigentryRoot, 'brain', 'local'),
|
|
142
|
+
brain_profiles: path.join(aigentryRoot, 'brain', 'profiles'),
|
|
143
|
+
inbox: path.join(aigentryRoot, 'inbox'),
|
|
144
|
+
telepty_shared: path.join(aigentryRoot, 'telepty', 'shared'),
|
|
145
|
+
cache_search: path.join(aigentryRoot, 'cache', 'search'),
|
|
146
|
+
logs: path.join(aigentryRoot, 'logs'),
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (options.legacyTeleptyShared) {
|
|
150
|
+
paths.legacy_telepty_shared = options.legacyTeleptyShared;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return paths;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildDefaultConfig(options) {
|
|
157
|
+
const config = {
|
|
158
|
+
version: options.version,
|
|
159
|
+
level: options.level,
|
|
160
|
+
installed_at: new Date().toISOString(),
|
|
161
|
+
platform: `${os.platform()}-${os.arch()}`,
|
|
162
|
+
paths: buildLevelPaths(options.aigentryRoot, {
|
|
163
|
+
legacyTeleptyShared: options.legacyTeleptyShared,
|
|
164
|
+
}),
|
|
165
|
+
shell: {
|
|
166
|
+
default: options.defaultShell ?? 'zsh',
|
|
167
|
+
},
|
|
168
|
+
workspace: {
|
|
169
|
+
default: options.defaultWorkspace ?? 'home',
|
|
170
|
+
},
|
|
171
|
+
orchestrator: {
|
|
172
|
+
enabled: false,
|
|
173
|
+
path: path.join(os.homedir(), 'projects', 'aigentry-orchestrator'),
|
|
174
|
+
},
|
|
175
|
+
tailscale: {
|
|
176
|
+
connect_on_launch: false,
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
if (options.projectRoot) {
|
|
181
|
+
config.project_root = options.projectRoot;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return config;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function ensureStructuredRoot(aigentryRoot, options) {
|
|
188
|
+
const reportProgress = options.onProgress ?? (() => {});
|
|
189
|
+
const directories = [
|
|
190
|
+
path.join(aigentryRoot, 'config'),
|
|
191
|
+
path.join(aigentryRoot, 'data'),
|
|
192
|
+
path.join(aigentryRoot, 'brain', 'local'),
|
|
193
|
+
path.join(aigentryRoot, 'brain', 'profiles'),
|
|
194
|
+
path.join(aigentryRoot, 'inbox'),
|
|
195
|
+
path.join(aigentryRoot, 'telepty', 'shared'),
|
|
196
|
+
path.join(aigentryRoot, 'cache', 'search'),
|
|
197
|
+
path.join(aigentryRoot, 'logs'),
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
for (const directoryPath of directories) {
|
|
201
|
+
ensureDirectory(directoryPath);
|
|
202
|
+
reportProgress({ type: 'dir', path: directoryPath });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (options.legacyTeleptyShared) {
|
|
206
|
+
ensureDirectory(options.legacyTeleptyShared);
|
|
207
|
+
reportProgress({ type: 'dir', path: options.legacyTeleptyShared });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const configPath = path.join(aigentryRoot, 'config', 'aterm.json');
|
|
211
|
+
const taskQueuePath = path.join(aigentryRoot, 'data', 'task-queue.json');
|
|
212
|
+
const lessonsPath = path.join(aigentryRoot, 'data', 'lessons.json');
|
|
213
|
+
ensureJsonFile(configPath, buildDefaultConfig(options));
|
|
214
|
+
reportProgress({ type: 'file', path: configPath });
|
|
215
|
+
ensureJsonFile(taskQueuePath, TASK_QUEUE_DEFAULT);
|
|
216
|
+
reportProgress({ type: 'file', path: taskQueuePath });
|
|
217
|
+
ensureJsonFile(lessonsPath, LESSONS_DEFAULT);
|
|
218
|
+
reportProgress({ type: 'file', path: lessonsPath });
|
|
219
|
+
|
|
220
|
+
if (options.createAgentsFile) {
|
|
221
|
+
const agentsPath = path.join(aigentryRoot, 'AGENTS.md');
|
|
222
|
+
ensureTextFile(agentsPath, PROJECT_AGENTS_TEMPLATE);
|
|
223
|
+
reportProgress({ type: 'file', path: agentsPath });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (options.configPatch) {
|
|
227
|
+
updateConfigFile(configPath, options.configPatch);
|
|
228
|
+
reportProgress({ type: 'file', path: configPath, patched: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
aigentryRoot,
|
|
233
|
+
configPath,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getSystemAigentryRoot() {
|
|
238
|
+
return SYSTEM_ROOT;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function getUserAigentryRoot(homeDir = os.homedir()) {
|
|
242
|
+
return path.join(homeDir, '.aigentry');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function getLegacyTeleptyShared(homeDir = os.homedir()) {
|
|
246
|
+
return path.join(homeDir, '.telepty', 'shared');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function resolveInstallHomeDir() {
|
|
250
|
+
return resolveUserHomeFromSudoUser() ?? os.homedir();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function resolveProjectRoot(startDir = process.cwd()) {
|
|
254
|
+
return findGitRoot(startDir) ?? path.resolve(startDir);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function isGlobalInstall(env = process.env) {
|
|
258
|
+
return env.npm_config_global === 'true';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function detectAiCliStatus(homeDir = os.homedir()) {
|
|
262
|
+
return {
|
|
263
|
+
claude: commandAvailable('claude') && fs.existsSync(path.join(homeDir, '.claude')),
|
|
264
|
+
codex: commandAvailable('codex') && fs.existsSync(path.join(homeDir, '.codex')),
|
|
265
|
+
gemini: commandAvailable('gemini') && fs.existsSync(path.join(homeDir, '.gemini')),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function updateConfigFile(configPath, patch) {
|
|
270
|
+
const existing = fs.existsSync(configPath)
|
|
271
|
+
? JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
272
|
+
: {};
|
|
273
|
+
const merged = mergeJson(existing, patch);
|
|
274
|
+
fs.writeFileSync(configPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
275
|
+
return merged;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function ensureSystemLayout(options) {
|
|
279
|
+
const systemRoot = getSystemAigentryRoot();
|
|
280
|
+
const configPath = path.join(systemRoot, 'config', 'aterm.json');
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
ensureDirectory(path.dirname(configPath));
|
|
284
|
+
ensureJsonFile(
|
|
285
|
+
configPath,
|
|
286
|
+
buildDefaultConfig({
|
|
287
|
+
level: 'system',
|
|
288
|
+
version: options.version,
|
|
289
|
+
aigentryRoot: systemRoot,
|
|
290
|
+
}),
|
|
291
|
+
);
|
|
292
|
+
return {
|
|
293
|
+
skipped: false,
|
|
294
|
+
aigentryRoot: systemRoot,
|
|
295
|
+
configPath,
|
|
296
|
+
};
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (error.code === 'EACCES' || error.code === 'EPERM') {
|
|
299
|
+
return {
|
|
300
|
+
skipped: true,
|
|
301
|
+
reason: `${systemRoot} is not writable`,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function ensureUserLayout(options) {
|
|
309
|
+
const homeDir = options.homeDir ?? resolveInstallHomeDir();
|
|
310
|
+
const userRoot = getUserAigentryRoot(homeDir);
|
|
311
|
+
return ensureStructuredRoot(userRoot, {
|
|
312
|
+
level: 'user',
|
|
313
|
+
version: options.version,
|
|
314
|
+
aigentryRoot: userRoot,
|
|
315
|
+
legacyTeleptyShared: getLegacyTeleptyShared(homeDir),
|
|
316
|
+
defaultShell: options.defaultShell,
|
|
317
|
+
defaultWorkspace: options.defaultWorkspace,
|
|
318
|
+
configPatch: options.configPatch,
|
|
319
|
+
onProgress: options.onProgress,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function addGitignoreSuggestion(projectRoot) {
|
|
324
|
+
const gitRoot = findGitRoot(projectRoot);
|
|
325
|
+
if (!gitRoot) {
|
|
326
|
+
return {
|
|
327
|
+
updated: false,
|
|
328
|
+
gitignorePath: null,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const gitignorePath = path.join(gitRoot, '.gitignore');
|
|
333
|
+
const entryPattern = /^\s*(?:\/?\.aigentry\/)\s*$/m;
|
|
334
|
+
const currentContents = fs.existsSync(gitignorePath)
|
|
335
|
+
? fs.readFileSync(gitignorePath, 'utf8')
|
|
336
|
+
: '';
|
|
337
|
+
|
|
338
|
+
if (entryPattern.test(currentContents)) {
|
|
339
|
+
return {
|
|
340
|
+
updated: false,
|
|
341
|
+
gitignorePath,
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const prefix = currentContents && !currentContents.endsWith('\n') ? '\n' : '';
|
|
346
|
+
fs.writeFileSync(
|
|
347
|
+
gitignorePath,
|
|
348
|
+
`${currentContents}${prefix}# aterm project config\n.aigentry/\n`,
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
updated: true,
|
|
353
|
+
gitignorePath,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function ensureProjectLayout(options) {
|
|
358
|
+
const projectRoot = resolveProjectRoot(options.startDir);
|
|
359
|
+
const aigentryRoot = path.join(projectRoot, '.aigentry');
|
|
360
|
+
const layout = ensureStructuredRoot(aigentryRoot, {
|
|
361
|
+
level: 'project',
|
|
362
|
+
version: options.version,
|
|
363
|
+
aigentryRoot,
|
|
364
|
+
projectRoot,
|
|
365
|
+
createAgentsFile: true,
|
|
366
|
+
defaultShell: options.defaultShell,
|
|
367
|
+
defaultWorkspace: options.defaultWorkspace,
|
|
368
|
+
configPatch: options.configPatch,
|
|
369
|
+
onProgress: options.onProgress,
|
|
370
|
+
});
|
|
371
|
+
const gitignore = addGitignoreSuggestion(projectRoot);
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
...layout,
|
|
375
|
+
projectRoot,
|
|
376
|
+
gitignoreUpdated: gitignore.updated,
|
|
377
|
+
gitignorePath: gitignore.gitignorePath,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function resolveAigentryConfig(options = {}) {
|
|
382
|
+
const warnings = [];
|
|
383
|
+
const currentWorkingDirectory = options.cwd ?? process.cwd();
|
|
384
|
+
const userRoot = getUserAigentryRoot();
|
|
385
|
+
const projectRoot = resolveProjectRoot(currentWorkingDirectory);
|
|
386
|
+
const layerCandidates = [
|
|
387
|
+
{
|
|
388
|
+
level: 'system',
|
|
389
|
+
path: path.join(getSystemAigentryRoot(), 'config', 'aterm.json'),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
level: 'user',
|
|
393
|
+
path: path.join(userRoot, 'config', 'aterm.json'),
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
level: 'project',
|
|
397
|
+
path: path.join(projectRoot, '.aigentry', 'config', 'aterm.json'),
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
let mergedConfig = {};
|
|
402
|
+
const layers = [];
|
|
403
|
+
|
|
404
|
+
for (const layer of layerCandidates) {
|
|
405
|
+
const data = readJsonFile(layer.path, warnings);
|
|
406
|
+
if (!data) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
mergedConfig = mergeJson(mergedConfig, data);
|
|
410
|
+
layers.push({
|
|
411
|
+
level: layer.level,
|
|
412
|
+
path: layer.path,
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
config: mergedConfig,
|
|
418
|
+
layers,
|
|
419
|
+
warnings,
|
|
420
|
+
systemRoot: getSystemAigentryRoot(),
|
|
421
|
+
userRoot,
|
|
422
|
+
projectRoot,
|
|
423
|
+
projectAigentryRoot: path.join(projectRoot, '.aigentry'),
|
|
424
|
+
};
|
|
425
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dmsdc-ai/aterm",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Native aterm launcher package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./bin/aterm.js",
|
|
@@ -8,15 +8,23 @@
|
|
|
8
8
|
"aterm": "bin/aterm.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"postinstall": "node
|
|
11
|
+
"postinstall": "node scripts/postinstall.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"bin",
|
|
15
|
-
"
|
|
15
|
+
"lib",
|
|
16
|
+
"scripts",
|
|
16
17
|
"package.json"
|
|
17
18
|
],
|
|
18
19
|
"optionalDependencies": {
|
|
19
|
-
"@dmsdc-ai/aterm-darwin-arm64": "0.1.
|
|
20
|
+
"@dmsdc-ai/aterm-darwin-arm64": "0.1.2"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@dmsdc-ai/aigentry-brain": "latest",
|
|
24
|
+
"@dmsdc-ai/aigentry-deliberation": "latest",
|
|
25
|
+
"@dmsdc-ai/aigentry-telepty": "latest",
|
|
26
|
+
"blessed": "^0.1.81",
|
|
27
|
+
"prompts": "^2.4.2"
|
|
20
28
|
},
|
|
21
29
|
"license": "UNLICENSED",
|
|
22
30
|
"publishConfig": {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import {
|
|
7
|
+
detectAiCliStatus,
|
|
8
|
+
ensureProjectLayout,
|
|
9
|
+
ensureSystemLayout,
|
|
10
|
+
ensureUserLayout,
|
|
11
|
+
isGlobalInstall,
|
|
12
|
+
resolveInstallHomeDir,
|
|
13
|
+
resolveProjectRoot,
|
|
14
|
+
} from '../lib/aigentry.js';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
const packageJson = JSON.parse(
|
|
21
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const PLATFORM_PACKAGES = {
|
|
25
|
+
darwin: {
|
|
26
|
+
arm64: '@dmsdc-ai/aterm-darwin-arm64',
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function resolvePlatformPackage() {
|
|
31
|
+
return PLATFORM_PACKAGES[process.platform]?.[process.arch] ?? null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function collectInstallerPlan() {
|
|
35
|
+
const mode = isGlobalInstall() ? 'global' : 'local';
|
|
36
|
+
const installRoot = process.env.INIT_CWD ? path.resolve(process.env.INIT_CWD) : process.cwd();
|
|
37
|
+
const projectRoot = mode === 'local' ? resolveProjectRoot(installRoot) : null;
|
|
38
|
+
|
|
39
|
+
const defaultPlan = {
|
|
40
|
+
installLevels: mode === 'global' ? ['system', 'user'] : ['project'],
|
|
41
|
+
configPatch: {
|
|
42
|
+
shell: { default: 'zsh' },
|
|
43
|
+
workspace: {
|
|
44
|
+
default: mode === 'local' ? 'project' : 'home',
|
|
45
|
+
path: mode === 'local' ? projectRoot : resolveInstallHomeDir(),
|
|
46
|
+
},
|
|
47
|
+
orchestrator: {
|
|
48
|
+
enabled: false,
|
|
49
|
+
path: path.join(resolveInstallHomeDir(), 'projects', 'aigentry-orchestrator'),
|
|
50
|
+
},
|
|
51
|
+
tailscale: {
|
|
52
|
+
connect_on_launch: false,
|
|
53
|
+
},
|
|
54
|
+
ai: {
|
|
55
|
+
detected_clis: detectAiCliStatus(resolveInstallHomeDir()),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
mode,
|
|
59
|
+
projectRoot,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let tuiModule;
|
|
63
|
+
try {
|
|
64
|
+
tuiModule = await import('./tui-installer.js');
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.warn(`[aterm] installer TUI unavailable, falling back to defaults (${error.message})`);
|
|
67
|
+
return defaultPlan;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!tuiModule.shouldRunInstallerTui()) {
|
|
71
|
+
return defaultPlan;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const context = tuiModule.buildInstallerContext(mode, projectRoot);
|
|
75
|
+
const plan = await tuiModule.runInstallerTui(context);
|
|
76
|
+
return {
|
|
77
|
+
...defaultPlan,
|
|
78
|
+
...plan,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function applyInstallPlan(plan, progress) {
|
|
83
|
+
const onProgress = (event) => {
|
|
84
|
+
if (!progress) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (event.type === 'dir') {
|
|
88
|
+
progress(`{green-fg}dir{/} ${event.path}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
progress(`{cyan-fg}file{/} ${event.path}`);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
if (plan.installLevels.includes('system')) {
|
|
95
|
+
progress?.('{bold}Preparing system level{/bold}');
|
|
96
|
+
const systemLayout = ensureSystemLayout({
|
|
97
|
+
version: packageJson.version,
|
|
98
|
+
});
|
|
99
|
+
if (systemLayout.skipped) {
|
|
100
|
+
progress?.(`{yellow-fg}skip{/} system: ${systemLayout.reason}`);
|
|
101
|
+
} else {
|
|
102
|
+
progress?.(`{green-fg}ok{/} ${systemLayout.configPath}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (plan.installLevels.includes('user')) {
|
|
107
|
+
progress?.('{bold}Preparing user level{/bold}');
|
|
108
|
+
const userLayout = ensureUserLayout({
|
|
109
|
+
version: packageJson.version,
|
|
110
|
+
configPatch: plan.configPatch,
|
|
111
|
+
defaultShell: plan.configPatch.shell?.default,
|
|
112
|
+
defaultWorkspace: plan.configPatch.workspace?.default,
|
|
113
|
+
onProgress,
|
|
114
|
+
});
|
|
115
|
+
progress?.(`{green-fg}ok{/} ${userLayout.configPath}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (plan.installLevels.includes('project')) {
|
|
119
|
+
progress?.('{bold}Preparing project level{/bold}');
|
|
120
|
+
const projectLayout = ensureProjectLayout({
|
|
121
|
+
version: packageJson.version,
|
|
122
|
+
startDir: plan.projectRoot,
|
|
123
|
+
configPatch: plan.configPatch,
|
|
124
|
+
defaultShell: plan.configPatch.shell?.default,
|
|
125
|
+
defaultWorkspace: plan.configPatch.workspace?.default,
|
|
126
|
+
onProgress,
|
|
127
|
+
});
|
|
128
|
+
progress?.(`{green-fg}ok{/} ${projectLayout.configPath}`);
|
|
129
|
+
if (projectLayout.gitignoreUpdated && projectLayout.gitignorePath) {
|
|
130
|
+
progress?.(`{green-fg}ok{/} added .aigentry/ to ${projectLayout.gitignorePath}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function installNativeBundle() {
|
|
136
|
+
const platformPackage = resolvePlatformPackage();
|
|
137
|
+
if (!platformPackage) {
|
|
138
|
+
console.warn(
|
|
139
|
+
`[aterm] no native package for ${process.platform} ${process.arch}; install skipped`,
|
|
140
|
+
);
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let platformPackageJsonPath;
|
|
145
|
+
try {
|
|
146
|
+
platformPackageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
|
147
|
+
} catch {
|
|
148
|
+
console.warn(`[aterm] optional native package not installed: ${platformPackage}`);
|
|
149
|
+
console.warn('[aterm] reinstall after the platform package is available');
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const platformRoot = path.dirname(platformPackageJsonPath);
|
|
154
|
+
const sourceApp = path.join(platformRoot, 'dist', 'aterm.app');
|
|
155
|
+
const targetApp = path.join(packageRoot, 'dist', 'aterm.app');
|
|
156
|
+
|
|
157
|
+
if (!fs.existsSync(sourceApp)) {
|
|
158
|
+
console.error(`[aterm] native app bundle missing from ${platformPackage}: ${sourceApp}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fs.rmSync(path.dirname(targetApp), { recursive: true, force: true });
|
|
163
|
+
fs.mkdirSync(path.dirname(targetApp), { recursive: true });
|
|
164
|
+
fs.cpSync(sourceApp, targetApp, { recursive: true });
|
|
165
|
+
|
|
166
|
+
console.log(`[aterm] installed native bundle from ${platformPackage}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function main() {
|
|
170
|
+
const plan = await collectInstallerPlan();
|
|
171
|
+
|
|
172
|
+
let tuiModule = null;
|
|
173
|
+
try {
|
|
174
|
+
tuiModule = await import('./tui-installer.js');
|
|
175
|
+
} catch {
|
|
176
|
+
tuiModule = null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (tuiModule?.shouldRunInstallerTui()) {
|
|
180
|
+
await tuiModule.showProgressScreen(async (progress) => {
|
|
181
|
+
applyInstallPlan(plan, progress);
|
|
182
|
+
progress('{bold}Staging native bundle{/bold}');
|
|
183
|
+
installNativeBundle();
|
|
184
|
+
progress('{green-fg}ok{/} native bundle ready');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
await tuiModule.showDoneScreen([
|
|
188
|
+
'aterm setup is complete.',
|
|
189
|
+
'',
|
|
190
|
+
`Mode: ${plan.mode}`,
|
|
191
|
+
`Levels: ${plan.installLevels.join(', ')}`,
|
|
192
|
+
`Shell: ${plan.configPatch.shell?.default ?? 'zsh'}`,
|
|
193
|
+
`Workspace: ${plan.configPatch.workspace?.default ?? 'home'}`,
|
|
194
|
+
`Tailscale connect: ${plan.configPatch.tailscale?.connect_on_launch ? 'yes' : 'no'}`,
|
|
195
|
+
]);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
applyInstallPlan(plan);
|
|
200
|
+
installNativeBundle();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await main();
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { detectAiCliStatus, resolveInstallHomeDir } from '../lib/aigentry.js';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
|
|
8
|
+
function waitForKeypress(screen, keys = ['enter', 'space', 'escape']) {
|
|
9
|
+
return new Promise((resolve) => {
|
|
10
|
+
const handler = (_, key) => {
|
|
11
|
+
if (!key || !keys.includes(key.name)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
screen.off('keypress', handler);
|
|
15
|
+
resolve();
|
|
16
|
+
};
|
|
17
|
+
screen.on('keypress', handler);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function showInfoScreen(blessed, title, lines, footer) {
|
|
22
|
+
const screen = blessed.screen({
|
|
23
|
+
smartCSR: true,
|
|
24
|
+
title: 'aterm installer',
|
|
25
|
+
fullUnicode: true,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
blessed.box({
|
|
29
|
+
parent: screen,
|
|
30
|
+
top: 'center',
|
|
31
|
+
left: 'center',
|
|
32
|
+
width: '78%',
|
|
33
|
+
height: Math.max(12, lines.length + 6),
|
|
34
|
+
border: { type: 'line' },
|
|
35
|
+
tags: true,
|
|
36
|
+
label: ` ${title} `,
|
|
37
|
+
style: {
|
|
38
|
+
border: { fg: 'cyan' },
|
|
39
|
+
fg: 'white',
|
|
40
|
+
},
|
|
41
|
+
content: `${lines.join('\n')}\n\n${footer}`,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
screen.render();
|
|
45
|
+
await waitForKeypress(screen);
|
|
46
|
+
screen.destroy();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function buildWorkspaceChoices(context) {
|
|
50
|
+
const choices = [];
|
|
51
|
+
if (context.orchestratorAvailable) {
|
|
52
|
+
choices.push({
|
|
53
|
+
title: 'aigentry-orchestrator',
|
|
54
|
+
value: 'orchestrator',
|
|
55
|
+
description: context.orchestratorPath,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (context.mode === 'local') {
|
|
60
|
+
choices.push({
|
|
61
|
+
title: 'current project',
|
|
62
|
+
value: 'project',
|
|
63
|
+
description: context.projectRoot,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
choices.push({
|
|
68
|
+
title: 'home directory',
|
|
69
|
+
value: 'home',
|
|
70
|
+
description: resolveInstallHomeDir(),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return choices;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildConfigPatch(context, answers) {
|
|
77
|
+
const workspacePathMap = {
|
|
78
|
+
orchestrator: context.orchestratorPath,
|
|
79
|
+
project: context.projectRoot,
|
|
80
|
+
home: resolveInstallHomeDir(),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
shell: {
|
|
85
|
+
default: answers.defaultShell,
|
|
86
|
+
},
|
|
87
|
+
workspace: {
|
|
88
|
+
default: answers.defaultWorkspace,
|
|
89
|
+
path: workspacePathMap[answers.defaultWorkspace] ?? resolveInstallHomeDir(),
|
|
90
|
+
},
|
|
91
|
+
orchestrator: {
|
|
92
|
+
enabled: answers.defaultWorkspace === 'orchestrator',
|
|
93
|
+
path: context.orchestratorPath,
|
|
94
|
+
},
|
|
95
|
+
tailscale: {
|
|
96
|
+
connect_on_launch: answers.tailscaleConnect,
|
|
97
|
+
},
|
|
98
|
+
ai: {
|
|
99
|
+
detected_clis: context.cliStatus,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function runInstallerTui(context) {
|
|
105
|
+
const blessed = require('blessed');
|
|
106
|
+
const prompts = (await import('prompts')).default;
|
|
107
|
+
|
|
108
|
+
await showInfoScreen(
|
|
109
|
+
blessed,
|
|
110
|
+
' aterm Installer ',
|
|
111
|
+
[
|
|
112
|
+
'{center}{bold}Aigentry Terminal Installer{/bold}{/center}',
|
|
113
|
+
'',
|
|
114
|
+
'This installer will prepare the requested aigentry config level(s),',
|
|
115
|
+
'write default settings, and stage the native aterm bundle.',
|
|
116
|
+
'',
|
|
117
|
+
`Detected install mode: ${context.mode}`,
|
|
118
|
+
],
|
|
119
|
+
'{gray-fg}Press Enter to continue{/}',
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const cliLines = [
|
|
123
|
+
'{bold}AI CLI detection{/bold}',
|
|
124
|
+
'',
|
|
125
|
+
`claude ${context.cliStatus.claude ? '✅' : '❌'}`,
|
|
126
|
+
`codex ${context.cliStatus.codex ? '✅' : '❌'}`,
|
|
127
|
+
`gemini ${context.cliStatus.gemini ? '✅' : '❌'}`,
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
await showInfoScreen(
|
|
131
|
+
blessed,
|
|
132
|
+
' CLI Status ',
|
|
133
|
+
cliLines,
|
|
134
|
+
'{gray-fg}Press Enter to continue{/}',
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const workspaceChoices = buildWorkspaceChoices(context);
|
|
138
|
+
const defaultResponses = {
|
|
139
|
+
installLevels: context.availableLevels.map(level => level.value),
|
|
140
|
+
defaultShell: 'zsh',
|
|
141
|
+
defaultWorkspace: workspaceChoices[0]?.value ?? 'home',
|
|
142
|
+
tailscaleConnect: false,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const responses = await prompts(
|
|
146
|
+
[
|
|
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
|
+
{
|
|
157
|
+
type: 'select',
|
|
158
|
+
name: 'defaultShell',
|
|
159
|
+
message: 'Default shell',
|
|
160
|
+
choices: [
|
|
161
|
+
{ title: 'zsh', value: 'zsh' },
|
|
162
|
+
{ title: 'bash', value: 'bash' },
|
|
163
|
+
{ title: 'fish', value: 'fish' },
|
|
164
|
+
],
|
|
165
|
+
initial: 0,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
type: 'select',
|
|
169
|
+
name: 'defaultWorkspace',
|
|
170
|
+
message: 'Default workspace / orchestrator target',
|
|
171
|
+
choices: workspaceChoices,
|
|
172
|
+
initial: 0,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
type: 'toggle',
|
|
176
|
+
name: 'tailscaleConnect',
|
|
177
|
+
message: 'Enable Tailscale connect on launch?',
|
|
178
|
+
initial: false,
|
|
179
|
+
active: 'yes',
|
|
180
|
+
inactive: 'no',
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
{
|
|
184
|
+
onCancel: () => true,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const mergedAnswers = {
|
|
189
|
+
...defaultResponses,
|
|
190
|
+
...responses,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
installLevels: mergedAnswers.installLevels,
|
|
195
|
+
configPatch: buildConfigPatch(context, mergedAnswers),
|
|
196
|
+
summary: mergedAnswers,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function shouldRunInstallerTui() {
|
|
201
|
+
if (process.env.CI) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (process.env.npm_config_yes === 'true') {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function buildInstallerContext(mode, projectRoot = null) {
|
|
211
|
+
const installHome = resolveInstallHomeDir();
|
|
212
|
+
const orchestratorPath = path.join(installHome, 'projects', 'aigentry-orchestrator');
|
|
213
|
+
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
|
+
|
|
235
|
+
return {
|
|
236
|
+
mode,
|
|
237
|
+
projectRoot,
|
|
238
|
+
cliStatus,
|
|
239
|
+
orchestratorPath,
|
|
240
|
+
orchestratorAvailable: require('node:fs').existsSync(orchestratorPath),
|
|
241
|
+
availableLevels,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function showProgressScreen(runSteps) {
|
|
246
|
+
const blessed = require('blessed');
|
|
247
|
+
const screen = blessed.screen({
|
|
248
|
+
smartCSR: true,
|
|
249
|
+
title: 'aterm installer',
|
|
250
|
+
fullUnicode: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const box = blessed.log({
|
|
254
|
+
parent: screen,
|
|
255
|
+
top: 'center',
|
|
256
|
+
left: 'center',
|
|
257
|
+
width: '84%',
|
|
258
|
+
height: '70%',
|
|
259
|
+
border: { type: 'line' },
|
|
260
|
+
tags: true,
|
|
261
|
+
label: ' Installation Progress ',
|
|
262
|
+
style: {
|
|
263
|
+
border: { fg: 'green' },
|
|
264
|
+
fg: 'white',
|
|
265
|
+
},
|
|
266
|
+
scrollable: true,
|
|
267
|
+
alwaysScroll: true,
|
|
268
|
+
scrollbar: { ch: ' ', inverse: true },
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const report = (line) => {
|
|
272
|
+
box.add(line);
|
|
273
|
+
screen.render();
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
screen.render();
|
|
277
|
+
await runSteps(report);
|
|
278
|
+
await new Promise(resolve => setTimeout(resolve, 250));
|
|
279
|
+
screen.destroy();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export async function showDoneScreen(summaryLines) {
|
|
283
|
+
const blessed = require('blessed');
|
|
284
|
+
await showInfoScreen(
|
|
285
|
+
blessed,
|
|
286
|
+
' Install Complete ',
|
|
287
|
+
summaryLines,
|
|
288
|
+
'{green-fg}Run: aterm{/}',
|
|
289
|
+
);
|
|
290
|
+
}
|
package/install.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
const packageJson = JSON.parse(
|
|
12
|
-
fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'),
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
const PLATFORM_PACKAGES = {
|
|
16
|
-
darwin: {
|
|
17
|
-
arm64: '@dmsdc-ai/aterm-darwin-arm64',
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
function resolvePlatformPackage() {
|
|
22
|
-
return PLATFORM_PACKAGES[os.platform()]?.[os.arch()] ?? null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function bootstrapEcosystem() {
|
|
26
|
-
const homeDir = os.homedir();
|
|
27
|
-
const directories = [
|
|
28
|
-
path.join(homeDir, '.aigentry', 'inbox'),
|
|
29
|
-
path.join(homeDir, '.aigentry', 'config'),
|
|
30
|
-
path.join(homeDir, '.aigentry', 'brain', 'local'),
|
|
31
|
-
path.join(homeDir, '.aigentry', 'brain', 'profiles'),
|
|
32
|
-
path.join(homeDir, '.aigentry', 'brain', 'search'),
|
|
33
|
-
path.join(homeDir, '.telepty', 'shared'),
|
|
34
|
-
path.join(homeDir, 'Library', 'Application Support', 'aterm'),
|
|
35
|
-
];
|
|
36
|
-
|
|
37
|
-
for (const directory of directories) {
|
|
38
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const configPath = path.join(homeDir, '.aigentry', 'config', 'aterm.json');
|
|
42
|
-
if (!fs.existsSync(configPath)) {
|
|
43
|
-
const defaultConfig = {
|
|
44
|
-
version: packageJson.version,
|
|
45
|
-
installed_at: new Date().toISOString(),
|
|
46
|
-
platform: `${os.platform()}-${os.arch()}`,
|
|
47
|
-
};
|
|
48
|
-
fs.writeFileSync(`${configPath}`, `${JSON.stringify(defaultConfig, null, 2)}\n`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
bootstrapEcosystem();
|
|
53
|
-
|
|
54
|
-
const platformPackage = resolvePlatformPackage();
|
|
55
|
-
if (!platformPackage) {
|
|
56
|
-
console.warn(`[aterm] no native package for ${os.platform()} ${os.arch()}; install skipped`);
|
|
57
|
-
process.exit(0);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let platformPackageJsonPath;
|
|
61
|
-
try {
|
|
62
|
-
platformPackageJsonPath = require.resolve(`${platformPackage}/package.json`);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
console.warn(`[aterm] optional native package not installed: ${platformPackage}`);
|
|
65
|
-
console.warn('[aterm] reinstall after the platform package is available');
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const platformRoot = path.dirname(platformPackageJsonPath);
|
|
70
|
-
const sourceApp = path.join(platformRoot, 'dist', 'aterm.app');
|
|
71
|
-
const targetApp = path.join(__dirname, 'dist', 'aterm.app');
|
|
72
|
-
|
|
73
|
-
if (!fs.existsSync(sourceApp)) {
|
|
74
|
-
console.error(`[aterm] native app bundle missing from ${platformPackage}: ${sourceApp}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
fs.rmSync(path.dirname(targetApp), { recursive: true, force: true });
|
|
79
|
-
fs.mkdirSync(path.dirname(targetApp), { recursive: true });
|
|
80
|
-
fs.cpSync(sourceApp, targetApp, { recursive: true });
|
|
81
|
-
|
|
82
|
-
console.log(`[aterm] installed native bundle from ${platformPackage}`);
|