insta 0.0.42 → 0.0.44
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 +1 -1
- package/dist/commands/setup.js +67 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,7 +157,7 @@ That host is a CloudFront cache, so after a change to the installer it can serve
|
|
|
157
157
|
previous copy for up to about a day. This form is equivalent and always current:
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
|
-
curl -fsSL https://raw.githubusercontent.com/InsForge/insta-cli/main/install.sh | sh -s -- --agents --staging
|
|
160
|
+
curl -fsSL https://raw.githubusercontent.com/InsForge/insta-cli/main/install.sh | sh -s -- --agents --staging
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
If the environment cannot be applied — an installed CLI older than 0.0.23 has no `insta
|
package/dist/commands/setup.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// Stack skills (tigris/better-auth) intentionally stay per-project: their presence in a
|
|
7
7
|
// project doubles as its stack manifest — that install happens on `project create|link`.
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
9
|
+
import { closeSync, createReadStream, existsSync, openSync, readFileSync, statSync } from 'node:fs';
|
|
10
10
|
import { dirname, join } from 'node:path';
|
|
11
11
|
import os from 'node:os';
|
|
12
12
|
import { createInterface } from 'node:readline';
|
|
@@ -74,13 +74,13 @@ export function parseInstalledAgents(output) {
|
|
|
74
74
|
export function summarizeInstall(output) {
|
|
75
75
|
const { count, names } = parseInstalledAgents(output);
|
|
76
76
|
if (count === 0)
|
|
77
|
-
return '✓
|
|
77
|
+
return '✓ Agents set up';
|
|
78
78
|
const shown = names.slice(0, 6);
|
|
79
79
|
const more = count - shown.length;
|
|
80
80
|
const list = shown.length
|
|
81
81
|
? shown.join(', ') + (more > 0 ? `, +${more} more` : '')
|
|
82
82
|
: `${count} agent${count === 1 ? '' : 's'}`;
|
|
83
|
-
return `✓
|
|
83
|
+
return `✓ Agents — ${list}`;
|
|
84
84
|
}
|
|
85
85
|
// ---- CLI self-install (makes `npx -y insta setup agent` a complete one-liner) ----
|
|
86
86
|
// Under npx the CLI runs from the npm cache and vanishes when the process exits — but the skill
|
|
@@ -357,8 +357,55 @@ export function shouldOfferLogin(yes, loggedIn, stdinTty, stdoutTty) {
|
|
|
357
357
|
// One Enter continues into the browser login; only an explicit n/no declines. Matches the
|
|
358
358
|
// curl-installer feel: the single command carries you as far as automation can go, and the one
|
|
359
359
|
// genuinely human step (authorizing in the browser) starts itself instead of being homework.
|
|
360
|
+
// Where to read the answer from. Under `curl … | sh` stdin is the SCRIPT pipe, not the human —
|
|
361
|
+
// but the controlling terminal can still answer, via /dev/tty (the standard installer trick;
|
|
362
|
+
// Homebrew prompts the same way). Never on Windows (no /dev/tty, and the curl path doesn't exist
|
|
363
|
+
// there), and never without one (agents, CI, cron — openSync fails, so they can't be prompted).
|
|
364
|
+
/** Wrap an already-open fd as a prompt input with SINGLE-OWNER cleanup: the stream owns the fd
|
|
365
|
+
* (autoClose), close() only destroys the stream, and post-close stream errors are swallowed.
|
|
366
|
+
* ReadStream.destroy() closes the fd asynchronously on Node 20, so a second closeSync here
|
|
367
|
+
* would race it into an unhandled EBADF right after the user answers. */
|
|
368
|
+
export function makePromptSource(fd) {
|
|
369
|
+
const stream = createReadStream('', { fd, autoClose: true });
|
|
370
|
+
stream.on('error', () => { });
|
|
371
|
+
return { input: stream, close: () => { try {
|
|
372
|
+
stream.destroy();
|
|
373
|
+
}
|
|
374
|
+
catch { /* already destroyed */ } } };
|
|
375
|
+
}
|
|
376
|
+
const openPromptInput = () => {
|
|
377
|
+
if (process.stdin.isTTY)
|
|
378
|
+
return { input: process.stdin, close: () => { } };
|
|
379
|
+
if (process.platform === 'win32')
|
|
380
|
+
return null;
|
|
381
|
+
try {
|
|
382
|
+
return makePromptSource(openSync('/dev/tty', 'r'));
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
/** Whether a human can answer a prompt at all: an interactive stdin, or a reachable /dev/tty
|
|
389
|
+
* (the curl|sh case). The stdout-TTY requirement lives in shouldOfferLogin — an agent piping
|
|
390
|
+
* our output must never be prompted even though its process may have a controlling terminal. */
|
|
391
|
+
export function canPromptViaTty() {
|
|
392
|
+
if (process.stdin.isTTY)
|
|
393
|
+
return true;
|
|
394
|
+
if (process.platform === 'win32')
|
|
395
|
+
return false;
|
|
396
|
+
try {
|
|
397
|
+
closeSync(openSync('/dev/tty', 'r'));
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
360
404
|
const defaultAsk = async (question) => {
|
|
361
|
-
const
|
|
405
|
+
const src = openPromptInput();
|
|
406
|
+
if (!src)
|
|
407
|
+
return false; // gate said yes but the terminal vanished — decline, never hang
|
|
408
|
+
const rl = createInterface({ input: src.input, output: process.stdout });
|
|
362
409
|
// EOF (Ctrl-D) closes the interface without ever answering the question — resolve that as a
|
|
363
410
|
// decline instead of hanging forever after the checkmarks.
|
|
364
411
|
const answer = await new Promise((resolve) => {
|
|
@@ -366,12 +413,13 @@ const defaultAsk = async (question) => {
|
|
|
366
413
|
rl.question(question, resolve);
|
|
367
414
|
});
|
|
368
415
|
rl.close();
|
|
416
|
+
src.close();
|
|
369
417
|
return !/^n/i.test(answer.trim());
|
|
370
418
|
};
|
|
371
419
|
export async function setupAgent(opts, run = defaultRunner, mint, installConfigs = installAgentConfigs, ensure = (r) => ensureCliInstalled(r), readStored = readPersistedGlobal, switchEnv = (n) => envUse(n), loginFlow = {
|
|
372
420
|
ask: defaultAsk,
|
|
373
421
|
login: () => loginOauth('github', {}),
|
|
374
|
-
stdinTty:
|
|
422
|
+
stdinTty: canPromptViaTty(),
|
|
375
423
|
stdoutTty: !!process.stdout.isTTY,
|
|
376
424
|
}) {
|
|
377
425
|
if (!opts.yes && !process.stdout.isTTY) {
|
|
@@ -392,8 +440,8 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
|
|
|
392
440
|
const { env, skills } = await resolveEnv();
|
|
393
441
|
const args = setupArgs(skills);
|
|
394
442
|
info(env && env !== DEFAULT_ENV
|
|
395
|
-
? `setting up coding
|
|
396
|
-
: 'setting up coding
|
|
443
|
+
? `setting up your coding agents (${env}) …`
|
|
444
|
+
: 'setting up your coding agents …');
|
|
397
445
|
const res = await run('npx', args);
|
|
398
446
|
if (!res.ok) {
|
|
399
447
|
info(' skill install failed — install manually with:');
|
|
@@ -409,9 +457,9 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
|
|
|
409
457
|
process.exitCode = 1;
|
|
410
458
|
return;
|
|
411
459
|
}
|
|
412
|
-
|
|
413
|
-
//
|
|
414
|
-
//
|
|
460
|
+
// Register everything first, then summarize ONCE below: the skill files, Claude Code's
|
|
461
|
+
// `claude mcp add`, and the config-file MCP entries are three mechanisms with one outcome —
|
|
462
|
+
// "your agents are ready" — so they get one line, not three inventories of agent names.
|
|
415
463
|
let claude = await registerMcp(run, mint, !!opts.mcpToken, false);
|
|
416
464
|
const others = await installConfigs();
|
|
417
465
|
// Default into login on an interactive terminal (see shouldOfferLogin) BEFORE the MCP summary:
|
|
@@ -434,21 +482,18 @@ export async function setupAgent(opts, run = defaultRunner, mint, installConfigs
|
|
|
434
482
|
}
|
|
435
483
|
}
|
|
436
484
|
}
|
|
437
|
-
//
|
|
438
|
-
// at startup;
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
info(`✓ MCP — ${mcpTargets.join(', ')}${others.length ? ' (already-running tools load it on restart)' : ''}`);
|
|
442
|
-
}
|
|
485
|
+
// THE summary line. The restart note exists because config-file agents only read their MCP
|
|
486
|
+
// config at startup; the skill files need no restart.
|
|
487
|
+
const mcpOk = claude === 'new' || claude === 'existing' || others.length > 0;
|
|
488
|
+
info(`${summarizeInstall(res.output ?? '')} — ready to use InstaCloud${mcpOk ? ' (skill + MCP; restart any open tools)' : ''}`);
|
|
443
489
|
if (claude === 'new' && !opts.mcpToken) {
|
|
444
490
|
info(' Claude Code first use: run `/mcp` and authorize in the browser (headless machines: `insta setup agent --mcp-token`)');
|
|
445
491
|
}
|
|
446
|
-
// The
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
// even login. The human's next move is simply to go build.
|
|
492
|
+
// The user's next move: one concrete action, not a concept. The agents drive `insta` themselves
|
|
493
|
+
// (project create/link, deploys, login via the device flow), so the human just asks for the
|
|
494
|
+
// thing they actually want.
|
|
450
495
|
info(loggedIn
|
|
451
|
-
? 'next: open
|
|
452
|
-
: 'next: open
|
|
496
|
+
? 'next: open your coding agent inside your app and start building — ask it to "deploy this app on InstaCloud" when you\'re ready'
|
|
497
|
+
: 'next: open your coding agent inside your app and start building — ask it to "deploy this app on InstaCloud" when you\'re ready (it will walk you through `insta login`)');
|
|
453
498
|
}
|
|
454
499
|
//# sourceMappingURL=setup.js.map
|