@addai/node 0.10.0 → 0.11.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.
- package/dist/cli.js +37 -2
- package/dist/pair-destination.d.ts +10 -0
- package/dist/pair-destination.js +28 -0
- package/dist/pairing.js +15 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
//
|
|
5
5
|
// ainode run this node: daemon + live dashboard
|
|
6
6
|
// ainode entities the same, pairing into +Ai Entities rather than Vault
|
|
7
|
+
// ainode entities CODE pair straight away with a code the page minted
|
|
7
8
|
// ainode harnesses interactive harness manager (install / login)
|
|
8
9
|
// ainode startup … start this node when the machine starts
|
|
9
10
|
// ainode unpair --yes disconnect from +Ai, cancel in-flight work
|
|
@@ -53,6 +54,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
53
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
55
|
const lockfile_1 = require("./lockfile");
|
|
55
56
|
const index_1 = require("./index");
|
|
57
|
+
const pair_destination_1 = require("./pair-destination");
|
|
56
58
|
const store_1 = require("./store");
|
|
57
59
|
const unpair_1 = require("./unpair");
|
|
58
60
|
const run_1 = require("./tui/run");
|
|
@@ -139,6 +141,15 @@ async function main() {
|
|
|
139
141
|
stdinTTY: Boolean(process.stdin.isTTY),
|
|
140
142
|
noTui: process.env.AINODE_NO_TUI === '1',
|
|
141
143
|
});
|
|
144
|
+
// A code on the command line is an instruction to pair THIS machine.
|
|
145
|
+
// Anything that quietly does something else instead is worse than an
|
|
146
|
+
// error, so deal with it before the attach-to-a-running-daemon path below.
|
|
147
|
+
const given = (0, pair_destination_1.pairCodeFromArgv)(args);
|
|
148
|
+
if (given && (0, store_1.isPaired)()) {
|
|
149
|
+
console.log('This machine is already paired to +Ai — the code is not needed.');
|
|
150
|
+
console.log('Run `ainode unpair --yes` first if you want to pair it somewhere else.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
142
153
|
// A daemon may already own this node. Rather than refusing, attach
|
|
143
154
|
// read-only so "is it running?" is answerable from any terminal.
|
|
144
155
|
//
|
|
@@ -147,8 +158,14 @@ async function main() {
|
|
|
147
158
|
// later `ainode` into the viewer and returned before start() — so a node
|
|
148
159
|
// could never come back from a crash without someone deleting the file
|
|
149
160
|
// by hand. Same liveness check acquireLockfile() uses.
|
|
161
|
+
//
|
|
162
|
+
// And only when there is something to watch. Attaching to a daemon that
|
|
163
|
+
// is NOT paired makes `ainode` a closed loop: the screen it shows says
|
|
164
|
+
// "run `ainode` to pair again", and running it lands straight back here.
|
|
165
|
+
// A machine in that state — a daemon left over from before an unpair —
|
|
166
|
+
// could never be paired again from its own terminal.
|
|
150
167
|
const existing = (0, lockfile_1.readLockfile)();
|
|
151
|
-
if ((0, lockfile_1.lockfileAlive)(existing) && existing && tui) {
|
|
168
|
+
if (!given && (0, store_1.isPaired)() && (0, lockfile_1.lockfileAlive)(existing) && existing && tui) {
|
|
152
169
|
await (0, run_1.runDashboard)({
|
|
153
170
|
viewerMode: true,
|
|
154
171
|
pid: existing.pid,
|
|
@@ -157,7 +174,24 @@ async function main() {
|
|
|
157
174
|
});
|
|
158
175
|
return;
|
|
159
176
|
}
|
|
160
|
-
|
|
177
|
+
let runtime;
|
|
178
|
+
try {
|
|
179
|
+
runtime = await (0, index_1.start)(args);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const msg = err?.message ?? String(err);
|
|
183
|
+
if (/already running/.test(msg)) {
|
|
184
|
+
// Reached when another process holds the lock — commonly one left
|
|
185
|
+
// over from before an unpair. Naming the pid is the difference
|
|
186
|
+
// between a dead end and a ten-second fix.
|
|
187
|
+
console.error(msg);
|
|
188
|
+
const pid = /pid (\d+)/.exec(msg)?.[1];
|
|
189
|
+
// A pid is a diagnosis; a command is a fix.
|
|
190
|
+
console.error(pid ? `Stop it with \`kill ${pid}\`, then run this again.` : 'Stop it, then run this again.');
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
161
195
|
if (!tui) {
|
|
162
196
|
console.log(`ainode v${index_1.VERSION} running (pid ${runtime.pid}, port ${runtime.port})`);
|
|
163
197
|
await new Promise(() => { });
|
|
@@ -194,6 +228,7 @@ async function main() {
|
|
|
194
228
|
usage:
|
|
195
229
|
ainode run this +Ai Node — daemon + live dashboard
|
|
196
230
|
ainode entities the same, but pair into +Ai Entities
|
|
231
|
+
ainode entities <CODE> pair with a code from the page — no browser
|
|
197
232
|
ainode harnesses install / log in agent harnesses (interactive)
|
|
198
233
|
ainode startup enable start this node whenever you log in
|
|
199
234
|
ainode startup disable stop starting at login
|
|
@@ -18,3 +18,13 @@ export interface BrowserEnv {
|
|
|
18
18
|
}
|
|
19
19
|
/** Would opening a browser here actually show the user anything? */
|
|
20
20
|
export declare function canOpenBrowser({ isTTY, platform, env }: BrowserEnv): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The pairing code someone pasted into the command, if there is one.
|
|
23
|
+
*
|
|
24
|
+
* `npx -y @addai/node@latest entities WXYZ4` is the flow where the page mints
|
|
25
|
+
* the code and the browser never opens. Everything else here — no argument, a
|
|
26
|
+
* flag, something that is not code-shaped — falls through to the normal flow.
|
|
27
|
+
* Refusing to guess matters: a mistyped code that we accepted anyway would
|
|
28
|
+
* sit in the finalize loop for fifteen minutes before admitting it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pairCodeFromArgv(argv: string[]): string | null;
|
package/dist/pair-destination.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.pairDestination = pairDestination;
|
|
17
17
|
exports.canOpenBrowser = canOpenBrowser;
|
|
18
|
+
exports.pairCodeFromArgv = pairCodeFromArgv;
|
|
18
19
|
const config_1 = require("./config");
|
|
19
20
|
const destination = (product, url) => ({
|
|
20
21
|
product,
|
|
@@ -57,3 +58,30 @@ function canOpenBrowser({ isTTY, platform, env }) {
|
|
|
57
58
|
return false;
|
|
58
59
|
return true;
|
|
59
60
|
}
|
|
61
|
+
/** Pairing codes are five characters of A–Z and 0–9. */
|
|
62
|
+
const CODE_SHAPE = /^[A-Z0-9]{5}$/;
|
|
63
|
+
/**
|
|
64
|
+
* The pairing code someone pasted into the command, if there is one.
|
|
65
|
+
*
|
|
66
|
+
* `npx -y @addai/node@latest entities WXYZ4` is the flow where the page mints
|
|
67
|
+
* the code and the browser never opens. Everything else here — no argument, a
|
|
68
|
+
* flag, something that is not code-shaped — falls through to the normal flow.
|
|
69
|
+
* Refusing to guess matters: a mistyped code that we accepted anyway would
|
|
70
|
+
* sit in the finalize loop for fifteen minutes before admitting it.
|
|
71
|
+
*/
|
|
72
|
+
function pairCodeFromArgv(argv) {
|
|
73
|
+
for (const raw of argv) {
|
|
74
|
+
if (raw.startsWith('-'))
|
|
75
|
+
continue;
|
|
76
|
+
// Skip the subcommand itself.
|
|
77
|
+
if (['entities', 'run', 'start'].includes(raw))
|
|
78
|
+
continue;
|
|
79
|
+
const cleaned = raw.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
80
|
+
if (CODE_SHAPE.test(cleaned))
|
|
81
|
+
return cleaned;
|
|
82
|
+
// A non-flag argument that is not a code is a mistake, not a code we
|
|
83
|
+
// should keep hunting for behind it.
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
package/dist/pairing.js
CHANGED
|
@@ -82,7 +82,11 @@ async function awaitPairing(code) {
|
|
|
82
82
|
const deadline = Date.now() + config_1.PAIR_TIMEOUT_MS;
|
|
83
83
|
while (Date.now() < deadline) {
|
|
84
84
|
try {
|
|
85
|
-
const rows = await (0, supabase_client_1.rpc)('runtime_pair_finalize',
|
|
85
|
+
const rows = await (0, supabase_client_1.rpc)('runtime_pair_finalize',
|
|
86
|
+
// The machine is the authority on what it is called. A code minted by
|
|
87
|
+
// the browser carries no hostname, so without this every node paired
|
|
88
|
+
// from the page arrives called "Untitled runtime".
|
|
89
|
+
{ p_code: code, p_hostname: os.hostname(), p_platform: platform() });
|
|
86
90
|
// PostgREST returns a `setof record` as an array of objects.
|
|
87
91
|
const row = Array.isArray(rows) ? rows[0] : rows;
|
|
88
92
|
if (row?.daemon_token) {
|
|
@@ -131,6 +135,16 @@ async function awaitPairing(code) {
|
|
|
131
135
|
async function runPairing(argv) {
|
|
132
136
|
const dest = (0, pair_destination_1.pairDestination)(argv);
|
|
133
137
|
const hostname = os.hostname();
|
|
138
|
+
// A code handed to us on the command line was minted by a page the user was
|
|
139
|
+
// already looking at, and claimed by them as it was made. There is nothing
|
|
140
|
+
// to ask and nobody to send anywhere: finalize it and get on with running.
|
|
141
|
+
const given = (0, pair_destination_1.pairCodeFromArgv)(argv);
|
|
142
|
+
if (given) {
|
|
143
|
+
process.stdout.write(`\n Connecting ${hostname} to ${dest.product}…\n`);
|
|
144
|
+
const result = await awaitPairing(given);
|
|
145
|
+
process.stdout.write(` ✓ Connected as ${hostname}\n\n`);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
134
148
|
const interactive = (0, pair_destination_1.canOpenBrowser)({
|
|
135
149
|
isTTY: Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY),
|
|
136
150
|
platform: process.platform,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@addai/node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|