iranti-control-plane 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # Iranti Control Plane
2
2
 
3
- Local-first operator dashboard for [Iranti](https://github.com/nfemmanuel/iranti) - inspect memory, watch Staff activity, manage instances, and diagnose your setup without raw SQL.
3
+ Local-first operator dashboard for [Iranti](https://github.com/nfemmanuel/iranti) inspect memory, watch Staff activity, manage instances, and diagnose your setup without raw SQL.
4
4
 
5
5
  ## Status
6
6
 
7
- Current package version: `0.4.3`.
8
- The operator surface is live and under active UX hardening.
7
+ Current package version: `0.5.5`.
8
+ The operator surface is live and under active development.
9
9
 
10
- ## Install
10
+ Available on npm and mirrored by jsDelivr:
11
+ - npm: `npm install -g iranti-control-plane`
12
+ - jsDelivr CDN: `https://cdn.jsdelivr.net/npm/iranti-control-plane/`
11
13
 
12
- For the packaged control-plane CLI path (after npm publish, or from a locally packed tarball):
14
+ ## Install
13
15
 
14
16
  ```bash
15
17
  npm install -g iranti-control-plane
@@ -57,8 +59,8 @@ npm run migrate
57
59
  npm run dev
58
60
  ```
59
61
 
60
- Open http://localhost:5173 for the frontend dev server.
61
-
62
+ Open http://localhost:5173 for the frontend dev server.
63
+
62
64
  ### Port model
63
65
 
64
66
  The control plane has two common local startup modes:
@@ -145,4 +147,4 @@ See `docs/specs/control-plane-api.md` for the full API spec and `docs/prd/contro
145
147
  For release and manual publish checks, see [`docs/guides/releasing.md`](docs/guides/releasing.md).
146
148
 
147
149
  If npm publish is run from GitHub Actions, the repo `NPM_TOKEN` secret must be an npm **Automation token**. A standard token that still requires OTP will fail with `EOTP`.
148
-
150
+
package/bin/iranti-cp.js CHANGED
@@ -31,6 +31,9 @@ Usage:
31
31
  iranti-cp doctor [iranti doctor args...]
32
32
  iranti-cp upgrade [self]
33
33
  iranti-cp upgrade iranti [iranti upgrade args...]
34
+ iranti-cp config [get]
35
+ iranti-cp config set port <n>
36
+ iranti-cp config unset port
34
37
 
35
38
  Commands:
36
39
  open Open an existing Control Plane if one is running, otherwise start it in the background.
@@ -41,9 +44,13 @@ Commands:
41
44
  version Print the installed iranti-control-plane version.
42
45
  doctor Proxy to "iranti doctor".
43
46
  upgrade Upgrade iranti-control-plane itself, or proxy to "iranti upgrade" for core Iranti.
47
+ config Read or write persistent Control Plane configuration.
48
+ get Show current config.
49
+ set port <n> Set the default startup port (1024–65535).
50
+ unset port Clear the default port, reverting to the 3000–3010 auto-range.
44
51
 
45
52
  Options:
46
- --port <n> Prefer a specific Control Plane port for open/start/status.
53
+ --port <n> Prefer a specific Control Plane port for open/start/status (one-time override).
47
54
  --json Emit machine-readable output for status.
48
55
  -h, --help Show this help.
49
56
  `);
@@ -86,6 +93,26 @@ function parseArgs(argv) {
86
93
  return { help, port, json, positionals };
87
94
  }
88
95
 
96
+ function getUserConfigPath() {
97
+ return path.join(require('os').homedir(), '.iranti-runtime', 'iranti-cp-config.json');
98
+ }
99
+
100
+ function readUserConfig() {
101
+ try {
102
+ const raw = fs.readFileSync(getUserConfigPath(), 'utf8');
103
+ const parsed = JSON.parse(raw);
104
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
105
+ } catch {
106
+ return {};
107
+ }
108
+ }
109
+
110
+ function writeUserConfig(config) {
111
+ const configPath = getUserConfigPath();
112
+ fs.mkdirSync(path.dirname(configPath), { recursive: true });
113
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
114
+ }
115
+
89
116
  function preferredPorts(explicitPort) {
90
117
  const seen = new Set();
91
118
  const ordered = [];
@@ -100,11 +127,64 @@ function preferredPorts(explicitPort) {
100
127
 
101
128
  add(explicitPort);
102
129
  add(process.env.CONTROL_PLANE_PORT);
130
+ add(readUserConfig().defaultPort);
103
131
  for (let port = 3000; port <= 3010; port += 1) add(port);
104
132
  add(3002);
105
133
  return ordered;
106
134
  }
107
135
 
136
+ async function handleConfig(subcommands) {
137
+ const sub = subcommands[0];
138
+
139
+ if (!sub || sub === 'get') {
140
+ const config = readUserConfig();
141
+ const portDisplay = config.defaultPort != null
142
+ ? String(config.defaultPort)
143
+ : '(not set — uses 3000–3010 auto-range)';
144
+ console.log(`defaultPort: ${portDisplay}`);
145
+ return 0;
146
+ }
147
+
148
+ if (sub === 'set') {
149
+ const key = subcommands[1];
150
+ const value = subcommands[2];
151
+ if (key === 'port') {
152
+ if (!value) {
153
+ console.error('iranti-cp config set port: missing port number');
154
+ return 1;
155
+ }
156
+ const parsed = Number.parseInt(value, 10);
157
+ if (!Number.isFinite(parsed) || parsed < 1024 || parsed > 65535) {
158
+ console.error(`iranti-cp config set port: "${value}" is not a valid port (1024–65535)`);
159
+ return 1;
160
+ }
161
+ const config = readUserConfig();
162
+ config.defaultPort = parsed;
163
+ writeUserConfig(config);
164
+ console.log(`Default port set to ${parsed}. Takes effect on next iranti-cp start.`);
165
+ return 0;
166
+ }
167
+ console.error(`iranti-cp config set: unknown key "${key !== undefined ? key : ''}". Supported: port`);
168
+ return 1;
169
+ }
170
+
171
+ if (sub === 'unset') {
172
+ const key = subcommands[1];
173
+ if (key === 'port') {
174
+ const config = readUserConfig();
175
+ delete config.defaultPort;
176
+ writeUserConfig(config);
177
+ console.log('Default port cleared. Will use 3000–3010 auto-range on next start.');
178
+ return 0;
179
+ }
180
+ console.error(`iranti-cp config unset: unknown key "${key !== undefined ? key : ''}". Supported: port`);
181
+ return 1;
182
+ }
183
+
184
+ console.error(`iranti-cp config: unknown subcommand "${sub}". Use get, set, or unset.`);
185
+ return 1;
186
+ }
187
+
108
188
  async function fetchJson(url, timeoutMs = 1500) {
109
189
  const controller = new AbortController();
110
190
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -229,6 +309,25 @@ function spawnControlPlane({ port, openBrowser, detached }) {
229
309
  if (port) env.CONTROL_PLANE_PORT = String(port);
230
310
  if (!openBrowser) env.IRANTI_CP_NO_OPEN = '1';
231
311
 
312
+ // On Windows, spawning node.exe directly causes Windows Terminal (wt.exe)
313
+ // to intercept the new console process and flash a tab, even with
314
+ // windowsHide:true. windowsHide suppresses a standalone console window but
315
+ // does not prevent Windows Terminal from attaching.
316
+ //
317
+ // Routing through `cmd /c start "" /b node bundle.cjs` creates the child
318
+ // with the DETACHED_PROCESS flag implicitly, so no console is allocated and
319
+ // Windows Terminal never attaches. This is only needed for the detached
320
+ // (background) case; foreground start keeps stdio:inherit as usual.
321
+ if (process.platform === 'win32' && detached) {
322
+ const child = spawn(
323
+ 'cmd',
324
+ ['/d', '/s', '/c', 'start', '', '/b', process.execPath, BUNDLE],
325
+ { env, detached: true, stdio: 'ignore', windowsHide: true },
326
+ );
327
+ child.unref();
328
+ return child;
329
+ }
330
+
232
331
  const child = spawn(process.execPath, [BUNDLE], {
233
332
  env,
234
333
  stdio: detached ? 'ignore' : 'inherit',
@@ -527,6 +626,8 @@ async function main() {
527
626
  exitCode = await runIrantiProxy(['doctor', ...rest]);
528
627
  } else if (command === 'upgrade') {
529
628
  exitCode = await handleUpgrade(rest[0] || 'self', rest.slice(1));
629
+ } else if (command === 'config') {
630
+ exitCode = await handleConfig(rest);
530
631
  } else {
531
632
  console.error(`iranti-cp: unknown command "${command}".`);
532
633
  printHelp();