@addai/node 0.8.1 → 0.9.0
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 +96 -96
- package/dist/autostart-mac.js +30 -30
- package/dist/autostart-win.js +50 -50
- package/dist/cli.js +24 -21
- package/dist/config.d.ts +1 -0
- package/dist/config.js +18 -3
- package/dist/heartbeat.js +46 -0
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -7
- package/dist/open-browser.d.ts +1 -0
- package/dist/open-browser.js +42 -0
- package/dist/pair-destination.d.ts +20 -0
- package/dist/pair-destination.js +59 -0
- package/dist/pairing.d.ts +4 -2
- package/dist/pairing.js +64 -16
- package/dist/request-pump.d.ts +1 -0
- package/dist/request-pump.js +9 -0
- package/dist/session-runner.js +2 -1
- package/dist/tui/pair.d.ts +33 -0
- package/dist/tui/pair.js +178 -0
- package/package.json +60 -60
- package/scripts/fix-pty-helper.js +28 -28
- package/scripts/precompact-capture.js +292 -292
- package/scripts/probe-tui.mjs +122 -122
- package/scripts/smoke-test.sh +74 -74
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Open a URL in whatever the user calls their browser.
|
|
3
|
+
//
|
|
4
|
+
// No dependency for this. The three commands are stable, and a package that
|
|
5
|
+
// shells out for you is a package that can break pairing on a platform we
|
|
6
|
+
// cannot test from here.
|
|
7
|
+
//
|
|
8
|
+
// Windows deliberately does NOT go through cmd.exe. `start` is a shell
|
|
9
|
+
// builtin, so reaching it means spawning a shell around a URL, and this
|
|
10
|
+
// codebase has already been burned by putting cmd.exe near strings it did not
|
|
11
|
+
// control. rundll32 takes the URL as a plain argument and hands it to the
|
|
12
|
+
// registered handler.
|
|
13
|
+
//
|
|
14
|
+
// Everything here is best-effort by design. A browser that fails to open is
|
|
15
|
+
// not a failed pairing — the terminal still shows the code and the link, and
|
|
16
|
+
// the user carries on by hand. So this never throws and never blocks: it
|
|
17
|
+
// reports whether the launch was ACCEPTED, which is all the caller can
|
|
18
|
+
// honestly say.
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.openBrowser = openBrowser;
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
22
|
+
function openBrowser(url, platform = process.platform) {
|
|
23
|
+
const [cmd, args] = platform === 'darwin' ? ['open', [url]]
|
|
24
|
+
: platform === 'win32' ? ['rundll32', ['url.dll,FileProtocolHandler', url]]
|
|
25
|
+
: ['xdg-open', [url]];
|
|
26
|
+
try {
|
|
27
|
+
const child = (0, child_process_1.spawn)(cmd, args, {
|
|
28
|
+
stdio: 'ignore',
|
|
29
|
+
// The browser outlives us — and on a machine where the daemon is about
|
|
30
|
+
// to take over the terminal, a child holding the TTY would fight it.
|
|
31
|
+
detached: true,
|
|
32
|
+
});
|
|
33
|
+
// A missing xdg-open surfaces here rather than as an unhandled error that
|
|
34
|
+
// takes the daemon down with it.
|
|
35
|
+
child.on('error', () => { });
|
|
36
|
+
child.unref();
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface PairDestination {
|
|
2
|
+
/** What to call it on screen. */
|
|
3
|
+
product: string;
|
|
4
|
+
/** Where the user goes to confirm. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** That URL carrying the code, which is what actually gets opened. */
|
|
7
|
+
link(code: string): string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* @param argv process.argv.slice(2)
|
|
11
|
+
* @param env process.env
|
|
12
|
+
*/
|
|
13
|
+
export declare function pairDestination(argv: string[], env?: NodeJS.ProcessEnv): PairDestination;
|
|
14
|
+
export interface BrowserEnv {
|
|
15
|
+
isTTY: boolean;
|
|
16
|
+
platform: NodeJS.Platform | string;
|
|
17
|
+
env: NodeJS.ProcessEnv;
|
|
18
|
+
}
|
|
19
|
+
/** Would opening a browser here actually show the user anything? */
|
|
20
|
+
export declare function canOpenBrowser({ isTTY, platform, env }: BrowserEnv): boolean;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Which product this machine is joining, and whether we can open a browser
|
|
3
|
+
// to finish the job.
|
|
4
|
+
//
|
|
5
|
+
// The command the user ran is the answer to the first question. They copied it
|
|
6
|
+
// off a page that already knew — so asking "which product?" in the terminal
|
|
7
|
+
// would be asking them to repeat themselves. `ainode entities` goes to Entity
|
|
8
|
+
// Studio; everything else keeps going to Vault, exactly as it always has.
|
|
9
|
+
//
|
|
10
|
+
// The second question is the one that decides whether pairing feels like
|
|
11
|
+
// magic or like a broken promise. Opening a browser on a machine nobody is
|
|
12
|
+
// sitting at does nothing; opening one over SSH opens it on the wrong
|
|
13
|
+
// computer entirely. Both are common, so both are checked, and when the
|
|
14
|
+
// answer is no the code gets printed like it always did.
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.pairDestination = pairDestination;
|
|
17
|
+
exports.canOpenBrowser = canOpenBrowser;
|
|
18
|
+
const config_1 = require("./config");
|
|
19
|
+
const destination = (product, url) => ({
|
|
20
|
+
product,
|
|
21
|
+
url,
|
|
22
|
+
link: (code) => `${url}/${encodeURIComponent(code)}`,
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* @param argv process.argv.slice(2)
|
|
26
|
+
* @param env process.env
|
|
27
|
+
*/
|
|
28
|
+
function pairDestination(argv, env = process.env) {
|
|
29
|
+
const entities = argv[0] === 'entities';
|
|
30
|
+
// A dev override has to move BOTH products or a local Studio ends up
|
|
31
|
+
// pairing against production Vault, which is a confusing way to lose an
|
|
32
|
+
// afternoon.
|
|
33
|
+
const base = env.AINODE_PAIR_BASE?.replace(/\/+$/, '');
|
|
34
|
+
if (base) {
|
|
35
|
+
return entities
|
|
36
|
+
? destination('+Ai Entities', `${base}/connect`)
|
|
37
|
+
: destination('+Ai Vault', `${base}/add/entity`);
|
|
38
|
+
}
|
|
39
|
+
return entities
|
|
40
|
+
? destination('+Ai Entities', config_1.ENTITIES_PAIR_URL)
|
|
41
|
+
: destination('+Ai Vault', config_1.VAULT_PAIR_URL);
|
|
42
|
+
}
|
|
43
|
+
/** Would opening a browser here actually show the user anything? */
|
|
44
|
+
function canOpenBrowser({ isTTY, platform, env }) {
|
|
45
|
+
if (!isTTY)
|
|
46
|
+
return false;
|
|
47
|
+
if (env.AINODE_NO_BROWSER === '1')
|
|
48
|
+
return false;
|
|
49
|
+
// Over SSH the browser would open on the machine being administered, where
|
|
50
|
+
// nobody is looking at it — and the terminal would sit there claiming it
|
|
51
|
+
// had done something helpful.
|
|
52
|
+
if (env.SSH_TTY || env.SSH_CONNECTION)
|
|
53
|
+
return false;
|
|
54
|
+
// A Linux box with no display server has no browser to open. macOS and
|
|
55
|
+
// Windows always have one.
|
|
56
|
+
if (platform === 'linux' && !env.DISPLAY && !env.WAYLAND_DISPLAY)
|
|
57
|
+
return false;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
package/dist/pairing.d.ts
CHANGED
|
@@ -14,7 +14,9 @@ export declare function requestPairCode(): Promise<string>;
|
|
|
14
14
|
*/
|
|
15
15
|
export declare function awaitPairing(code: string): Promise<FinalizeResult>;
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
17
|
+
* Run the whole first-run flow: ask, open, wait, save.
|
|
18
|
+
*
|
|
19
|
+
* @param argv process.argv.slice(2) — decides which product to join
|
|
18
20
|
*/
|
|
19
|
-
export declare function
|
|
21
|
+
export declare function runPairing(argv: string[]): Promise<FinalizeResult>;
|
|
20
22
|
export {};
|
package/dist/pairing.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// First-run pairing flow. Called when state.json doesn't exist.
|
|
3
|
-
//
|
|
4
|
-
//
|
|
2
|
+
// First-run pairing flow. Called when state.json doesn't exist.
|
|
3
|
+
//
|
|
4
|
+
// The machine now does the walking. It asks one question, opens the user's
|
|
5
|
+
// browser at the confirm page carrying its own code, and waits — so the
|
|
6
|
+
// person types nothing and never has to know a code existed. Where that
|
|
7
|
+
// cannot work (no terminal, over SSH, headless Linux) it prints the code and
|
|
8
|
+
// the link exactly as it always did, because those machines are real and are
|
|
9
|
+
// often the ones that most need a node on them.
|
|
10
|
+
//
|
|
11
|
+
// Which product the browser lands on comes from the command the user ran:
|
|
12
|
+
// `ainode entities` goes to Entity Studio, everything else to Vault.
|
|
5
13
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
14
|
if (k2 === undefined) k2 = k;
|
|
7
15
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -38,10 +46,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
38
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
47
|
exports.requestPairCode = requestPairCode;
|
|
40
48
|
exports.awaitPairing = awaitPairing;
|
|
41
|
-
exports.
|
|
49
|
+
exports.runPairing = runPairing;
|
|
42
50
|
const os = __importStar(require("os"));
|
|
43
51
|
const supabase_client_1 = require("./supabase-client");
|
|
44
52
|
const store_1 = require("./store");
|
|
53
|
+
const pair_destination_1 = require("./pair-destination");
|
|
54
|
+
const open_browser_1 = require("./open-browser");
|
|
55
|
+
const pair_1 = require("./tui/pair");
|
|
45
56
|
const config_1 = require("./config");
|
|
46
57
|
function platform() {
|
|
47
58
|
const p = process.platform;
|
|
@@ -113,17 +124,54 @@ async function awaitPairing(code) {
|
|
|
113
124
|
throw new Error('pair_timeout');
|
|
114
125
|
}
|
|
115
126
|
/**
|
|
116
|
-
*
|
|
127
|
+
* Run the whole first-run flow: ask, open, wait, save.
|
|
128
|
+
*
|
|
129
|
+
* @param argv process.argv.slice(2) — decides which product to join
|
|
117
130
|
*/
|
|
118
|
-
function
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
async function runPairing(argv) {
|
|
132
|
+
const dest = (0, pair_destination_1.pairDestination)(argv);
|
|
133
|
+
const hostname = os.hostname();
|
|
134
|
+
const interactive = (0, pair_destination_1.canOpenBrowser)({
|
|
135
|
+
isTTY: Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
|
|
136
|
+
platform: process.platform,
|
|
137
|
+
env: process.env,
|
|
138
|
+
});
|
|
139
|
+
// Ask BEFORE minting a code. A code starts a 15-minute clock the moment it
|
|
140
|
+
// exists, and someone who walks away at the question should not come back
|
|
141
|
+
// to an expired one.
|
|
142
|
+
const choice = interactive ? await (0, pair_1.promptConnect)({ product: dest.product, hostname }) : 'manual';
|
|
143
|
+
const code = await requestPairCode();
|
|
144
|
+
const link = dest.link(code);
|
|
145
|
+
const opened = choice === 'open' ? (0, open_browser_1.openBrowser)(link) : false;
|
|
146
|
+
let waiting = null;
|
|
147
|
+
if (interactive) {
|
|
148
|
+
waiting = (0, pair_1.renderWaiting)({ product: dest.product, link, code, opened });
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Headless: plain lines, no spinner, no cursor games. This output ends up
|
|
152
|
+
// in a log file as often as on a screen.
|
|
153
|
+
console.log('');
|
|
154
|
+
console.log(` Connect this machine to ${dest.product}`);
|
|
155
|
+
console.log('');
|
|
156
|
+
console.log(` 1. Open ${dest.url}`);
|
|
157
|
+
console.log(` 2. Enter this code: ${code}`);
|
|
158
|
+
console.log('');
|
|
159
|
+
console.log(` (or open directly: ${link})`);
|
|
160
|
+
console.log('');
|
|
161
|
+
console.log(' Waiting for pairing… (expires in 15 minutes)');
|
|
162
|
+
console.log('');
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
const result = await awaitPairing(code);
|
|
166
|
+
waiting?.stop();
|
|
167
|
+
if (interactive)
|
|
168
|
+
(0, pair_1.renderConnected)(hostname);
|
|
169
|
+
else
|
|
170
|
+
console.log(` ✓ Connected as ${hostname}`);
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
waiting?.stop();
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
129
177
|
}
|
package/dist/request-pump.d.ts
CHANGED
package/dist/request-pump.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// arrives.
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.inflightCount = inflightCount;
|
|
8
|
+
exports.activeRequestIdList = activeRequestIdList;
|
|
8
9
|
exports.start = start;
|
|
9
10
|
exports.stop = stop;
|
|
10
11
|
exports.drain = drain;
|
|
@@ -59,6 +60,14 @@ let lastHealthBlockAt = 0;
|
|
|
59
60
|
// Exported so /stats can surface "pump stuck at N inflight" without
|
|
60
61
|
// having to guess.
|
|
61
62
|
function inflightCount() { return inflight; }
|
|
63
|
+
// The runs this daemon is holding a child process for, right now. This is the
|
|
64
|
+
// only honest answer to "is that run alive?" — the server used to infer it from
|
|
65
|
+
// how recently the agent emitted an event, which measures how chatty the agent
|
|
66
|
+
// is, not whether it is alive. A grok turn routinely goes ten minutes between
|
|
67
|
+
// its last streamed token and turn_complete, and the server was failing those
|
|
68
|
+
// runs mid-flight and apologising to the user for a reply that had already
|
|
69
|
+
// arrived. Reported every 30s by the heartbeat.
|
|
70
|
+
function activeRequestIdList() { return [...activeRequestIds]; }
|
|
62
71
|
async function tick() {
|
|
63
72
|
if (stopped)
|
|
64
73
|
return;
|
package/dist/session-runner.js
CHANGED
|
@@ -2206,7 +2206,8 @@ async function runRequest(req) {
|
|
|
2206
2206
|
if (installed.mcps?.some((m) => m.slug === 'entity-self-mcp')) {
|
|
2207
2207
|
const livingPresence = [
|
|
2208
2208
|
'LIVING PRESENCE — narrate your work so people can see what you are doing in real time.',
|
|
2209
|
-
'Whenever you are working on a specific table record (a card / task / row), call the `report_status` tool at your real milestones: when you claim/start it (status "working", a low progress, a short
|
|
2209
|
+
'Whenever you are working on a specific table record (a card / task / row), call the `report_status` tool at your real milestones: when you claim/start it (status "working", a low progress, a short note), at each checkpoint (bump progress + update the note), if you get stuck and need a human (status "blocked"), and when you finish (status "done"). ALWAYS pass the record_id you are working on; if you are working on several records at once, report each separately with its own record_id so they never overwrite each other. This is lightweight — a handful of milestone updates per task; progress is a rough milestone percent (0-100), not a precise measurement.',
|
|
2210
|
+
'THE `note` MUST BE ULTRA-SHORT — 1 to 3 plain, friendly words a teammate could read at a glance, e.g. "Reviewing", "Playing widget", "Writing summary", "Checking design", "Almost done". It renders in a tiny bubble, so NEVER write a sentence and NEVER use technical or internal jargon (not "waiting for publish propagation" — just "Publishing"). Think status label, not explanation.',
|
|
2210
2211
|
].join('\n');
|
|
2211
2212
|
req.system_prompt =
|
|
2212
2213
|
req.system_prompt && req.system_prompt.trim().length > 0
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type PairChoice = 'open' | 'manual';
|
|
2
|
+
interface PromptOpts {
|
|
3
|
+
/** '+Ai Entities' — what the user thinks they are joining. */
|
|
4
|
+
product: string;
|
|
5
|
+
hostname: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Ask whether to open the browser. Resolves the user's answer; never rejects.
|
|
9
|
+
*
|
|
10
|
+
* Ctrl-C here means "I did not want to do this at all", so it exits rather
|
|
11
|
+
* than falling through to a code the user never asked for.
|
|
12
|
+
*/
|
|
13
|
+
export declare function promptConnect({ product, hostname }: PromptOpts): Promise<PairChoice>;
|
|
14
|
+
export interface WaitingHandle {
|
|
15
|
+
/** Stop the spinner and leave the block on screen. */
|
|
16
|
+
stop(): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The block that stays up while the daemon waits to be claimed.
|
|
20
|
+
*
|
|
21
|
+
* It shows the code and the link even when the browser opened fine. That is
|
|
22
|
+
* not clutter — a browser that opened on the wrong profile, or behind another
|
|
23
|
+
* window, is invisible from here, and the only recovery is the thing this
|
|
24
|
+
* block is holding.
|
|
25
|
+
*/
|
|
26
|
+
export declare function renderWaiting(opts: {
|
|
27
|
+
product: string;
|
|
28
|
+
link: string;
|
|
29
|
+
code: string;
|
|
30
|
+
opened: boolean;
|
|
31
|
+
}): WaitingHandle;
|
|
32
|
+
export declare function renderConnected(name: string): void;
|
|
33
|
+
export {};
|
package/dist/tui/pair.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The first thing anyone sees: one question, then the browser opens.
|
|
3
|
+
//
|
|
4
|
+
// This is not a screen on the console stack, and deliberately so. That stack
|
|
5
|
+
// owns the alt screen and exits the process when it quits — both wrong here.
|
|
6
|
+
// Pairing has to hand back to a daemon that then runs for months, and the code
|
|
7
|
+
// and link have to stay in scrollback afterwards, because the most likely
|
|
8
|
+
// reason someone is still reading this block is that the browser didn't open.
|
|
9
|
+
//
|
|
10
|
+
// So: no alt screen, no takeover. A question that repaints in place, then a
|
|
11
|
+
// block that stays on the terminal and a spinner line that updates until the
|
|
12
|
+
// machine is through.
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.promptConnect = promptConnect;
|
|
48
|
+
exports.renderWaiting = renderWaiting;
|
|
49
|
+
exports.renderConnected = renderConnected;
|
|
50
|
+
const readline = __importStar(require("readline"));
|
|
51
|
+
const render_1 = require("./render");
|
|
52
|
+
const E = '\x1b[';
|
|
53
|
+
const write = (s) => { process.stdout.write(s); };
|
|
54
|
+
/** Move up n lines and clear from there down — how a block repaints in place. */
|
|
55
|
+
const rewind = (n) => { if (n > 0)
|
|
56
|
+
write(`${E}${n}A${E}0J`); };
|
|
57
|
+
const CHOICES = [
|
|
58
|
+
{ id: 'open', label: 'Yes, open my browser' },
|
|
59
|
+
{ id: 'manual', label: 'No, show me a code instead' },
|
|
60
|
+
];
|
|
61
|
+
/**
|
|
62
|
+
* Ask whether to open the browser. Resolves the user's answer; never rejects.
|
|
63
|
+
*
|
|
64
|
+
* Ctrl-C here means "I did not want to do this at all", so it exits rather
|
|
65
|
+
* than falling through to a code the user never asked for.
|
|
66
|
+
*/
|
|
67
|
+
function promptConnect({ product, hostname }) {
|
|
68
|
+
return new Promise(resolve => {
|
|
69
|
+
let sel = 0;
|
|
70
|
+
let painted = 0;
|
|
71
|
+
const frame = () => [
|
|
72
|
+
'',
|
|
73
|
+
` ${(0, render_1.bold)((0, render_1.cyan)('✦'))} ${(0, render_1.bold)('+Ai Node')}`,
|
|
74
|
+
'',
|
|
75
|
+
` Connect ${(0, render_1.bold)(hostname)} to ${(0, render_1.bold)(product)}?`,
|
|
76
|
+
'',
|
|
77
|
+
...CHOICES.map((c, i) => i === sel ? ` ${(0, render_1.green)('▸')} ${c.label}` : ` ${(0, render_1.grey)(c.label)}`),
|
|
78
|
+
'',
|
|
79
|
+
` ${(0, render_1.dim)('↑↓ to choose · ⏎ to continue · ^C to cancel')}`,
|
|
80
|
+
'',
|
|
81
|
+
];
|
|
82
|
+
const paint = () => {
|
|
83
|
+
rewind(painted);
|
|
84
|
+
const lines = frame();
|
|
85
|
+
write(lines.join('\n') + '\n');
|
|
86
|
+
painted = lines.length;
|
|
87
|
+
};
|
|
88
|
+
const done = (choice) => {
|
|
89
|
+
rewind(painted);
|
|
90
|
+
process.stdin.off('keypress', onKey);
|
|
91
|
+
if (process.stdin.isTTY)
|
|
92
|
+
process.stdin.setRawMode(false);
|
|
93
|
+
process.stdin.pause();
|
|
94
|
+
resolve(choice);
|
|
95
|
+
};
|
|
96
|
+
function onKey(_s, key) {
|
|
97
|
+
if (!key)
|
|
98
|
+
return;
|
|
99
|
+
if (key.ctrl && key.name === 'c') {
|
|
100
|
+
rewind(painted);
|
|
101
|
+
write(` ${(0, render_1.dim)('Cancelled. Run the command again when you are ready.')}\n\n`);
|
|
102
|
+
process.exit(130);
|
|
103
|
+
}
|
|
104
|
+
if (key.name === 'up' || key.name === 'k') {
|
|
105
|
+
sel = (sel + CHOICES.length - 1) % CHOICES.length;
|
|
106
|
+
paint();
|
|
107
|
+
}
|
|
108
|
+
else if (key.name === 'down' || key.name === 'j') {
|
|
109
|
+
sel = (sel + 1) % CHOICES.length;
|
|
110
|
+
paint();
|
|
111
|
+
}
|
|
112
|
+
else if (key.name === 'y')
|
|
113
|
+
done('open');
|
|
114
|
+
else if (key.name === 'n')
|
|
115
|
+
done('manual');
|
|
116
|
+
else if (key.name === 'return' || key.name === 'space')
|
|
117
|
+
done(CHOICES[sel].id);
|
|
118
|
+
}
|
|
119
|
+
readline.emitKeypressEvents(process.stdin);
|
|
120
|
+
if (process.stdin.isTTY)
|
|
121
|
+
process.stdin.setRawMode(true);
|
|
122
|
+
process.stdin.resume();
|
|
123
|
+
process.stdin.on('keypress', onKey);
|
|
124
|
+
paint();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* The block that stays up while the daemon waits to be claimed.
|
|
129
|
+
*
|
|
130
|
+
* It shows the code and the link even when the browser opened fine. That is
|
|
131
|
+
* not clutter — a browser that opened on the wrong profile, or behind another
|
|
132
|
+
* window, is invisible from here, and the only recovery is the thing this
|
|
133
|
+
* block is holding.
|
|
134
|
+
*/
|
|
135
|
+
function renderWaiting(opts) {
|
|
136
|
+
const { product, link, code, opened } = opts;
|
|
137
|
+
write('\n');
|
|
138
|
+
if (opened) {
|
|
139
|
+
write(` ${(0, render_1.green)('✦')} ${(0, render_1.bold)(`Waiting in your browser…`)}\n`);
|
|
140
|
+
write('\n');
|
|
141
|
+
write(` ${(0, render_1.dim)('Nothing opened? Go to')} ${(0, render_1.cyan)(link)}\n`);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
write(` ${(0, render_1.bold)(`Connect this machine to ${product}`)}\n`);
|
|
145
|
+
write('\n');
|
|
146
|
+
write(` ${(0, render_1.dim)('1.')} Open ${(0, render_1.cyan)(link.replace(`/${code}`, ''))}\n`);
|
|
147
|
+
write(` ${(0, render_1.dim)('2.')} Enter this code: ${(0, render_1.bold)((0, render_1.green)(code))}\n`);
|
|
148
|
+
write('\n');
|
|
149
|
+
write(` ${(0, render_1.dim)('or open this directly:')} ${(0, render_1.cyan)(link)}\n`);
|
|
150
|
+
}
|
|
151
|
+
if (opened)
|
|
152
|
+
write(` ${(0, render_1.dim)('or enter this code:')} ${(0, render_1.bold)((0, render_1.green)(code))}\n`);
|
|
153
|
+
write('\n');
|
|
154
|
+
let n = 0;
|
|
155
|
+
let painted = false;
|
|
156
|
+
const line = () => ` ${(0, render_1.green)(render_1.SPIN[n % render_1.SPIN.length])} ${(0, render_1.dim)('waiting for you to confirm · expires in 15 minutes')}`;
|
|
157
|
+
const tick = () => {
|
|
158
|
+
if (painted)
|
|
159
|
+
rewind(1);
|
|
160
|
+
write(line() + '\n');
|
|
161
|
+
painted = true;
|
|
162
|
+
n++;
|
|
163
|
+
};
|
|
164
|
+
tick();
|
|
165
|
+
const timer = setInterval(tick, 120);
|
|
166
|
+
// A spinner is not a reason to keep the process alive.
|
|
167
|
+
timer.unref?.();
|
|
168
|
+
return {
|
|
169
|
+
stop() {
|
|
170
|
+
clearInterval(timer);
|
|
171
|
+
if (painted)
|
|
172
|
+
rewind(1);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function renderConnected(name) {
|
|
177
|
+
write(` ${(0, render_1.green)('✓')} ${(0, render_1.bold)('Connected')} ${(0, render_1.dim)('as')} ${name}\n\n`);
|
|
178
|
+
}
|
package/package.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@addai/node",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"addai",
|
|
8
|
-
"entity-studio",
|
|
9
|
-
"claude",
|
|
10
|
-
"codex",
|
|
11
|
-
"kimi",
|
|
12
|
-
"gemini",
|
|
13
|
-
"agent",
|
|
14
|
-
"daemon",
|
|
15
|
-
"supabase",
|
|
16
|
-
"mcp"
|
|
17
|
-
],
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/just-AddAi/addai-entity-runtime.git"
|
|
21
|
-
},
|
|
22
|
-
"homepage": "https://github.com/just-AddAi/addai-entity-runtime#readme",
|
|
23
|
-
"bugs": {
|
|
24
|
-
"url": "https://github.com/just-AddAi/addai-entity-runtime/issues"
|
|
25
|
-
},
|
|
26
|
-
"type": "commonjs",
|
|
27
|
-
"main": "dist/index.js",
|
|
28
|
-
"bin": {
|
|
29
|
-
"ainode": "dist/cli.js"
|
|
30
|
-
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"access": "public"
|
|
33
|
-
},
|
|
34
|
-
"files": [
|
|
35
|
-
"dist",
|
|
36
|
-
"scripts",
|
|
37
|
-
"README.md"
|
|
38
|
-
],
|
|
39
|
-
"scripts": {
|
|
40
|
-
"build": "tsc -p tsconfig.json",
|
|
41
|
-
"start": "node dist/cli.js",
|
|
42
|
-
"dev": "tsc -p tsconfig.json && node dist/cli.js",
|
|
43
|
-
"test": "npm run build && node --test test/*.test.mjs",
|
|
44
|
-
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
45
|
-
"postinstall": "node scripts/fix-pty-helper.js",
|
|
46
|
-
"prepublishOnly": "npm run build"
|
|
47
|
-
},
|
|
48
|
-
"engines": {
|
|
49
|
-
"node": ">=18"
|
|
50
|
-
},
|
|
51
|
-
"dependencies": {
|
|
52
|
-
"node-pty": "^1.1.0",
|
|
53
|
-
"ws": "^8.21.1"
|
|
54
|
-
},
|
|
55
|
-
"devDependencies": {
|
|
56
|
-
"@types/node": "^20.0.0",
|
|
57
|
-
"@types/ws": "^8.18.1",
|
|
58
|
-
"typescript": "^5.6.0"
|
|
59
|
-
}
|
|
60
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@addai/node",
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "Daemon that pairs a machine with your +Ai account and runs Claude / Codex / Kimi / Gemini agents on its behalf. Reachable via Supabase from Vault, Entity Studio, or any other +Ai surface.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"addai",
|
|
8
|
+
"entity-studio",
|
|
9
|
+
"claude",
|
|
10
|
+
"codex",
|
|
11
|
+
"kimi",
|
|
12
|
+
"gemini",
|
|
13
|
+
"agent",
|
|
14
|
+
"daemon",
|
|
15
|
+
"supabase",
|
|
16
|
+
"mcp"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/just-AddAi/addai-entity-runtime.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/just-AddAi/addai-entity-runtime#readme",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/just-AddAi/addai-entity-runtime/issues"
|
|
25
|
+
},
|
|
26
|
+
"type": "commonjs",
|
|
27
|
+
"main": "dist/index.js",
|
|
28
|
+
"bin": {
|
|
29
|
+
"ainode": "dist/cli.js"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"scripts",
|
|
37
|
+
"README.md"
|
|
38
|
+
],
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "tsc -p tsconfig.json",
|
|
41
|
+
"start": "node dist/cli.js",
|
|
42
|
+
"dev": "tsc -p tsconfig.json && node dist/cli.js",
|
|
43
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
44
|
+
"clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
|
|
45
|
+
"postinstall": "node scripts/fix-pty-helper.js",
|
|
46
|
+
"prepublishOnly": "npm run build"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=18"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"node-pty": "^1.1.0",
|
|
53
|
+
"ws": "^8.21.1"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^20.0.0",
|
|
57
|
+
"@types/ws": "^8.18.1",
|
|
58
|
+
"typescript": "^5.6.0"
|
|
59
|
+
}
|
|
60
|
+
}
|