agentic-workflow-manager 2.0.1 → 2.1.1

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.
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ALL_SENTINEL = void 0;
4
+ exports.parseKey = parseKey;
5
+ exports.pickerReducer = pickerReducer;
6
+ exports.multiselectPicker = multiselectPicker;
7
+ const picker_view_1 = require("./picker-view");
8
+ const registry_view_1 = require("../utils/registry-view");
9
+ // ALL_SENTINEL: single source of truth lives in registry-view.ts — re-exported here.
10
+ exports.ALL_SENTINEL = registry_view_1.ALL_SENTINEL;
11
+ function parseKey(data) {
12
+ switch (data) {
13
+ case '\x1b[A': return { action: 'up' };
14
+ case '\x1b[B': return { action: 'down' };
15
+ case '\r':
16
+ case '\n': return { action: 'confirm' };
17
+ case ' ': return { action: 'toggle' };
18
+ case '\t': return { action: 'toggleAll' };
19
+ case '\x7f':
20
+ case '\b': return { action: 'backspace' };
21
+ case '\x03': return { action: 'cancel' }; // Ctrl-C
22
+ case '\x1b': return { action: 'clearOrCancel' }; // Esc
23
+ }
24
+ if (data.length === 1 && /[a-zA-Z0-9-]/.test(data))
25
+ return { action: 'char', char: data };
26
+ return { action: 'none' };
27
+ }
28
+ function realValues(state) {
29
+ return state.items.filter((i) => i.value !== exports.ALL_SENTINEL).map((i) => i.value);
30
+ }
31
+ function pickerReducer(state, key) {
32
+ const vis = (0, picker_view_1.visibleItems)(state);
33
+ switch (key.type) {
34
+ case 'up': {
35
+ if (vis.length === 0)
36
+ return state;
37
+ return { ...state, cursor: (state.cursor - 1 + vis.length) % vis.length };
38
+ }
39
+ case 'down': {
40
+ if (vis.length === 0)
41
+ return state;
42
+ return { ...state, cursor: (state.cursor + 1) % vis.length };
43
+ }
44
+ case 'toggle': {
45
+ if (vis.length === 0)
46
+ return state;
47
+ const item = vis[Math.max(0, Math.min(state.cursor, vis.length - 1))];
48
+ const selected = new Set(state.selected);
49
+ if (item.value === exports.ALL_SENTINEL) {
50
+ const reals = realValues(state);
51
+ const allSel = reals.every((v) => selected.has(v));
52
+ if (allSel) {
53
+ reals.forEach((v) => selected.delete(v));
54
+ selected.delete(exports.ALL_SENTINEL);
55
+ }
56
+ else {
57
+ reals.forEach((v) => selected.add(v));
58
+ selected.add(exports.ALL_SENTINEL);
59
+ }
60
+ }
61
+ else {
62
+ if (selected.has(item.value)) {
63
+ selected.delete(item.value);
64
+ selected.delete(exports.ALL_SENTINEL); // no longer all-selected
65
+ }
66
+ else {
67
+ selected.add(item.value);
68
+ // if every real item is now selected, also mark the sentinel
69
+ const reals = realValues(state);
70
+ if (reals.every((v) => selected.has(v)))
71
+ selected.add(exports.ALL_SENTINEL);
72
+ }
73
+ }
74
+ return { ...state, selected };
75
+ }
76
+ case 'toggleAll': {
77
+ const reals = vis.filter((i) => i.value !== exports.ALL_SENTINEL).map((i) => i.value);
78
+ const selected = new Set(state.selected);
79
+ const allSel = reals.every((v) => selected.has(v));
80
+ if (allSel) {
81
+ reals.forEach((v) => selected.delete(v));
82
+ selected.delete(exports.ALL_SENTINEL);
83
+ }
84
+ else {
85
+ reals.forEach((v) => selected.add(v));
86
+ selected.add(exports.ALL_SENTINEL);
87
+ }
88
+ return { ...state, selected };
89
+ }
90
+ case 'filterChar':
91
+ return { ...state, filter: state.filter + key.char, cursor: 0 };
92
+ case 'backspace':
93
+ return { ...state, filter: state.filter.slice(0, -1), cursor: 0 };
94
+ }
95
+ }
96
+ function defaultIO() {
97
+ return { input: process.stdin, output: process.stdout };
98
+ }
99
+ /**
100
+ * Interactive multiselect. Returns the selected values, or null if cancelled.
101
+ * Streams are injectable for testing. Always restores terminal state on exit.
102
+ */
103
+ function multiselectPicker(opts, io = defaultIO()) {
104
+ return new Promise((resolve) => {
105
+ let state = {
106
+ title: opts.title,
107
+ items: opts.items,
108
+ selected: new Set(opts.initialSelected ?? []),
109
+ cursor: 0,
110
+ filter: '',
111
+ };
112
+ let lastLineCount = 0;
113
+ const size = () => ({
114
+ columns: io.output.columns ?? 80,
115
+ rows: io.output.rows ?? 24,
116
+ });
117
+ const draw = () => {
118
+ const lines = (0, picker_view_1.renderPicker)(state, size());
119
+ // Move up over the previous frame and clear downward.
120
+ if (lastLineCount > 0)
121
+ io.output.write(`\x1b[${lastLineCount}A`);
122
+ io.output.write('\x1b[0J');
123
+ io.output.write(lines.join('\n') + '\n');
124
+ lastLineCount = lines.length;
125
+ };
126
+ let onSigint;
127
+ const cleanup = () => {
128
+ io.input.removeListener('data', onData);
129
+ if (onSigint)
130
+ process.removeListener('SIGINT', onSigint);
131
+ try {
132
+ io.input.setRawMode?.(false);
133
+ }
134
+ catch { /* best-effort: terminal may not support raw mode */ }
135
+ io.input.pause?.();
136
+ io.output.write('\x1b[?25h'); // show cursor
137
+ };
138
+ const onData = (chunk) => {
139
+ const key = parseKey(chunk.toString());
140
+ switch (key.action) {
141
+ case 'confirm':
142
+ cleanup();
143
+ resolve(Array.from(state.selected));
144
+ return;
145
+ case 'cancel':
146
+ cleanup();
147
+ resolve(null);
148
+ return;
149
+ case 'clearOrCancel':
150
+ if (state.filter) {
151
+ state = { ...state, filter: '', cursor: 0 };
152
+ break;
153
+ }
154
+ cleanup();
155
+ resolve(null);
156
+ return;
157
+ case 'up':
158
+ state = pickerReducer(state, { type: 'up' });
159
+ break;
160
+ case 'down':
161
+ state = pickerReducer(state, { type: 'down' });
162
+ break;
163
+ case 'toggle':
164
+ state = pickerReducer(state, { type: 'toggle' });
165
+ break;
166
+ case 'toggleAll':
167
+ state = pickerReducer(state, { type: 'toggleAll' });
168
+ break;
169
+ case 'backspace':
170
+ state = pickerReducer(state, { type: 'backspace' });
171
+ break;
172
+ case 'char':
173
+ state = pickerReducer(state, { type: 'filterChar', char: key.char });
174
+ break;
175
+ case 'none': return; // ignore, no redraw
176
+ }
177
+ draw();
178
+ };
179
+ onSigint = () => {
180
+ cleanup();
181
+ resolve(null);
182
+ };
183
+ process.once('SIGINT', onSigint);
184
+ try {
185
+ io.input.setRawMode?.(true);
186
+ }
187
+ catch { /* best-effort: degrade if raw mode unsupported */ }
188
+ io.input.resume?.();
189
+ io.input.setEncoding?.('utf8');
190
+ io.output.write('\x1b[?25l'); // hide cursor
191
+ io.input.on('data', onData);
192
+ draw();
193
+ });
194
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stripAnsi = stripAnsi;
4
+ exports.displayWidth = displayWidth;
5
+ exports.truncate = truncate;
6
+ exports.wrap = wrap;
7
+ const ANSI_RE = /\x1b\[[0-9;]*m/g;
8
+ function stripAnsi(s) {
9
+ return s.replace(ANSI_RE, '');
10
+ }
11
+ function isWide(cp) {
12
+ return ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
13
+ (cp >= 0x2300 && cp <= 0x23ff) || // misc technical (⏎ etc.)
14
+ (cp >= 0x2600 && cp <= 0x27bf) || // misc symbols / dingbats
15
+ (cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified Ideographs
16
+ (cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
17
+ (cp >= 0xff00 && cp <= 0xffef) || // Fullwidth Latin/symbols
18
+ (cp >= 0x1f000 && cp <= 0x1faff) // emoji
19
+ );
20
+ }
21
+ /** Visible terminal cells, ignoring ANSI color and treating known wide glyphs as 2. */
22
+ function displayWidth(s) {
23
+ const plain = stripAnsi(s);
24
+ let w = 0;
25
+ for (const ch of plain) {
26
+ const cp = ch.codePointAt(0);
27
+ if (cp === 0xfe0f)
28
+ continue; // variation selector — zero width
29
+ w += isWide(cp) ? 2 : 1;
30
+ }
31
+ return w;
32
+ }
33
+ /** Truncate plain text to `width` cells, appending '…'. Strips ANSI — color AFTER truncating. */
34
+ function truncate(s, width) {
35
+ if (width <= 0)
36
+ return '';
37
+ const plain = stripAnsi(s);
38
+ if (displayWidth(plain) <= width)
39
+ return plain;
40
+ if (width === 1)
41
+ return '…';
42
+ let out = '';
43
+ let w = 0;
44
+ for (const ch of plain) {
45
+ const cw = displayWidth(ch);
46
+ if (w + cw > width - 1)
47
+ break;
48
+ out += ch;
49
+ w += cw;
50
+ }
51
+ return out + '…';
52
+ }
53
+ /** Word-wrap plain text to `width` cells. Long single words are not split (acceptable). */
54
+ function wrap(s, width) {
55
+ if (width <= 0)
56
+ return [s];
57
+ const words = stripAnsi(s).split(/\s+/).filter(Boolean);
58
+ const lines = [];
59
+ let cur = '';
60
+ for (const word of words) {
61
+ if (cur === '') {
62
+ cur = word;
63
+ continue;
64
+ }
65
+ if (displayWidth(cur) + 1 + displayWidth(word) <= width)
66
+ cur += ' ' + word;
67
+ else {
68
+ lines.push(cur);
69
+ cur = word;
70
+ }
71
+ }
72
+ if (cur)
73
+ lines.push(cur);
74
+ return lines.length ? lines : [''];
75
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isInteractive = isInteractive;
4
+ exports.terminalSize = terminalSize;
5
+ /** True only when both ends are TTYs — the precondition for the interactive picker. */
6
+ function isInteractive() {
7
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
8
+ }
9
+ /** Current terminal size, with a safe 80x24 fallback when undefined (non-TTY). */
10
+ function terminalSize() {
11
+ return {
12
+ columns: process.stdout.columns ?? 80,
13
+ rows: process.stdout.rows ?? 24,
14
+ };
15
+ }
@@ -8,14 +8,14 @@ exports.savePreferences = savePreferences;
8
8
  // src/utils/config.ts
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
- const os_1 = __importDefault(require("os"));
11
+ const paths_1 = require("../core/paths");
12
12
  const DEFAULT_PREFS = {
13
13
  defaultAgent: 'antigravity',
14
14
  installMethod: 'symlink',
15
15
  defaultScope: 'local'
16
16
  };
17
17
  function prefsDir() {
18
- return process.env.AWM_HOME || path_1.default.join(process.env.HOME || os_1.default.homedir(), '.awm');
18
+ return (0, paths_1.awmHome)();
19
19
  }
20
20
  function getPreferences() {
21
21
  const file = path_1.default.join(prefsDir(), 'preferences.json');
@@ -10,11 +10,12 @@ exports.packageSummaryLines = packageSummaryLines;
10
10
  exports.packageDetailLines = packageDetailLines;
11
11
  exports.findPackage = findPackage;
12
12
  exports.artifactValue = artifactValue;
13
- exports.buildLevel1Options = buildLevel1Options;
14
- exports.buildLevel2Options = buildLevel2Options;
13
+ exports.packagePickerItems = packagePickerItems;
14
+ exports.artifactPickerItems = artifactPickerItems;
15
15
  exports.resolveLevel2Selection = resolveLevel2Selection;
16
16
  const picocolors_1 = __importDefault(require("picocolors"));
17
17
  const registries_1 = require("../core/registries");
18
+ const text_1 = require("../ui/text");
18
19
  exports.STANDALONE_NAME = 'standalone';
19
20
  function makePackage(name, description, isStandalone, visibility, artifacts) {
20
21
  return {
@@ -73,7 +74,7 @@ function artifactCountLabel(counts) {
73
74
  function packageIcon(pkg) {
74
75
  return pkg.isStandalone ? '🔹' : '📦';
75
76
  }
76
- function packageSummaryLines(packages) {
77
+ function packageSummaryLines(packages, width) {
77
78
  const uniqueSkillNames = new Set(packages.flatMap((p) => p.artifacts.filter((a) => a.type === 'skill').map((a) => a.name)));
78
79
  const totalSkills = uniqueSkillNames.size;
79
80
  const lines = [`AWM Registry — ${plural(packages.length, 'package')}, ${plural(totalSkills, 'skill')}`, ''];
@@ -83,12 +84,17 @@ function packageSummaryLines(packages) {
83
84
  packages.forEach((p, i) => {
84
85
  const name = p.name.padEnd(nameWidth);
85
86
  const count = countLabels[i].padEnd(countWidth);
86
- const desc = p.isStandalone ? '' : p.description;
87
+ let desc = p.isStandalone ? '' : p.description;
88
+ if (width && desc) {
89
+ const used = 3 + nameWidth + 3 + countWidth + 3; // icon(2)+space(1), name, gap, count, gap
90
+ const avail = width - used;
91
+ desc = avail > 1 ? (0, text_1.truncate)(desc, avail) : '';
92
+ }
87
93
  lines.push(`${packageIcon(p)} ${name} ${count} ${desc}`.trimEnd());
88
94
  });
89
95
  return lines;
90
96
  }
91
- function packageDetailLines(pkg) {
97
+ function packageDetailLines(pkg, width) {
92
98
  const lines = [];
93
99
  const header = pkg.isStandalone
94
100
  ? `${packageIcon(pkg)} ${pkg.name} — ${plural(pkg.artifacts.length, 'artifact')}`
@@ -99,8 +105,10 @@ function packageDetailLines(pkg) {
99
105
  ? picocolors_1.default.yellow(` ← ${(0, registries_1.registryNameForPath)(a.sourcePath) ?? 'unknown'} (override)`)
100
106
  : '';
101
107
  lines.push(` ${TYPE_ICON[a.type]}${a.name}${mark}`);
102
- if (a.description)
103
- lines.push(` ${a.description}`);
108
+ if (a.description) {
109
+ const desc = width ? (0, text_1.truncate)(a.description, width - 5) : a.description;
110
+ lines.push(` ${desc}`);
111
+ }
104
112
  });
105
113
  return lines;
106
114
  }
@@ -130,21 +138,27 @@ exports.ALL_SENTINEL = '__ALL__';
130
138
  function artifactValue(a) {
131
139
  return `${a.type}:${a.name}`;
132
140
  }
133
- function buildLevel1Options(packages) {
134
- return packages.map((p) => {
135
- const count = p.isStandalone ? plural(p.artifacts.length, 'artifact') : artifactCountLabel(p.counts);
136
- const desc = p.isStandalone ? '' : ` · ${p.description}`;
137
- return { value: p.name, label: `${packageIcon(p)} ${p.name} ${picocolors_1.default.dim(`${count}${desc}`)}` };
138
- });
141
+ function packagePickerItems(packages) {
142
+ return packages.map((p) => ({
143
+ value: p.name,
144
+ label: `${packageIcon(p)} ${p.name}`,
145
+ description: p.isStandalone
146
+ ? plural(p.artifacts.length, 'artifact')
147
+ : `${artifactCountLabel(p.counts)} · ${p.description}`,
148
+ }));
139
149
  }
140
- function buildLevel2Options(pkg) {
141
- const installAll = { value: exports.ALL_SENTINEL, label: `✨ Install entire package (${pkg.artifacts.length})` };
142
- const rest = pkg.artifacts.map((a) => {
143
- const title = `${TYPE_ICON[a.type]}${a.name}`;
144
- const label = a.description ? `${title}\n ${picocolors_1.default.dim(a.description)}` : title;
145
- return { value: artifactValue(a), label };
146
- });
147
- return [installAll, ...rest];
150
+ function artifactPickerItems(pkg) {
151
+ const all = {
152
+ value: exports.ALL_SENTINEL,
153
+ label: `✨ Install entire package (${pkg.artifacts.length})`,
154
+ description: 'Select every artifact in this package.',
155
+ };
156
+ const rest = pkg.artifacts.map((a) => ({
157
+ value: artifactValue(a),
158
+ label: `${TYPE_ICON[a.type]}${a.name}`,
159
+ description: a.description || '',
160
+ }));
161
+ return [all, ...rest];
148
162
  }
149
163
  function resolveLevel2Selection(pkg, selectedValues) {
150
164
  if (selectedValues.includes(exports.ALL_SENTINEL))
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const doctor_1 = require("../../src/commands/doctor");
4
+ describe('doctor renderReport — platform line', () => {
5
+ const realPlatform = process.platform;
6
+ afterEach(() => {
7
+ Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
8
+ });
9
+ function emptyReport() {
10
+ return { overall: 'healthy', hasProject: false, projectName: undefined, results: [] };
11
+ }
12
+ it('renders the platform label under the Machine header', () => {
13
+ Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
14
+ const out = (0, doctor_1.renderReport)(emptyReport());
15
+ expect(out).toContain('platform: Linux');
16
+ });
17
+ it('flags native Windows with a WSL hint', () => {
18
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
19
+ const out = (0, doctor_1.renderReport)(emptyReport());
20
+ expect(out).toContain('WSL');
21
+ });
22
+ });
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ describe('hooks/install — skill symlink fallback to copy', () => {
10
+ let tmpHome;
11
+ let origHome;
12
+ let origAwmHome;
13
+ let symlinkSpy;
14
+ beforeEach(() => {
15
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-symlink-fb-'));
16
+ origHome = process.env.HOME;
17
+ origAwmHome = process.env.AWM_HOME;
18
+ process.env.HOME = tmpHome;
19
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
20
+ jest.resetModules();
21
+ });
22
+ afterEach(() => {
23
+ symlinkSpy?.mockRestore();
24
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
25
+ if (origHome === undefined)
26
+ delete process.env.HOME;
27
+ else
28
+ process.env.HOME = origHome;
29
+ if (origAwmHome === undefined)
30
+ delete process.env.AWM_HOME;
31
+ else
32
+ process.env.AWM_HOME = origAwmHome;
33
+ });
34
+ function seedRegistry(root) {
35
+ const hooksDir = path_1.default.join(root, 'hooks');
36
+ const skillDir = path_1.default.join(root, 'skills', 'using-awm');
37
+ fs_1.default.mkdirSync(hooksDir, { recursive: true });
38
+ fs_1.default.mkdirSync(skillDir, { recursive: true });
39
+ fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'session-start'), '#!/bin/sh\n');
40
+ fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'run-hook.cmd'), '#!/bin/sh\n');
41
+ fs_1.default.writeFileSync(path_1.default.join(skillDir, 'SKILL.md'), '# using-awm\n');
42
+ }
43
+ it('copies the skill when symlink throws (EPERM), preserving content', () => {
44
+ const registryRoot = path_1.default.join(tmpHome, 'registry');
45
+ seedRegistry(registryRoot);
46
+ // Force symlinkSync to fail like a platform without symlink permission.
47
+ symlinkSpy = jest.spyOn(fs_1.default, 'symlinkSync').mockImplementation(() => {
48
+ const err = new Error('EPERM: operation not permitted, symlink');
49
+ err.code = 'EPERM';
50
+ throw err;
51
+ });
52
+ const { installHook } = require('../../../src/commands/hooks/install');
53
+ const result = installHook({ agent: 'claude-code', registryRoot, installMethod: 'copy' });
54
+ const skillDest = path_1.default.join(result.scriptsDir, 'using-awm.md');
55
+ expect(fs_1.default.existsSync(skillDest)).toBe(true);
56
+ expect(fs_1.default.lstatSync(skillDest).isSymbolicLink()).toBe(false); // it was copied, not linked
57
+ expect(fs_1.default.readFileSync(skillDest, 'utf-8')).toContain('using-awm');
58
+ });
59
+ });
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const eslint_1 = require("../../../src/commands/sensors/formatters/eslint");
8
+ describe('parseEslintOutput — relative path normalization', () => {
9
+ it('produces a cwd-relative path using the OS separator', () => {
10
+ const cwd = process.cwd();
11
+ const abs = path_1.default.join(cwd, 'src', 'foo.ts');
12
+ const raw = JSON.stringify([
13
+ { filePath: abs, messages: [{ ruleId: 'no-eval', severity: 2, message: 'no eval', line: 3, column: 1 }] },
14
+ ]);
15
+ const errors = (0, eslint_1.parseEslintOutput)(raw);
16
+ expect(errors).toHaveLength(1);
17
+ const first = errors[0];
18
+ if (!first)
19
+ throw new Error('Expected at least one error');
20
+ // path.join('src','foo.ts') uses the OS separator; on POSIX this is 'src/foo.ts'
21
+ expect(first.file).toBe(path_1.default.join('src', 'foo.ts'));
22
+ const file = first.file ?? '';
23
+ expect(file.startsWith(path_1.default.sep)).toBe(false); // genuinely relative
24
+ });
25
+ it('preserves the absolute path for files outside cwd', () => {
26
+ const abs = '/some/other/project/file.ts';
27
+ const raw = JSON.stringify([
28
+ { filePath: abs, messages: [{ ruleId: 'no-eval', severity: 2, message: 'no eval', line: 1, column: 1 }] },
29
+ ]);
30
+ const errors = (0, eslint_1.parseEslintOutput)(raw);
31
+ expect(errors).toHaveLength(1);
32
+ expect(errors[0]?.file).toBe(abs); // absolute path preserved, no traversal strings
33
+ });
34
+ });
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const os_1 = __importDefault(require("os"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const paths_1 = require("../../src/core/paths");
9
+ describe('core/paths', () => {
10
+ let origHome;
11
+ let origAwmHome;
12
+ const realPlatform = process.platform;
13
+ beforeEach(() => {
14
+ origHome = process.env.HOME;
15
+ origAwmHome = process.env.AWM_HOME;
16
+ });
17
+ afterEach(() => {
18
+ if (origHome === undefined)
19
+ delete process.env.HOME;
20
+ else
21
+ process.env.HOME = origHome;
22
+ if (origAwmHome === undefined)
23
+ delete process.env.AWM_HOME;
24
+ else
25
+ process.env.AWM_HOME = origAwmHome;
26
+ Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
27
+ });
28
+ function setPlatform(p) {
29
+ Object.defineProperty(process, 'platform', { value: p, configurable: true });
30
+ }
31
+ it('homeDir uses process.env.HOME when set', () => {
32
+ process.env.HOME = '/tmp/fake-home';
33
+ expect((0, paths_1.homeDir)()).toBe('/tmp/fake-home');
34
+ });
35
+ it('homeDir falls back to os.homedir() when HOME is unset', () => {
36
+ delete process.env.HOME;
37
+ expect((0, paths_1.homeDir)()).toBe(os_1.default.homedir());
38
+ });
39
+ it('awmHome honors AWM_HOME override', () => {
40
+ process.env.AWM_HOME = '/tmp/custom-awm';
41
+ expect((0, paths_1.awmHome)()).toBe('/tmp/custom-awm');
42
+ });
43
+ it('awmHome defaults to <home>/.awm when AWM_HOME is unset', () => {
44
+ delete process.env.AWM_HOME;
45
+ process.env.HOME = '/tmp/fake-home';
46
+ expect((0, paths_1.awmHome)()).toBe(path_1.default.join('/tmp/fake-home', '.awm'));
47
+ });
48
+ it('platform reflects process.platform', () => {
49
+ setPlatform('linux');
50
+ expect((0, paths_1.platform)()).toBe('linux');
51
+ });
52
+ it('isWindowsNative is true only on win32', () => {
53
+ setPlatform('win32');
54
+ expect((0, paths_1.isWindowsNative)()).toBe(true);
55
+ setPlatform('linux');
56
+ expect((0, paths_1.isWindowsNative)()).toBe(false);
57
+ setPlatform('darwin');
58
+ expect((0, paths_1.isWindowsNative)()).toBe(false);
59
+ });
60
+ it('platformLabel describes each known platform', () => {
61
+ setPlatform('linux');
62
+ expect((0, paths_1.platformLabel)()).toBe('Linux');
63
+ setPlatform('darwin');
64
+ expect((0, paths_1.platformLabel)()).toBe('macOS');
65
+ setPlatform('win32');
66
+ expect((0, paths_1.platformLabel)()).toContain('WSL');
67
+ });
68
+ it('warnIfUnsupportedPlatform calls the logger only on win32', () => {
69
+ const calls = [];
70
+ const log = (m) => calls.push(m);
71
+ setPlatform('linux');
72
+ (0, paths_1.warnIfUnsupportedPlatform)(log);
73
+ expect(calls).toHaveLength(0);
74
+ setPlatform('win32');
75
+ (0, paths_1.warnIfUnsupportedPlatform)(log);
76
+ expect(calls).toEqual([paths_1.WINDOWS_NATIVE_WARNING]);
77
+ });
78
+ });