adaptiqai 0.1.0 → 0.2.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 +45 -18
- package/lib/index.js +988 -377
- package/package.json +2 -3
package/lib/index.js
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
// src/entry.ts
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
-
import { join as
|
|
5
|
+
import { join as join16 } from "node:path";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
8
|
|
|
9
9
|
// src/apps/claude-code.ts
|
|
10
|
-
import { access, chmod as chmod2, constants as constants2, mkdir as
|
|
11
|
-
import { existsSync as
|
|
12
|
-
import { dirname as dirname2, join as
|
|
10
|
+
import { access, chmod as chmod2, constants as constants2, mkdir as mkdir3, readdir } from "node:fs/promises";
|
|
11
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
12
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
13
13
|
|
|
14
14
|
// src/context.ts
|
|
15
15
|
import { hostname } from "node:os";
|
|
@@ -57,7 +57,7 @@ function checkedAddress(raw, variable) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
// src/files.ts
|
|
60
|
-
import { chmod, lstat, mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
60
|
+
import { chmod, lstat, mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
61
61
|
import { basename, dirname, join as join2 } from "node:path";
|
|
62
62
|
|
|
63
63
|
// src/output.ts
|
|
@@ -99,6 +99,25 @@ var Output = class {
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
// src/files.ts
|
|
102
|
+
var KEY_SHAPE = /ak_(?:live|test)_[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{8,}/u;
|
|
103
|
+
var PIECE = 1 << 20;
|
|
104
|
+
async function fileHoldsKey(path) {
|
|
105
|
+
const handle = await open(path, "r").catch(() => void 0);
|
|
106
|
+
if (handle === void 0) return false;
|
|
107
|
+
try {
|
|
108
|
+
const piece = Buffer.alloc(PIECE);
|
|
109
|
+
let carried = "";
|
|
110
|
+
for (; ; ) {
|
|
111
|
+
const { bytesRead } = await handle.read(piece, 0, PIECE, null);
|
|
112
|
+
if (bytesRead === 0) return false;
|
|
113
|
+
const text = carried + piece.toString("latin1", 0, bytesRead);
|
|
114
|
+
if (KEY_SHAPE.test(text)) return true;
|
|
115
|
+
carried = text.slice(-256);
|
|
116
|
+
}
|
|
117
|
+
} finally {
|
|
118
|
+
await handle.close();
|
|
119
|
+
}
|
|
120
|
+
}
|
|
102
121
|
async function readText(path) {
|
|
103
122
|
try {
|
|
104
123
|
return await readFile(path, "utf8");
|
|
@@ -123,8 +142,8 @@ async function readJson(path) {
|
|
|
123
142
|
}
|
|
124
143
|
var Backups = class {
|
|
125
144
|
/** @param dir - this run's folder, made when the first copy is. */
|
|
126
|
-
constructor(
|
|
127
|
-
this.dir =
|
|
145
|
+
constructor(dir2) {
|
|
146
|
+
this.dir = dir2;
|
|
128
147
|
}
|
|
129
148
|
saved = [];
|
|
130
149
|
seen = /* @__PURE__ */ new Map();
|
|
@@ -219,6 +238,40 @@ async function removeFile(path, backups) {
|
|
|
219
238
|
return true;
|
|
220
239
|
}
|
|
221
240
|
|
|
241
|
+
// src/node.ts
|
|
242
|
+
var ADAPTIQ_NODE_BIN = "$HOME/.adaptiq/node/current/bin";
|
|
243
|
+
var HandedOver = class extends Error {
|
|
244
|
+
constructor(exitCode) {
|
|
245
|
+
super("handed over");
|
|
246
|
+
this.exitCode = exitCode;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function atLeast(version2, minimum) {
|
|
250
|
+
const have = version2.replace(/^v/u, "").split(".").map(Number);
|
|
251
|
+
const need = minimum.split(".").map(Number);
|
|
252
|
+
for (let index = 0; index < 3; index++) {
|
|
253
|
+
const a = have[index] ?? 0;
|
|
254
|
+
const b = need[index] ?? 0;
|
|
255
|
+
if (a !== b) return a > b;
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
async function needNode(context2, minimum, what, args) {
|
|
260
|
+
if (atLeast(context2.node, minimum)) return;
|
|
261
|
+
const said = `${what} needs Node.js ${minimum} or newer, and this runs on ${context2.node}.`;
|
|
262
|
+
const script = `${context2.origin}/v1/install.sh`;
|
|
263
|
+
const command2 = `curl -fsSL '${script}' | ADAPTIQ_NODE_MIN=${minimum} ADAPTIQ_INSTALL_NODE=1 sh -s -- ${args.join(" ")}`;
|
|
264
|
+
const windows2 = context2.platform === "win32";
|
|
265
|
+
if (windows2 || !await context2.confirm(`${said} Install a newer Node.js for AdaptiqAI, in your home folder? It needs no admin password.`)) {
|
|
266
|
+
throw new Problem("node_too_old", `${said} Install it from nodejs.org${windows2 ? "" : `, or have AdaptiqAI install one: ${command2}`}`, {
|
|
267
|
+
node: context2.node,
|
|
268
|
+
needs: minimum,
|
|
269
|
+
...windows2 ? {} : { install: command2 }
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
throw new HandedOver(await context2.handOver(command2));
|
|
273
|
+
}
|
|
274
|
+
|
|
222
275
|
// src/run.ts
|
|
223
276
|
import { spawn } from "node:child_process";
|
|
224
277
|
import { accessSync, constants, statSync } from "node:fs";
|
|
@@ -228,10 +281,10 @@ var FLUSH_MS = 150;
|
|
|
228
281
|
function which(name2, env, platform = process.platform) {
|
|
229
282
|
const windows2 = platform === "win32";
|
|
230
283
|
const extensions = windows2 ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
231
|
-
for (const
|
|
232
|
-
if (
|
|
284
|
+
for (const dir2 of (env.PATH ?? env.Path ?? "").split(windows2 ? ";" : delimiter)) {
|
|
285
|
+
if (dir2 === "") continue;
|
|
233
286
|
for (const extension of ["", ...extensions]) {
|
|
234
|
-
const found = join3(
|
|
287
|
+
const found = join3(dir2, name2 + extension);
|
|
235
288
|
try {
|
|
236
289
|
accessSync(found, windows2 ? constants.F_OK : constants.X_OK);
|
|
237
290
|
if (statSync(found).isFile()) return found;
|
|
@@ -246,12 +299,39 @@ function launch(command2, args, platform) {
|
|
|
246
299
|
const quoted = [command2, ...args].map((word) => `"${word.replace(/"/gu, '""')}"`).join(" ");
|
|
247
300
|
return ["cmd.exe", ["/d", "/s", "/c", `"${quoted}"`], { windowsVerbatimArguments: true }];
|
|
248
301
|
}
|
|
302
|
+
function ownEnv(env, platform) {
|
|
303
|
+
const own = Object.fromEntries(Object.entries(env).filter(([name2]) => !/^npm_/iu.test(name2) && name2 !== "INIT_CWD" && !["ADAPTIQ_CONNECT", "ADAPTIQ_HELLO", "ADAPTIQ_KEY"].includes(name2)));
|
|
304
|
+
const path = Object.keys(own).find((name2) => name2.toUpperCase() === "PATH");
|
|
305
|
+
if (env.npm_lifecycle_event === void 0 || path === void 0) return own;
|
|
306
|
+
const separator = platform === "win32" ? ";" : delimiter;
|
|
307
|
+
const dirs = (own[path] ?? "").split(separator);
|
|
308
|
+
const first = dirs.findIndex((dir2) => !/(?:node_modules[\\/]\.bin|node-gyp-bin)[\\/]?$/u.test(dir2));
|
|
309
|
+
return { ...own, [path]: dirs.slice(first < 0 ? dirs.length : first).join(separator) };
|
|
310
|
+
}
|
|
311
|
+
function handOver(base, platform = process.platform) {
|
|
312
|
+
return async (command2) => {
|
|
313
|
+
if (holdsKey(command2)) throw new Problem("key_on_command_line", "Refused to put a key on a command line.");
|
|
314
|
+
const [file3, args] = platform === "win32" ? ["cmd.exe", ["/d", "/s", "/c", `"${command2}"`]] : ["/bin/sh", ["-c", command2]];
|
|
315
|
+
const keep = () => {
|
|
316
|
+
};
|
|
317
|
+
process.on("SIGINT", keep);
|
|
318
|
+
try {
|
|
319
|
+
return await new Promise((resolve) => {
|
|
320
|
+
const child = spawn(file3, args, { env: ownEnv(base, platform), stdio: "inherit", windowsVerbatimArguments: platform === "win32" });
|
|
321
|
+
child.on("error", () => resolve(1));
|
|
322
|
+
child.on("exit", (code) => resolve(code ?? 1));
|
|
323
|
+
});
|
|
324
|
+
} finally {
|
|
325
|
+
process.off("SIGINT", keep);
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
249
329
|
function runner(base, platform = process.platform) {
|
|
250
330
|
return async (command2, args, options = {}) => {
|
|
251
331
|
if (args.some(holdsKey) || holdsKey(command2)) throw new Problem("key_on_command_line", "Refused to put a key on a command line.");
|
|
252
|
-
const [
|
|
332
|
+
const [file3, words, extra] = launch(command2, args, platform);
|
|
253
333
|
return await new Promise((resolve, reject) => {
|
|
254
|
-
const child = spawn(
|
|
334
|
+
const child = spawn(file3, [...words], {
|
|
255
335
|
env: { ...base, ...options.env },
|
|
256
336
|
stdio: [options.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
|
|
257
337
|
...options.cwd === void 0 ? {} : { cwd: options.cwd },
|
|
@@ -296,6 +376,7 @@ import { mkdtemp, rm as rm2 } from "node:fs/promises";
|
|
|
296
376
|
import { tmpdir } from "node:os";
|
|
297
377
|
import { join as join4 } from "node:path";
|
|
298
378
|
var QUESTION = "AdaptiqAI connection check: reply with the single word connected.";
|
|
379
|
+
var shellWord = (text) => `'${text.replace(/'/gu, "'\\''")}'`;
|
|
299
380
|
var record = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
300
381
|
function macApp(context2, name2) {
|
|
301
382
|
if (context2.platform !== "darwin") return void 0;
|
|
@@ -318,6 +399,14 @@ async function emptyFolder(work) {
|
|
|
318
399
|
await rm2(folder2, { recursive: true, force: true });
|
|
319
400
|
}
|
|
320
401
|
}
|
|
402
|
+
async function forgetGrant(context2, folder2) {
|
|
403
|
+
const grant = join4(folder2, "grant.json");
|
|
404
|
+
try {
|
|
405
|
+
if (JSON.parse(await readText(grant) ?? "{}").apiBase === context2.origin && await removeFile(grant)) return grant;
|
|
406
|
+
} catch {
|
|
407
|
+
}
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
321
410
|
async function running(context2, name2) {
|
|
322
411
|
if (context2.platform === "win32") return false;
|
|
323
412
|
const seen = await context2.run("pgrep", ["-x", name2], { timeoutMs: 5e3 }).catch(() => void 0);
|
|
@@ -325,25 +414,57 @@ async function running(context2, name2) {
|
|
|
325
414
|
}
|
|
326
415
|
async function quitMacApp(context2, bundleId) {
|
|
327
416
|
const script = `application id "${bundleId}" is running`;
|
|
328
|
-
const
|
|
329
|
-
if (!await
|
|
417
|
+
const open3 = async () => (await context2.run("osascript", ["-e", script], { timeoutMs: 5e3 }).catch(() => void 0))?.stdout.trim() === "true";
|
|
418
|
+
if (!await open3()) return true;
|
|
330
419
|
await context2.run("osascript", ["-e", `quit application id "${bundleId}"`], { timeoutMs: 35e3 }).catch(() => void 0);
|
|
331
|
-
for (let tries = 0; tries < 60 && await
|
|
332
|
-
return !await
|
|
420
|
+
for (let tries = 0; tries < 60 && await open3(); tries++) await context2.sleep(500);
|
|
421
|
+
return !await open3();
|
|
333
422
|
}
|
|
334
423
|
var openMacApp = async (context2, path) => {
|
|
335
424
|
await context2.run("open", [path], { timeoutMs: 15e3 }).catch(() => void 0);
|
|
336
425
|
};
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
}
|
|
426
|
+
|
|
427
|
+
// src/apps/mcp.ts
|
|
428
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
429
|
+
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
430
|
+
import { join as join5 } from "node:path";
|
|
431
|
+
var PACKAGE = "@adaptiqai/mcp";
|
|
432
|
+
var mcpDir = (context2) => join5(configDir(context2.env, context2.home), "mcp");
|
|
433
|
+
var mcpServer = (context2) => join5(mcpDir(context2), "node_modules", ".bin", context2.platform === "win32" ? "adaptiqai-mcp.cmd" : "adaptiqai-mcp");
|
|
434
|
+
var mcpPlugin = (context2) => join5(mcpDir(context2), "node_modules", "@adaptiqai", "mcp", "claude-code");
|
|
435
|
+
var mcpHasHook = (context2) => existsSync2(join5(mcpPlugin(context2), "hooks", "hooks.json"));
|
|
436
|
+
var mcpEnv = (context2) => context2.origin === PRODUCTION_API ? void 0 : { ADAPTIQAI_API_BASE: context2.origin, ADAPTIQAI_CONSOLE_URL: context2.console };
|
|
437
|
+
var MCP_UPDATE = `U="$D/mcp/.checked"; if [ -x "$X" ] && command -v npm >/dev/null 2>&1 && [ -z "$(find "$U" -mtime -1 2>/dev/null)" ]; then touch "$U"; N="$D/mcp.next.$$"; O="$D/mcp.old.$$"; rm -rf "$N"; if B=$(node -e 'process.stdout.write(new Date(Date.now()-172800000).toISOString())' 2>/dev/null) && npm install --prefix "$N" --no-audit --no-fund --fetch-retries=0 --fetch-timeout=10000 --before="$B" ${PACKAGE}@latest </dev/null >/dev/null 2>&1 && [ -x "$N/node_modules/.bin/adaptiqai-mcp" ] && node --check "$N/node_modules/@adaptiqai/mcp/lib/index.js" >/dev/null 2>&1; then touch "$N/.checked"; mv "$D/mcp" "$O" && { mv "$N" "$D/mcp" || mv "$O" "$D/mcp"; }; fi; rm -rf "$N" "$O"; fi`;
|
|
438
|
+
async function installMcp(context2, app) {
|
|
439
|
+
const had = existsSync2(mcpServer(context2));
|
|
440
|
+
const npm = command(context2, "npm");
|
|
441
|
+
if (npm === void 0) {
|
|
442
|
+
if (had) return;
|
|
443
|
+
throw new Problem("tools_not_installed", `AdaptiqAI's knowledge tools for ${app} need npm, which comes with Node.js. Install Node.js from nodejs.org, then run this again.`, { app });
|
|
444
|
+
}
|
|
445
|
+
context2.say.note(`Installing AdaptiqAI's knowledge tools for ${app}...`);
|
|
446
|
+
await mkdir2(mcpDir(context2), { recursive: true });
|
|
447
|
+
const done = await context2.run(npm, ["install", "--prefix", mcpDir(context2), "--no-audit", "--no-fund", PACKAGE], { timeoutMs: 3e5 });
|
|
448
|
+
if (done.code === 0 && existsSync2(mcpServer(context2)) || had) return;
|
|
449
|
+
const said = `${done.stderr}
|
|
450
|
+
${done.stdout}`.trim().split("\n").pop() ?? "";
|
|
451
|
+
throw new Problem("tools_not_installed", `AdaptiqAI's knowledge tools for ${app} could not be installed, so ${app} was left as it was. Check the connection, then run this again.${said === "" ? "" : ` npm said: ${said}`}`, { app });
|
|
452
|
+
}
|
|
342
453
|
|
|
343
454
|
// src/apps/claude-code.ts
|
|
344
455
|
var LAUNCHER = "adaptiq-claude";
|
|
345
|
-
var ALIASES = ["
|
|
346
|
-
var MOST_OUTPUT =
|
|
456
|
+
var ALIASES = ["ANTHROPIC_DEFAULT_OPUS_MODEL", "ANTHROPIC_DEFAULT_SONNET_MODEL", "ANTHROPIC_DEFAULT_HAIKU_MODEL", "CLAUDE_CODE_SUBAGENT_MODEL"];
|
|
457
|
+
var MOST_OUTPUT = 16e3;
|
|
458
|
+
var KEEP_JS = [
|
|
459
|
+
'const fs=require("fs"),e=process.env,file=e.AQ_USER,',
|
|
460
|
+
'read=(f)=>{try{return JSON.parse(fs.readFileSync(f,"utf8"))}catch(x){return x.code==="ENOENT"?{}:null}},',
|
|
461
|
+
"ours=((read(e.AQ_MODELS)||{}).options||[]).map((o)=>o.model),s=read(file);",
|
|
462
|
+
'if(!s||typeof s!=="object"||Array.isArray(s))process.exit(0);',
|
|
463
|
+
'if(!ours.includes(s.model)){if(process.argv[2]==="start")fs.writeFileSync(e.AQ_WAS,JSON.stringify({model:s.model??null}));process.exit(0)}',
|
|
464
|
+
"const was=(read(e.AQ_WAS)||{}).model;if(was==null||ours.includes(was))delete s.model;else s.model=was;",
|
|
465
|
+
'const tmp=file+".adaptiq";fs.writeFileSync(tmp,JSON.stringify(s,null,2)+"\\n",{mode:fs.statSync(file).mode&0o777});',
|
|
466
|
+
"fs.renameSync(tmp,file)"
|
|
467
|
+
].join("\n");
|
|
347
468
|
var SETTINGS = [
|
|
348
469
|
// AdaptiqAI keeps the whole conversation in native memory, so Claude Code does not compact it by itself; /compact still does.
|
|
349
470
|
["DISABLE_AUTO_COMPACT", "1"],
|
|
@@ -353,20 +474,24 @@ var SETTINGS = [
|
|
|
353
474
|
["CLAUDE_CODE_SUBAGENT_MODEL_FORCE", "1"],
|
|
354
475
|
["CLAUDE_CODE_AUTO_MODE_SERVER", "0"],
|
|
355
476
|
// Each request says whether it is the chat's, an agent's or a side request, so the gateway keeps one session per chat.
|
|
356
|
-
["CLAUDE_CODE_GATEWAY_HINT_HEADERS", "1"]
|
|
477
|
+
["CLAUDE_CODE_GATEWAY_HINT_HEADERS", "1"],
|
|
478
|
+
// Behind a gateway Claude Code hides MCP tools behind its tool search, so the model would see AdaptiqAI's knowledge tools by name only.
|
|
479
|
+
["ENABLE_TOOL_SEARCH", "false"]
|
|
357
480
|
];
|
|
481
|
+
var ALLOW = ["mcp__adaptiqai__adaptiq_knowledge", "mcp__adaptiqai__adaptiq_memory"];
|
|
358
482
|
var folder = (context2) => configDir(context2.env, context2.home);
|
|
359
|
-
var keyFile = (context2) =>
|
|
360
|
-
var bin = (context2) =>
|
|
483
|
+
var keyFile = (context2) => join6(folder(context2), "claude-code.key");
|
|
484
|
+
var bin = (context2) => join6(context2.home, ".local", "bin");
|
|
361
485
|
var name = (context2) => context2.platform === "win32" ? `${LAUNCHER}.cmd` : LAUNCHER;
|
|
362
486
|
async function launcherDir(context2) {
|
|
363
487
|
const claude = which("claude", context2.env, context2.platform);
|
|
364
488
|
if (claude === void 0) return bin(context2);
|
|
365
489
|
return await access(dirname2(claude), constants2.W_OK).then(() => dirname2(claude), () => bin(context2));
|
|
366
490
|
}
|
|
367
|
-
var installed = (context2) => which(LAUNCHER, context2.env, context2.platform) ?? [
|
|
491
|
+
var installed = (context2) => which(LAUNCHER, context2.env, context2.platform) ?? [join6(bin(context2), name(context2))].find(existsSync3);
|
|
368
492
|
function posix(context2, model2) {
|
|
369
493
|
const output = Math.min(model2.output, MOST_OUTPUT);
|
|
494
|
+
const env = mcpEnv(context2);
|
|
370
495
|
return [
|
|
371
496
|
"#!/bin/sh",
|
|
372
497
|
"# Claude Code on AdaptiqAI, from `npx -y adaptiqai connect claude-code`. Running it again writes this again.",
|
|
@@ -376,27 +501,61 @@ function posix(context2, model2) {
|
|
|
376
501
|
`M=$(printf %s "\${ADAPTIQ_MODEL:-${model2.id}}" | tr -cd 'A-Za-z0-9._:/-')`,
|
|
377
502
|
`S='{"env":{"ANTHROPIC_BASE_URL":"${context2.origin}"'`,
|
|
378
503
|
`for A in ${ALIASES.join(" ")}; do S="$S,\\"$A\\":\\"$M\\""; done`,
|
|
504
|
+
// The window and the answer length are the launcher's model's: another one in ADAPTIQ_MODEL has its own.
|
|
505
|
+
`[ "$M" = '${model2.id}' ] && S="$S"',"CLAUDE_CODE_MAX_CONTEXT_TOKENS":"${CONVERSATION_TOKENS}"'`,
|
|
379
506
|
...output > 0 ? [`[ "$M" = '${model2.id}' ] && S="$S"',"CLAUDE_CODE_MAX_OUTPUT_TOKENS":"${output}"'`] : [],
|
|
380
|
-
|
|
507
|
+
// The plugin's hook reads Claude Code's own environment for an AdaptiqAI other than production.
|
|
508
|
+
`S="$S"'${[...SETTINGS, ...Object.entries(env ?? {})].map(([key, value]) => `,"${key}":"${value}"`).join("")}'`,
|
|
509
|
+
// A Node.js AdaptiqAI installed comes last, for the MCP server, the guard below and a Claude Code npm put there.
|
|
510
|
+
`PATH="$PATH:${ADAPTIQ_NODE_BIN}"`,
|
|
511
|
+
// Once a day /model takes the models the workspace has now, in place of Anthropic's lineup, so a model added later
|
|
512
|
+
// shows up by itself. The first start waits up to 5 seconds. The key goes to curl on its input, never in its
|
|
513
|
+
// arguments. Only one line of ASCII shaped as the list is kept: a failure, or any other answer, keeps the list there was.
|
|
514
|
+
'L="$D/claude-code-models.json"; V="$L.checked"',
|
|
515
|
+
`aq_list() { case "$1" in '{"options":[{"model":'*'}],"replaceBuiltInOptions":true}') ;; *) return 1 ;; esac; ! printf %s "$1" | LC_ALL=C grep -q '[^ -~]'; }`,
|
|
516
|
+
`aq_models() { printf 'Authorization: Bearer %s\\n' "$K" | curl -fsS --max-time "$1" -H @- '${context2.origin}/v1/cli-auth/claude-code/models' -o "$L.new" 2>/dev/null && aq_list "$(cat "$L.new")" && mv -f "$L.new" "$L"; rm -f "$L.new"; }`,
|
|
517
|
+
'if command -v curl >/dev/null 2>&1 && [ -z "$(find "$V" -mtime -1 2>/dev/null)" ]; then touch "$V"; if [ -s "$L" ]; then ( aq_models 20 ) </dev/null >/dev/null 2>&1 & else aq_models 5; fi; fi',
|
|
518
|
+
'Q=$(cat "$L" 2>/dev/null); aq_list "$Q" || Q=',
|
|
519
|
+
// A pick in /model is saved as the person's own default, where plain claude would then ask Anthropic for an
|
|
520
|
+
// AdaptiqAI model. So the list goes in only with Node.js to undo that, as the session ends and at the next start.
|
|
521
|
+
"command -v node >/dev/null 2>&1 || Q=",
|
|
522
|
+
'export AQ_KEEP="$D/claude-code-keep.js" AQ_MODELS="$L" AQ_WAS="$D/claude-code-own-model.json" AQ_USER="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json"',
|
|
523
|
+
`[ -z "$Q" ] || { cat >"$AQ_KEEP" <<'KEEP'`,
|
|
524
|
+
KEEP_JS,
|
|
525
|
+
"KEEP",
|
|
526
|
+
'node "$AQ_KEEP" start; }',
|
|
527
|
+
`[ -n "$Q" ] && Q=",\\"modelPicker\\":$Q"',"hooks":{"SessionEnd":[{"hooks":[{"type":"command","command":"node \\"$AQ_KEEP\\" end"}]}]}'`,
|
|
381
528
|
`F="$D/claude-code-$(printf %s "$M" | tr '/:' '__').json"`,
|
|
382
|
-
`( umask 077; printf '%s,"ANTHROPIC_AUTH_TOKEN":"%s","ANTHROPIC_API_KEY":""}}' "$S" "$K" >"$F" && chmod 600 "$F" ) || exit 1`,
|
|
529
|
+
`( umask 077; printf '%s,"ANTHROPIC_AUTH_TOKEN":"%s","ANTHROPIC_API_KEY":""},"permissions":{"allow":${JSON.stringify(ALLOW)}}%s}' "$S" "$K" "$Q" >"$F" && chmod 600 "$F" ) || exit 1`,
|
|
383
530
|
// Claude Code's own local install is often only an alias, which a script does not see.
|
|
384
531
|
'command -v claude >/dev/null 2>&1 || PATH="$HOME/.claude/local:$HOME/.local/bin:$PATH"',
|
|
385
532
|
"unset ANTHROPIC_API_KEY",
|
|
386
533
|
'export ANTHROPIC_AUTH_TOKEN="$K"',
|
|
534
|
+
// The server takes its key from Claude Code's environment. Its config comes before --settings: --mcp-config takes every word after it up to the next option.
|
|
535
|
+
'X="$D/mcp/node_modules/.bin/adaptiqai-mcp"; C="$D/claude-code-mcp.json"',
|
|
536
|
+
// Beside Claude Code, never before it: a slow npm delays no start, and the update takes effect at the next.
|
|
537
|
+
`( ${MCP_UPDATE} ) </dev/null >/dev/null 2>&1 &`,
|
|
538
|
+
// The server's Claude Code plugin, where the server installed carries one; --plugin-dir must name one.
|
|
539
|
+
'P="$D/mcp/node_modules/@adaptiqai/mcp/claude-code"',
|
|
540
|
+
`if [ -x "$X" ] && printf '{"mcpServers":{"adaptiqai":{"command":"%s"${env === void 0 ? "" : `,"env":${JSON.stringify(env)}`}}}}\\n' "$X" >"$C"; then`,
|
|
541
|
+
' [ -f "$P/.claude-plugin/plugin.json" ] && exec claude --model "$M" --plugin-dir "$P" --mcp-config "$C" --settings "$F" "$@"',
|
|
542
|
+
' exec claude --model "$M" --mcp-config "$C" --settings "$F" "$@"',
|
|
543
|
+
"fi",
|
|
387
544
|
'exec claude --model "$M" --settings "$F" "$@"',
|
|
388
545
|
""
|
|
389
546
|
].join("\n");
|
|
390
547
|
}
|
|
548
|
+
var mcpConfig = (context2) => join6(folder(context2), "claude-code-mcp.json");
|
|
391
549
|
function windows(context2, model2) {
|
|
392
|
-
const
|
|
550
|
+
const file3 = join6(folder(context2), "claude-code.json");
|
|
551
|
+
const tools2 = existsSync3(mcpServer(context2)) ? ` --mcp-config "${mcpConfig(context2)}"` : "";
|
|
393
552
|
return [
|
|
394
553
|
"@echo off",
|
|
395
554
|
"rem Claude Code on AdaptiqAI, from npx -y adaptiqai connect claude-code. Running it again writes this again.",
|
|
396
555
|
"setlocal",
|
|
397
556
|
'set "ANTHROPIC_API_KEY="',
|
|
398
557
|
`set /p ANTHROPIC_AUTH_TOKEN=<"${keyFile(context2)}"`,
|
|
399
|
-
`claude --model "${model2.id}" --settings "${
|
|
558
|
+
`claude --model "${model2.id}"${tools2} --settings "${file3}" %*`,
|
|
400
559
|
""
|
|
401
560
|
].join("\r\n");
|
|
402
561
|
}
|
|
@@ -411,7 +570,8 @@ function settingsJson(context2, model2, key) {
|
|
|
411
570
|
...Object.fromEntries(SETTINGS),
|
|
412
571
|
ANTHROPIC_AUTH_TOKEN: key,
|
|
413
572
|
ANTHROPIC_API_KEY: ""
|
|
414
|
-
}
|
|
573
|
+
},
|
|
574
|
+
permissions: { allow: ALLOW }
|
|
415
575
|
}, null, 2)}
|
|
416
576
|
`;
|
|
417
577
|
}
|
|
@@ -420,10 +580,13 @@ var claudeCode = {
|
|
|
420
580
|
aliases: ["claude"],
|
|
421
581
|
name: "Claude Code",
|
|
422
582
|
install: "npm install -g @anthropic-ai/claude-code",
|
|
423
|
-
does: "Added `adaptiq-claude`, which starts Claude Code on AdaptiqAI. Your own Claude Code stays as it is.",
|
|
583
|
+
does: "Added `adaptiq-claude`, which starts Claude Code on AdaptiqAI with your memory and knowledge. Your own Claude Code stays as it is.",
|
|
424
584
|
// The console knows a setup that lasts by this name.
|
|
425
585
|
signsInAs: async () => LAUNCHER,
|
|
426
|
-
found: async (context2) => command(context2, "claude", [
|
|
586
|
+
found: async (context2) => command(context2, "claude", [join6(context2.home, ".claude", "local", "claude")]) !== void 0,
|
|
587
|
+
async prepare(context2) {
|
|
588
|
+
await installMcp(context2, "Claude Code");
|
|
589
|
+
},
|
|
427
590
|
async current(context2) {
|
|
428
591
|
const path = installed(context2);
|
|
429
592
|
const text = path === void 0 ? void 0 : await readText(path);
|
|
@@ -439,16 +602,19 @@ var claudeCode = {
|
|
|
439
602
|
},
|
|
440
603
|
async apply(context2, facts) {
|
|
441
604
|
const [model2] = facts.models;
|
|
442
|
-
await
|
|
605
|
+
await mkdir3(folder(context2), { recursive: true, mode: 448 });
|
|
443
606
|
await chmod2(folder(context2), 448).catch(() => {
|
|
444
607
|
});
|
|
445
608
|
await writeWhole(keyFile(context2), `${facts.key}
|
|
446
609
|
`, null, { secret: true });
|
|
447
|
-
const
|
|
448
|
-
await
|
|
449
|
-
const launcher =
|
|
610
|
+
const dir2 = await launcherDir(context2);
|
|
611
|
+
await mkdir3(dir2, { recursive: true });
|
|
612
|
+
const launcher = join6(dir2, name(context2));
|
|
450
613
|
if (context2.platform === "win32") {
|
|
451
|
-
await writeWhole(
|
|
614
|
+
await writeWhole(join6(folder(context2), "claude-code.json"), settingsJson(context2, model2, facts.key), null, { secret: true });
|
|
615
|
+
const env = mcpEnv(context2);
|
|
616
|
+
if (existsSync3(mcpServer(context2))) await writeWhole(mcpConfig(context2), `${JSON.stringify({ mcpServers: { adaptiqai: { command: mcpServer(context2), ...env === void 0 ? {} : { env } } } })}
|
|
617
|
+
`, null);
|
|
452
618
|
await writeWhole(launcher, windows(context2, model2), context2.backups);
|
|
453
619
|
} else {
|
|
454
620
|
await writeWhole(launcher, posix(context2, model2), context2.backups, { mode: 493 });
|
|
@@ -460,217 +626,95 @@ var claudeCode = {
|
|
|
460
626
|
if (path !== void 0) await removeFile(path);
|
|
461
627
|
await removeFile(keyFile(context2));
|
|
462
628
|
const left = await readdir(folder(context2)).catch(() => []);
|
|
463
|
-
for (const
|
|
629
|
+
for (const file3 of left.filter((entry2) => /^claude-code.*\.json$/u.test(entry2))) await removeFile(join6(folder(context2), file3));
|
|
630
|
+
},
|
|
631
|
+
async clean(context2) {
|
|
632
|
+
const settings = (await readdir(folder(context2)).catch(() => [])).filter((entry2) => /^claude-code.*\.json$/u.test(entry2)).map((entry2) => join6(folder(context2), entry2));
|
|
633
|
+
const removed = [];
|
|
634
|
+
if (await removeFile(keyFile(context2))) removed.push(keyFile(context2));
|
|
635
|
+
for (const file3 of settings) if (await fileHoldsKey(file3) && await removeFile(file3)) removed.push(file3);
|
|
636
|
+
return { removed, left: [] };
|
|
464
637
|
},
|
|
465
638
|
async start(context2) {
|
|
466
639
|
if (which(LAUNCHER, context2.env, context2.platform) !== void 0) return LAUNCHER;
|
|
467
|
-
return installed(context2) ??
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
|
|
471
|
-
// src/apps/cline.ts
|
|
472
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
473
|
-
import { existsSync as existsSync3 } from "node:fs";
|
|
474
|
-
import { mkdir as mkdir4, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
475
|
-
import { join as join7 } from "node:path";
|
|
476
|
-
|
|
477
|
-
// src/record.ts
|
|
478
|
-
import { join as join6 } from "node:path";
|
|
479
|
-
import { mkdir as mkdir3 } from "node:fs/promises";
|
|
480
|
-
var file = (context2, app) => join6(cliDir(context2), `${app.id}.json`);
|
|
481
|
-
async function remember(context2, app, values) {
|
|
482
|
-
if (Object.keys(await readJson(file(context2, app))).length > 0) return;
|
|
483
|
-
await mkdir3(cliDir(context2), { recursive: true, mode: 448 });
|
|
484
|
-
await writeWhole(file(context2, app), `${JSON.stringify({ had: true, ...values }, null, 2)}
|
|
485
|
-
`, null, { secret: true });
|
|
486
|
-
}
|
|
487
|
-
async function recall(context2, app) {
|
|
488
|
-
return await readJson(file(context2, app));
|
|
489
|
-
}
|
|
490
|
-
var forget = async (context2, app) => {
|
|
491
|
-
await removeFile(file(context2, app));
|
|
492
|
-
};
|
|
493
|
-
|
|
494
|
-
// src/apps/cline.ts
|
|
495
|
-
var ID = "adaptiq";
|
|
496
|
-
var dir = (context2) => join7(context2.env.CLINE_DATA_DIR ?? join7(context2.env.CLINE_DIR ?? join7(context2.home, ".cline"), "data"), "settings");
|
|
497
|
-
var file2 = (context2, name2) => join7(dir(context2), name2);
|
|
498
|
-
var HUB = "^[^ ]+ --cline-hub-daemon( |$)";
|
|
499
|
-
function clineModel(model2) {
|
|
500
|
-
const { input, ...window } = modelWindow(model2);
|
|
501
|
-
return {
|
|
502
|
-
id: model2.id,
|
|
503
|
-
capabilities: ["streaming", "tools", ...model2.reasoning === null ? [] : ["reasoning"], ...input === void 0 ? [] : ["images"]],
|
|
504
|
-
...model2.reasoning === null ? {} : { supportsReasoning: true },
|
|
505
|
-
...window
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
async function apply(context2, facts) {
|
|
509
|
-
const base = apiBase(context2);
|
|
510
|
-
const entries = facts.models.map(clineModel);
|
|
511
|
-
const chosen = facts.models[0].id;
|
|
512
|
-
const { key } = facts;
|
|
513
|
-
const providers = record(await readJson(file2(context2, "providers.json")));
|
|
514
|
-
const settings = await readJson(file2(context2, "global-settings.json"));
|
|
515
|
-
await remember(context2, cline, { lastUsedProvider: providers.lastUsedProvider ?? null, compactionEnabled: settings.compactionEnabled ?? null });
|
|
516
|
-
await updateJson(file2(context2, "models.json"), (value) => {
|
|
517
|
-
const all = record(value.providers);
|
|
518
|
-
const had = record(record(all[ID]).models);
|
|
519
|
-
value.version = 1;
|
|
520
|
-
value.providers = {
|
|
521
|
-
...all,
|
|
522
|
-
[ID]: {
|
|
523
|
-
provider: {
|
|
524
|
-
name: "AdaptiqAI",
|
|
525
|
-
baseUrl: base,
|
|
526
|
-
defaultModelId: chosen,
|
|
527
|
-
capabilities: ["streaming", "tools", ...facts.models.every((model2) => model2.reasoning !== null) ? ["reasoning"] : []],
|
|
528
|
-
modelsSourceUrl: `${base}/models`
|
|
529
|
-
},
|
|
530
|
-
models: Object.fromEntries(entries.map((entry2) => {
|
|
531
|
-
const id = String(entry2.id);
|
|
532
|
-
const own = record(had[id]);
|
|
533
|
-
return [id, { ...own, ...entry2, name: own.name ?? id }];
|
|
534
|
-
})),
|
|
535
|
-
discoveredModelIds: entries.map((entry2) => entry2.id)
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
}, context2.backups, { secret: true });
|
|
539
|
-
await updateJson(file2(context2, "providers.json"), (value) => {
|
|
540
|
-
const all = record(value.providers);
|
|
541
|
-
const was = record(all[ID]);
|
|
542
|
-
const wanted = { provider: ID, apiKey: key, baseUrl: base, model: chosen };
|
|
543
|
-
const same = sameJson(was.settings) === sameJson(wanted);
|
|
544
|
-
value.version = 1;
|
|
545
|
-
value.modes = value.modes ?? {};
|
|
546
|
-
value.providers = { ...all, [ID]: { settings: wanted, updatedAt: same ? was.updatedAt : new Date(context2.now()).toISOString(), tokenSource: "manual" } };
|
|
547
|
-
value.lastUsedProvider = ID;
|
|
548
|
-
}, context2.backups, { secret: true });
|
|
549
|
-
await updateJson(file2(context2, "global-settings.json"), (value) => {
|
|
550
|
-
value.compactionEnabled = false;
|
|
551
|
-
}, context2.backups, { secret: true });
|
|
552
|
-
}
|
|
553
|
-
var HEX = (text) => Buffer.from(text, "utf16le").toString("hex");
|
|
554
|
-
async function appState(context2, facts) {
|
|
555
|
-
const root = join7(context2.home, "Library", "WebKit", "bot.cline.app", "WebsiteData", "Default");
|
|
556
|
-
const saltFile = join7(root, "salt");
|
|
557
|
-
await mkdir4(root, { recursive: true });
|
|
558
|
-
if (!existsSync3(saltFile)) await writeFile2(saltFile, randomBytes(8));
|
|
559
|
-
const folder2 = createHash("sha256").update(Buffer.concat([Buffer.from("tauri://localhost"), await readFile2(saltFile)])).digest("base64url");
|
|
560
|
-
const home3 = join7(root, folder2, folder2);
|
|
561
|
-
await mkdir4(join7(home3, "LocalStorage"), { recursive: true });
|
|
562
|
-
const part = (text) => {
|
|
563
|
-
const bytes = Buffer.from(text);
|
|
564
|
-
const size = Buffer.alloc(4);
|
|
565
|
-
size.writeUInt32LE(bytes.length);
|
|
566
|
-
return Buffer.concat([size, Buffer.from([1]), bytes]);
|
|
567
|
-
};
|
|
568
|
-
const one = Buffer.concat([part("tauri"), part("localhost"), Buffer.from([0])]);
|
|
569
|
-
const origin = join7(home3, "origin");
|
|
570
|
-
if (!existsSync3(origin)) await writeFile2(origin, Buffer.concat([one, one]));
|
|
571
|
-
const store = join7(home3, "LocalStorage", "localstorage.sqlite3");
|
|
572
|
-
const sql = async (text) => {
|
|
573
|
-
const done = await context2.run("sqlite3", [store, text], { timeoutMs: 1e4 });
|
|
574
|
-
if (done.code !== 0) throw new Error("sqlite3 failed");
|
|
575
|
-
return done.stdout.trim();
|
|
576
|
-
};
|
|
577
|
-
const blob = (value) => `X'${HEX(JSON.stringify(value))}'`;
|
|
578
|
-
const read = async (key) => {
|
|
579
|
-
try {
|
|
580
|
-
return record(JSON.parse(Buffer.from(await sql(`SELECT hex(value) FROM ItemTable WHERE key='${key}'`), "hex").toString("utf16le")));
|
|
581
|
-
} catch {
|
|
582
|
-
return {};
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
|
-
const SELECTION = "cline.code.model-selection.v1";
|
|
586
|
-
const EFFORT = "cline.code.reasoning-selection.v1";
|
|
587
|
-
await sql("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)");
|
|
588
|
-
const selection = await read(SELECTION);
|
|
589
|
-
const chosen = { lastProvider: ID, lastModelByProvider: { ...record(selection.lastModelByProvider), [ID]: facts.models[0].id } };
|
|
590
|
-
if (sameJson(selection) !== sameJson(chosen)) {
|
|
591
|
-
await sql(`INSERT INTO ItemTable VALUES('${SELECTION}',${blob(chosen)});INSERT OR IGNORE INTO ItemTable VALUES('cline.code.onboarding.v1',${blob({ completedAt: new Date(context2.now()).toISOString() })})`);
|
|
592
|
-
}
|
|
593
|
-
const level = facts.models[0].reasoning?.defaultLevel ?? "";
|
|
594
|
-
const picked = await read(EFFORT);
|
|
595
|
-
if (["low", "medium", "high", "xhigh"].includes(level) && picked[ID] === void 0) {
|
|
596
|
-
await sql(`INSERT INTO ItemTable VALUES('${EFFORT}',${blob({ ...picked, [ID]: level })})`);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
async function restart(context2) {
|
|
600
|
-
if (context2.platform === "darwin") await context2.run("osascript", ["-e", 'quit app "Cline"'], { timeoutMs: 35e3 }).catch(() => void 0);
|
|
601
|
-
else await context2.run("pkill", ["-x", "cline-app"], { timeoutMs: 5e3 }).catch(() => void 0);
|
|
602
|
-
await context2.run("pkill", ["-f", "--", HUB], { timeoutMs: 5e3 }).catch(() => void 0);
|
|
603
|
-
for (let tries = 0; tries < 10 && await busy(context2); tries++) await context2.sleep(500);
|
|
604
|
-
await context2.run("pkill", ["-x", "cline-app"], { timeoutMs: 5e3 }).catch(() => void 0);
|
|
605
|
-
return !await busy(context2);
|
|
606
|
-
}
|
|
607
|
-
async function busy(context2) {
|
|
608
|
-
if (context2.platform === "win32") return false;
|
|
609
|
-
if (await running(context2, "cline-app")) return true;
|
|
610
|
-
return (await context2.run("pgrep", ["-f", "--", HUB], { timeoutMs: 5e3 }).catch(() => void 0))?.code === 0;
|
|
611
|
-
}
|
|
612
|
-
var cline = {
|
|
613
|
-
id: "cline",
|
|
614
|
-
name: "Cline",
|
|
615
|
-
install: "npm install -g cline",
|
|
616
|
-
does: "Added AdaptiqAI to Cline and turned off its automatic compaction, for every model. Your other providers stay.",
|
|
617
|
-
signsInAs: async () => "Cline",
|
|
618
|
-
found: async (context2) => command(context2, "cline") !== void 0 || command(context2, "cline-app") !== void 0 || macApp(context2, "Cline") !== void 0 || existsSync3(dir(context2)),
|
|
619
|
-
async prepare(context2) {
|
|
620
|
-
await Promise.all(["providers.json", "models.json", "global-settings.json"].map((name2) => readJson(file2(context2, name2))));
|
|
621
|
-
},
|
|
622
|
-
async current(context2) {
|
|
623
|
-
const settings = record(record(record(await readJson(file2(context2, "providers.json"))).providers)[ID]).settings;
|
|
624
|
-
const { baseUrl, model: model2, apiKey } = record(settings);
|
|
625
|
-
return typeof baseUrl === "string" ? { base: baseUrl, ...typeof model2 === "string" ? { model: model2 } : {}, ...typeof apiKey === "string" ? { key: apiKey } : {} } : {};
|
|
640
|
+
return installed(context2) ?? join6(await launcherDir(context2), name(context2));
|
|
626
641
|
},
|
|
627
|
-
async
|
|
628
|
-
await apply(context2, facts);
|
|
629
|
-
const notes = [];
|
|
630
|
-
if (await busy(context2) && !(await context2.confirm("Cline is running, and reads its settings when it starts. Quit it now?") && await restart(context2))) {
|
|
631
|
-
notes.push("Cline is running. Quit it and open it again to see AdaptiqAI.");
|
|
632
|
-
}
|
|
633
|
-
if (macApp(context2, "Cline") !== void 0 && !await busy(context2)) await appState(context2, facts).catch(() => {
|
|
634
|
-
});
|
|
635
|
-
return notes;
|
|
636
|
-
},
|
|
637
|
-
async remove(context2) {
|
|
638
|
-
const had = await recall(context2, cline);
|
|
639
|
-
await updateJson(file2(context2, "providers.json"), (value) => {
|
|
640
|
-
const all = record(value.providers);
|
|
641
|
-
delete all[ID];
|
|
642
|
-
value.providers = all;
|
|
643
|
-
if (value.lastUsedProvider === ID) {
|
|
644
|
-
if (typeof had.lastUsedProvider === "string") value.lastUsedProvider = had.lastUsedProvider;
|
|
645
|
-
else delete value.lastUsedProvider;
|
|
646
|
-
}
|
|
647
|
-
}, context2.backups, { secret: true });
|
|
648
|
-
await updateJson(file2(context2, "models.json"), (value) => {
|
|
649
|
-
const all = record(value.providers);
|
|
650
|
-
delete all[ID];
|
|
651
|
-
value.providers = all;
|
|
652
|
-
}, context2.backups, { secret: true });
|
|
653
|
-
if ("compactionEnabled" in had) {
|
|
654
|
-
await updateJson(file2(context2, "global-settings.json"), (value) => {
|
|
655
|
-
if (had.compactionEnabled === null) delete value.compactionEnabled;
|
|
656
|
-
else value.compactionEnabled = had.compactionEnabled;
|
|
657
|
-
}, context2.backups, { secret: true });
|
|
658
|
-
}
|
|
659
|
-
await forget(context2, cline);
|
|
660
|
-
},
|
|
661
|
-
start: async (context2) => macApp(context2, "Cline") === void 0 ? "cline" : "open -a Cline"
|
|
642
|
+
greet: async (context2, model2, hello2) => context2.platform === "win32" ? void 0 : `${shellWord(await claudeCode.start(context2, model2))} ${shellWord(hello2)}`
|
|
662
643
|
};
|
|
663
644
|
|
|
664
645
|
// src/apps/codex.ts
|
|
665
|
-
import {
|
|
666
|
-
import {
|
|
646
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
647
|
+
import { chmod as chmod3, mkdir as mkdir4 } from "node:fs/promises";
|
|
648
|
+
import { join as join7 } from "node:path";
|
|
667
649
|
var PROVIDER = "adaptiqai-connect";
|
|
668
|
-
var home = (context2) => context2.env.CODEX_HOME ??
|
|
669
|
-
var profile = (context2) =>
|
|
670
|
-
var catalog = (context2) =>
|
|
671
|
-
var keyFile2 = (context2) =>
|
|
650
|
+
var home = (context2) => context2.env.CODEX_HOME ?? join7(context2.home, ".codex");
|
|
651
|
+
var profile = (context2) => join7(home(context2), "adaptiq.config.toml");
|
|
652
|
+
var catalog = (context2) => join7(home(context2), "adaptiq.models.json");
|
|
653
|
+
var keyFile2 = (context2) => join7(configDir(context2.env, context2.home), "codex.key");
|
|
654
|
+
var wrapper = (context2) => join7(configDir(context2.env, context2.home), context2.platform === "win32" ? "codex-mcp.cmd" : "codex-mcp");
|
|
672
655
|
var codexCommand = (context2) => command(context2, "codex") ?? "codex";
|
|
673
656
|
var toml = (text) => JSON.stringify(text);
|
|
657
|
+
var REFRESH_JS = [
|
|
658
|
+
'const fs=require("fs"),[file,body]=process.argv.slice(1),',
|
|
659
|
+
'now=JSON.parse(fs.readFileSync(file,"utf8")).models,got=JSON.parse(body).models;',
|
|
660
|
+
'if(Array.isArray(now)&&now.length&&Array.isArray(got)&&got.length&&got.every((m)=>m&&typeof m.slug=="string"&&m.slug&&typeof m.display_name=="string"',
|
|
661
|
+
"&&Number.isInteger(m.priority)&&Array.isArray(m.supported_reasoning_levels)&&Array.isArray(m.input_modalities))){",
|
|
662
|
+
"const{slug,display_name,priority,supported_reasoning_levels,default_reasoning_level,context_window,",
|
|
663
|
+
'max_context_window,effective_context_window_percent,input_modalities,...rest}=now[0],tmp=file+".adaptiq";',
|
|
664
|
+
"fs.writeFileSync(tmp,JSON.stringify({models:got.map((m)=>({...rest,...m}))}));fs.renameSync(tmp,file)}"
|
|
665
|
+
].join("\n");
|
|
666
|
+
var catalogUpdate = (context2) => [
|
|
667
|
+
'V="$D/codex-models.checked"; C="${CODEX_HOME:-$HOME/.codex}/adaptiq.models.json";',
|
|
668
|
+
' if [ -f "$C" ] && command -v curl >/dev/null 2>&1 && command -v node >/dev/null 2>&1 && [ -z "$(find "$V" -mtime -1 2>/dev/null)" ]; then touch "$V";\n',
|
|
669
|
+
`T=$(printf 'Authorization: Bearer %s\\n' "$(tr -cd 'A-Za-z0-9._-' <"$D/codex.key")" | curl -fsS --max-time 20 -H @- '${context2.origin}/v1/cli-auth/codex/models') && node -e '
|
|
670
|
+
`,
|
|
671
|
+
REFRESH_JS,
|
|
672
|
+
`' "$C" "$T"; fi`
|
|
673
|
+
].join("");
|
|
674
|
+
function wrapperText(context2) {
|
|
675
|
+
if (context2.platform === "win32") {
|
|
676
|
+
return [
|
|
677
|
+
"@echo off",
|
|
678
|
+
"rem AdaptiqAI's MCP server for Codex, from npx -y adaptiqai connect codex. Running it again writes this again.",
|
|
679
|
+
'set /p ADAPTIQAI_API_KEY=<"%~dp0codex.key"',
|
|
680
|
+
'"%~dp0mcp\\node_modules\\.bin\\adaptiqai-mcp.cmd" %*',
|
|
681
|
+
""
|
|
682
|
+
].join("\r\n");
|
|
683
|
+
}
|
|
684
|
+
return [
|
|
685
|
+
"#!/bin/sh",
|
|
686
|
+
"# AdaptiqAI's MCP server for Codex, from `npx -y adaptiqai connect codex`. Running it again writes this again.",
|
|
687
|
+
'D=$(dirname "$0"); X="$D/mcp/node_modules/.bin/adaptiqai-mcp"',
|
|
688
|
+
// A Node.js AdaptiqAI installed comes last: the server and npm run on it where there is no other.
|
|
689
|
+
`PATH="$PATH:${ADAPTIQ_NODE_BIN}"`,
|
|
690
|
+
`( ${MCP_UPDATE} ) </dev/null >/dev/null 2>&1 &`,
|
|
691
|
+
`( ${catalogUpdate(context2)} ) </dev/null >/dev/null 2>&1 &`,
|
|
692
|
+
'ADAPTIQAI_API_KEY=$(cat "$D/codex.key") exec "$X" "$@"',
|
|
693
|
+
""
|
|
694
|
+
].join("\n");
|
|
695
|
+
}
|
|
696
|
+
function tools(context2) {
|
|
697
|
+
if (!existsSync4(mcpServer(context2))) return [];
|
|
698
|
+
const env = mcpEnv(context2);
|
|
699
|
+
const windows2 = context2.platform === "win32";
|
|
700
|
+
const prefix = env === void 0 ? "" : Object.entries(env).map(([name2, value]) => `${name2}='${value}' `).join("");
|
|
701
|
+
return [
|
|
702
|
+
"[mcp_servers.adaptiqai]",
|
|
703
|
+
...windows2 ? ['command = "cmd"', `args = ["/d", "/c", ${toml(wrapper(context2))}]`] : [`command = ${toml(wrapper(context2))}`],
|
|
704
|
+
'env_vars = ["CODEX_HOME"]',
|
|
705
|
+
...env === void 0 ? [] : [`env = { ${Object.entries(env).map(([name2, value]) => `${name2} = ${toml(value)}`).join(", ")} }`],
|
|
706
|
+
"",
|
|
707
|
+
...["adaptiq_knowledge", "adaptiq_memory"].flatMap((tool) => [`[mcp_servers.adaptiqai.tools.${tool}]`, 'approval_mode = "approve"', ""]),
|
|
708
|
+
...windows2 || !mcpHasHook(context2) ? [] : [
|
|
709
|
+
"[[hooks.UserPromptSubmit]]",
|
|
710
|
+
"[[hooks.UserPromptSubmit.hooks]]",
|
|
711
|
+
'type = "command"',
|
|
712
|
+
`command = ${toml(`${prefix}ADAPTIQAI_API_KEY="$(cat '${keyFile2(context2)}')" exec '${mcpServer(context2)}' hook codex`)}`,
|
|
713
|
+
"timeout = 30",
|
|
714
|
+
""
|
|
715
|
+
]
|
|
716
|
+
];
|
|
717
|
+
}
|
|
674
718
|
function config(context2, facts) {
|
|
675
719
|
const [model2] = facts.models;
|
|
676
720
|
const path = keyFile2(context2);
|
|
@@ -694,7 +738,8 @@ function config(context2, facts) {
|
|
|
694
738
|
`[model_providers.${PROVIDER}.auth]`,
|
|
695
739
|
`command = ${toml(read[0])}`,
|
|
696
740
|
`args = [${read[1].map(toml).join(", ")}]`,
|
|
697
|
-
""
|
|
741
|
+
"",
|
|
742
|
+
...tools(context2)
|
|
698
743
|
].join("\n");
|
|
699
744
|
}
|
|
700
745
|
function catalogEntry(model2, priority, instructions2) {
|
|
@@ -736,11 +781,12 @@ var codex = {
|
|
|
736
781
|
id: "codex",
|
|
737
782
|
name: "Codex",
|
|
738
783
|
install: "npm install -g @openai/codex",
|
|
739
|
-
does: "Added the `adaptiq` profile, which starts Codex on AdaptiqAI. Your own Codex stays as it is.",
|
|
784
|
+
does: "Added the `adaptiq` profile, which starts Codex on AdaptiqAI with your memory and knowledge. Your own Codex stays as it is.",
|
|
740
785
|
signsInAs: async () => "Codex",
|
|
741
786
|
found: async (context2) => command(context2, "codex") !== void 0,
|
|
742
787
|
async prepare(context2) {
|
|
743
788
|
await instructions(context2);
|
|
789
|
+
await installMcp(context2, "Codex");
|
|
744
790
|
},
|
|
745
791
|
async current(context2) {
|
|
746
792
|
const text = await readText(profile(context2));
|
|
@@ -753,14 +799,14 @@ var codex = {
|
|
|
753
799
|
async apply(context2, facts) {
|
|
754
800
|
const prompt = await instructions(context2);
|
|
755
801
|
const folder2 = configDir(context2.env, context2.home);
|
|
756
|
-
await
|
|
802
|
+
await mkdir4(folder2, { recursive: true, mode: 448 });
|
|
757
803
|
await chmod3(folder2, 448).catch(() => {
|
|
758
804
|
});
|
|
759
805
|
await writeWhole(keyFile2(context2), `${facts.key}
|
|
760
806
|
`, null, { secret: true });
|
|
761
|
-
await
|
|
762
|
-
await
|
|
763
|
-
|
|
807
|
+
await writeWhole(wrapper(context2), wrapperText(context2), null, { mode: 493 });
|
|
808
|
+
await mkdir4(home(context2), { recursive: true });
|
|
809
|
+
await writeWhole(catalog(context2), JSON.stringify({ models: facts.models.map((model2, priority) => catalogEntry(model2, priority, prompt)) }), null);
|
|
764
810
|
await writeWhole(profile(context2), config(context2, facts), context2.backups, { secret: true });
|
|
765
811
|
return [];
|
|
766
812
|
},
|
|
@@ -768,15 +814,41 @@ var codex = {
|
|
|
768
814
|
await removeFile(profile(context2));
|
|
769
815
|
await removeFile(catalog(context2));
|
|
770
816
|
await removeFile(keyFile2(context2));
|
|
817
|
+
await removeFile(wrapper(context2));
|
|
771
818
|
},
|
|
772
|
-
|
|
819
|
+
// With Codex taken off this computer, or its profile, there was nothing to remove, and the key stayed in its file.
|
|
820
|
+
clean: async (context2) => ({ removed: await removeFile(keyFile2(context2)) ? [keyFile2(context2)] : [], left: [] }),
|
|
821
|
+
start: async () => "codex --profile adaptiq",
|
|
822
|
+
greet: async (context2, _model, hello2) => context2.platform === "win32" ? void 0 : `codex --profile adaptiq ${shellWord(hello2)}`
|
|
773
823
|
};
|
|
774
824
|
|
|
775
825
|
// src/apps/deepseek.ts
|
|
776
|
-
import {
|
|
777
|
-
import {
|
|
826
|
+
import { createHash } from "node:crypto";
|
|
827
|
+
import { access as access2, chmod as chmod4, constants as constants3, mkdir as mkdir6, mkdtemp as mkdtemp2, rm as rm3, writeFile as writeFile2 } from "node:fs/promises";
|
|
828
|
+
import { createWriteStream, existsSync as existsSync5 } from "node:fs";
|
|
829
|
+
import { Readable } from "node:stream";
|
|
830
|
+
import { pipeline } from "node:stream/promises";
|
|
778
831
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
779
832
|
import { delimiter as delimiter2, join as join9 } from "node:path";
|
|
833
|
+
|
|
834
|
+
// src/record.ts
|
|
835
|
+
import { join as join8 } from "node:path";
|
|
836
|
+
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
837
|
+
var file = (context2, app) => join8(cliDir(context2), `${app.id}.json`);
|
|
838
|
+
async function remember(context2, app, values) {
|
|
839
|
+
if (Object.keys(await readJson(file(context2, app))).length > 0) return;
|
|
840
|
+
await mkdir5(cliDir(context2), { recursive: true, mode: 448 });
|
|
841
|
+
await writeWhole(file(context2, app), `${JSON.stringify({ had: true, ...values }, null, 2)}
|
|
842
|
+
`, null, { secret: true });
|
|
843
|
+
}
|
|
844
|
+
async function recall(context2, app) {
|
|
845
|
+
return await readJson(file(context2, app));
|
|
846
|
+
}
|
|
847
|
+
var forget = async (context2, app) => {
|
|
848
|
+
await removeFile(file(context2, app));
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/apps/deepseek.ts
|
|
780
852
|
var PLUGIN = "@adaptiqai/deepseek-harness";
|
|
781
853
|
var ITS = "AdaptiqAI's plugin for DeepSeek Harness";
|
|
782
854
|
var BUNDLE = "com.deepseek.dsh";
|
|
@@ -788,6 +860,56 @@ var credentials = (context2) => join9(home2(context2), ".credentials.yaml");
|
|
|
788
860
|
var appApp = (context2) => macApp(context2, "DeepSeek Harness");
|
|
789
861
|
var appBinary = (app) => join9(app, "Contents", "MacOS", "DeepSeek Harness");
|
|
790
862
|
var NPX = ["npx", "-y", "@deepseek-ai/dsh"];
|
|
863
|
+
var TEAM = "NAN929V4UM";
|
|
864
|
+
function feedField(feed, key) {
|
|
865
|
+
const lines = feed.split("\n");
|
|
866
|
+
const at = lines.findIndex((line) => line.startsWith(`${key}:`));
|
|
867
|
+
if (at < 0) return void 0;
|
|
868
|
+
const value = lines[at]?.slice(key.length + 1).trim() ?? "";
|
|
869
|
+
return (/^[>|]/u.test(value) ? lines[at + 1]?.trim() : value)?.split(/\s/u)[0] || void 0;
|
|
870
|
+
}
|
|
871
|
+
async function installMacApp(context2) {
|
|
872
|
+
const arm = (await context2.run("sysctl", ["-n", "hw.optional.arm64"], { timeoutMs: 5e3 }).catch(() => void 0))?.stdout.trim() === "1";
|
|
873
|
+
const feedUrl = `https://download.deepseek.com/dsh-desk/feeds/mac-${arm ? "arm64" : "x64"}/nightly-mac.yml`;
|
|
874
|
+
const temp = await mkdtemp2(join9(tmpdir2(), "adaptiq-dsh-"));
|
|
875
|
+
try {
|
|
876
|
+
const feed = await context2.fetch(feedUrl);
|
|
877
|
+
if (!feed.ok) return false;
|
|
878
|
+
const text = await feed.text();
|
|
879
|
+
const path = feedField(text, "path");
|
|
880
|
+
const sum = feedField(text, "sha512");
|
|
881
|
+
if (path === void 0 || sum === void 0) return false;
|
|
882
|
+
const url = new URL(path, feedUrl);
|
|
883
|
+
if (url.protocol !== "https:") return false;
|
|
884
|
+
context2.say.note("Downloading the DeepSeek Harness app...");
|
|
885
|
+
const zip = await context2.fetch(url.href);
|
|
886
|
+
if (!zip.ok || zip.body === null) return false;
|
|
887
|
+
const file3 = join9(temp, "app.zip");
|
|
888
|
+
const hash = createHash("sha512");
|
|
889
|
+
await pipeline(Readable.fromWeb(zip.body), async function* (chunks) {
|
|
890
|
+
for await (const chunk of chunks) {
|
|
891
|
+
hash.update(chunk);
|
|
892
|
+
yield chunk;
|
|
893
|
+
}
|
|
894
|
+
}, createWriteStream(file3));
|
|
895
|
+
if (hash.digest("base64") !== sum) return false;
|
|
896
|
+
const app = join9(temp, "DeepSeek Harness.app");
|
|
897
|
+
for (const [command2, args] of [
|
|
898
|
+
["ditto", ["-x", "-k", file3, temp]],
|
|
899
|
+
["spctl", ["-a", "-t", "exec", app]],
|
|
900
|
+
["codesign", ["-v", `-R=anchor apple generic and certificate leaf[subject.OU] = ${TEAM}`, app]]
|
|
901
|
+
]) {
|
|
902
|
+
if ((await context2.run(command2, [...args], { timeoutMs: 3e5 })).code !== 0) return false;
|
|
903
|
+
}
|
|
904
|
+
const folder2 = await access2("/Applications", constants3.W_OK).then(() => "/Applications", () => join9(context2.home, "Applications"));
|
|
905
|
+
if ((await context2.run("mkdir", ["-p", folder2], { timeoutMs: 1e4 })).code !== 0) return false;
|
|
906
|
+
return (await context2.run("mv", [app, `${folder2}/`], { timeoutMs: 12e4 })).code === 0;
|
|
907
|
+
} catch {
|
|
908
|
+
return false;
|
|
909
|
+
} finally {
|
|
910
|
+
await rm3(temp, { recursive: true, force: true });
|
|
911
|
+
}
|
|
912
|
+
}
|
|
791
913
|
var EDIT_APP = String.raw`
|
|
792
914
|
const fs=require("fs"),path=require("path"),os=require("os");
|
|
793
915
|
const [app,base,provider,root,site]=process.argv.slice(1),given=JSON.parse(provider),ids=given.models.map((m)=>m.id);
|
|
@@ -852,6 +974,21 @@ else patch.deleteIn([p])}}
|
|
|
852
974
|
save(pf,patch)}
|
|
853
975
|
}catch(e){quit(String(e&&e.message||e))}
|
|
854
976
|
`;
|
|
977
|
+
var CLEAN_APP = String.raw`
|
|
978
|
+
const fs=require("fs"),path=require("path"),os=require("os");
|
|
979
|
+
const [app,root]=process.argv.slice(1);
|
|
980
|
+
const {parseDocument,isMap}=require(path.join(app,"Contents","Resources","app.asar","dsh","node_modules","yaml"));
|
|
981
|
+
const home=process.env.DSH_HOME||path.join(os.homedir(),".dsh"),quit=(text)=>{console.error(text);process.exit(1)};
|
|
982
|
+
try{
|
|
983
|
+
const cf=path.join(home,".credentials.yaml");let text;
|
|
984
|
+
try{text=fs.readFileSync(cf,"utf8")}catch(e){if(e.code==="ENOENT")process.exit(0);throw e}
|
|
985
|
+
const keys=parseDocument(text,{customTags:[{tag:"tag:yaml.org,2002:js",resolve:(v)=>v}]});if(keys.errors.length)quit(cf+" is not valid YAML.");
|
|
986
|
+
const refs=keys.get("refs");if(isMap(refs)){refs.delete("${APP_KEY}");if(!refs.items.length)keys.delete("refs")}
|
|
987
|
+
if(keys.getIn(["records","adaptiq/workspace","payload","apiBase"])===root)keys.deleteIn(["records","adaptiq/workspace"]);
|
|
988
|
+
if(String(keys)===text)process.exit(0);
|
|
989
|
+
const tmp=cf+".adaptiq";fs.writeFileSync(tmp,String(keys),{mode:0o600});fs.chmodSync(tmp,0o600);fs.renameSync(tmp,cf);console.log("changed")
|
|
990
|
+
}catch(e){quit(String(e&&e.message||e))}
|
|
991
|
+
`;
|
|
855
992
|
function dshModel(model2) {
|
|
856
993
|
const { reasoning } = model2;
|
|
857
994
|
return {
|
|
@@ -904,9 +1041,9 @@ async function withPnpm(context2, work) {
|
|
|
904
1041
|
if (which("pnpm", context2.env, context2.platform) !== void 0) return await work({});
|
|
905
1042
|
const bin2 = await mkdtemp2(join9(tmpdir2(), "adaptiq-pnpm-"));
|
|
906
1043
|
try {
|
|
907
|
-
if (context2.platform === "win32") await
|
|
1044
|
+
if (context2.platform === "win32") await writeFile2(join9(bin2, "pnpm.cmd"), "@npx -y pnpm@11 %*\r\n");
|
|
908
1045
|
else {
|
|
909
|
-
await
|
|
1046
|
+
await writeFile2(join9(bin2, "pnpm"), '#!/bin/sh\nexec npx -y pnpm@11 "$@"\n');
|
|
910
1047
|
await chmod4(join9(bin2, "pnpm"), 493);
|
|
911
1048
|
}
|
|
912
1049
|
return await work({ PATH: `${bin2}${delimiter2}${context2.env.PATH ?? ""}` });
|
|
@@ -915,38 +1052,39 @@ async function withPnpm(context2, work) {
|
|
|
915
1052
|
}
|
|
916
1053
|
}
|
|
917
1054
|
async function makeAppProfile(context2, app) {
|
|
918
|
-
if (
|
|
1055
|
+
if (existsSync5(join9(profileDir(context2, "desktop"), "package.json"))) return;
|
|
919
1056
|
const script = `(async()=>{const path=require("path"),url=require("url");const [app,prof]=process.argv.slice(1);
|
|
920
|
-
const
|
|
921
|
-
boot
|
|
1057
|
+
const lib=(pkg)=>import(url.pathToFileURL(path.join(app,"Contents","Resources","app.asar","dsh","node_modules","@deepseek-ai",pkg,"lib","index.js")).href);
|
|
1058
|
+
const boot=await lib("dsh-app-boot"),templates=boot.PROFILE_TEMPLATES||(await lib("dsh-official-bundles")).PROFILE_TEMPLATES;
|
|
1059
|
+
boot.initProfile(prof,templates.web.bundles)})().catch((e)=>{console.error(String(e&&e.message||e));process.exit(1)})`;
|
|
922
1060
|
const done = await context2.run(appBinary(app), ["-e", script, app, profileDir(context2, "desktop")], { env: { ELECTRON_RUN_AS_NODE: "1" }, timeoutMs: 6e4 });
|
|
923
1061
|
if (done.code !== 0) throw new Problem("app_refused", `DeepSeek Harness could not make its profile: ${done.stderr.trim().split("\n").pop() ?? ""}`);
|
|
924
1062
|
}
|
|
925
1063
|
async function applyApp(context2, facts, app, notes) {
|
|
926
|
-
const wasOpen = await
|
|
1064
|
+
const wasOpen = await open2(context2);
|
|
927
1065
|
const pluginThere = await installedVersion(context2, "desktop") !== "";
|
|
928
1066
|
const quit2 = wasOpen && await context2.confirm("DeepSeek Harness is open, and AdaptiqAI's plugin goes in while it is closed. Quit it now? It opens again after.");
|
|
929
1067
|
if (quit2 && !await quitMacApp(context2, BUNDLE)) throw new Problem("app_open", "DeepSeek Harness did not quit, so it was left as it was. Quit it, then run this again.");
|
|
930
1068
|
if (wasOpen && !quit2 && !pluginThere) throw new Problem("app_open", "DeepSeek Harness is open, and AdaptiqAI's plugin goes in while it is closed. Quit it, then run this again.");
|
|
931
1069
|
try {
|
|
932
|
-
if (
|
|
1070
|
+
if (!wasOpen || quit2) {
|
|
933
1071
|
await makeAppProfile(context2, app);
|
|
934
1072
|
await ensurePlugin(context2, [join9(app, "Contents", "Resources", "runtime", "cli", "bin", "dsh")], "desktop", {}, notes);
|
|
935
1073
|
}
|
|
936
1074
|
await rememberDefault(context2, "desktop");
|
|
937
1075
|
const files = [credentials(context2), join9(profileDir(context2, "desktop"), "cordis.patch.yml")];
|
|
938
|
-
for (const
|
|
1076
|
+
for (const file3 of files) await context2.backups.keep(file3);
|
|
939
1077
|
const edited = await context2.run(appBinary(app), ["-e", EDIT_APP, app, apiBase(context2), JSON.stringify(dshProvider(facts.models)), context2.origin, context2.console], {
|
|
940
1078
|
env: { ELECTRON_RUN_AS_NODE: "1", K: facts.key },
|
|
941
1079
|
timeoutMs: 6e4
|
|
942
1080
|
});
|
|
943
1081
|
if (edited.code !== 0) throw new Problem("app_refused", edited.stderr.trim().split("\n").pop() ?? "DeepSeek Harness's settings could not be changed.");
|
|
944
|
-
for (const
|
|
1082
|
+
for (const file3 of files) await context2.backups.dropIfSame(file3);
|
|
945
1083
|
} finally {
|
|
946
1084
|
if (quit2) await openMacApp(context2, app);
|
|
947
1085
|
}
|
|
948
1086
|
}
|
|
949
|
-
var
|
|
1087
|
+
var open2 = async (context2) => {
|
|
950
1088
|
const said = await context2.run("osascript", ["-e", `application id "${BUNDLE}" is running`], { timeoutMs: 5e3 }).catch(() => void 0);
|
|
951
1089
|
return said?.stdout.trim() === "true";
|
|
952
1090
|
};
|
|
@@ -960,7 +1098,7 @@ async function rememberDefault(context2, profile2) {
|
|
|
960
1098
|
async function applyWeb(context2, facts, notes) {
|
|
961
1099
|
const profile2 = profileDir(context2, "adaptiq");
|
|
962
1100
|
await withPnpm(context2, async (env2) => {
|
|
963
|
-
if (!
|
|
1101
|
+
if (!existsSync5(profile2)) {
|
|
964
1102
|
const made = await context2.run(NPX[0], [...NPX.slice(1), "--profile", "adaptiq", "--from-default-profile", "web", "--dump-config"], { env: env2, timeoutMs: 6e5, cwd: context2.home });
|
|
965
1103
|
if (made.code !== 0) throw new Problem("app_refused", `DeepSeek Harness could not be fetched: ${made.stderr.trim().split("\n").pop() ?? ""}`);
|
|
966
1104
|
}
|
|
@@ -987,11 +1125,16 @@ var deepseek = {
|
|
|
987
1125
|
aliases: ["deepseek-harness", "dsh"],
|
|
988
1126
|
name: "DeepSeek Harness",
|
|
989
1127
|
install: "Get the app from deepseek.com.",
|
|
1128
|
+
desktop: "https://www.deepseek.com/harness/",
|
|
1129
|
+
installMacApp,
|
|
990
1130
|
does: "Installed AdaptiqAI's plugin in DeepSeek Harness, with your memory and knowledge. Your other providers stay.",
|
|
991
1131
|
fetched: true,
|
|
992
1132
|
// The app signs in under a name of its own, so the console knows it again.
|
|
993
1133
|
signsInAs: async (context2) => appApp(context2) === void 0 ? "DeepSeek Harness" : "DeepSeek Harness app",
|
|
994
|
-
found: async (context2) => appApp(context2) !== void 0 ||
|
|
1134
|
+
found: async (context2) => appApp(context2) !== void 0 || existsSync5(home2(context2)),
|
|
1135
|
+
async prepare(context2) {
|
|
1136
|
+
if (appApp(context2) === void 0) await needNode(context2, "24.9.0", "DeepSeek Harness in the browser", ["connect", "deepseek"]);
|
|
1137
|
+
},
|
|
995
1138
|
async current(context2) {
|
|
996
1139
|
const app = appApp(context2) !== void 0;
|
|
997
1140
|
const patch = await readText(join9(profileDir(context2, app ? "desktop" : "adaptiq"), "cordis.patch.yml")) ?? "";
|
|
@@ -1026,11 +1169,35 @@ var deepseek = {
|
|
|
1026
1169
|
}
|
|
1027
1170
|
await forget(context2, deepseek);
|
|
1028
1171
|
},
|
|
1172
|
+
async clean(context2) {
|
|
1173
|
+
const removed = [];
|
|
1174
|
+
const app = appApp(context2);
|
|
1175
|
+
if (app !== void 0) {
|
|
1176
|
+
await context2.backups.keep(credentials(context2));
|
|
1177
|
+
const done = await context2.run(appBinary(app), ["-e", CLEAN_APP, app, context2.origin], { env: { ELECTRON_RUN_AS_NODE: "1" }, timeoutMs: 6e4 });
|
|
1178
|
+
if (done.code !== 0) throw new Problem("app_refused", done.stderr.trim().split("\n").pop() ?? "DeepSeek Harness's credentials could not be changed.");
|
|
1179
|
+
if (done.stdout.includes("changed")) removed.push(credentials(context2));
|
|
1180
|
+
await context2.backups.dropIfSame(credentials(context2));
|
|
1181
|
+
}
|
|
1182
|
+
const env = join9(home2(context2), ".env");
|
|
1183
|
+
const text = await readText(env);
|
|
1184
|
+
if (text?.split("\n").some((line) => line.startsWith(`${WEB_KEY}=`)) === true) {
|
|
1185
|
+
await writeWhole(env, `${text.split("\n").filter((line) => !line.startsWith(`${WEB_KEY}=`) && line !== "").join("\n")}
|
|
1186
|
+
`, context2.backups, { secret: true });
|
|
1187
|
+
removed.push(env);
|
|
1188
|
+
}
|
|
1189
|
+
if (!await fileHoldsKey(credentials(context2))) return { removed, left: [] };
|
|
1190
|
+
return {
|
|
1191
|
+
removed,
|
|
1192
|
+
left: [app === void 0 ? `DeepSeek Harness's credentials still hold an AdaptiqAI key (${credentials(context2)}), from a sign-in of AdaptiqAI's plugin. Start it (npx -y @deepseek-ai/dsh adaptiq) and type /adaptiq signout.` : `DeepSeek Harness's credentials still hold an AdaptiqAI key (${credentials(context2)}) that no setup of this AdaptiqAI wrote.`]
|
|
1193
|
+
};
|
|
1194
|
+
},
|
|
1029
1195
|
start: async (context2) => appApp(context2) === void 0 ? "npx -y @deepseek-ai/dsh adaptiq" : 'open -a "DeepSeek Harness"'
|
|
1030
1196
|
};
|
|
1031
1197
|
|
|
1032
1198
|
// src/apps/hermes.ts
|
|
1033
|
-
import {
|
|
1199
|
+
import { readdir as readdir2 } from "node:fs/promises";
|
|
1200
|
+
import { dirname as dirname3, isAbsolute, join as join10 } from "node:path";
|
|
1034
1201
|
var PLUGIN2 = "AdaptiqAI/hermes-plugin";
|
|
1035
1202
|
var ENTRY = "adaptiqai-connect";
|
|
1036
1203
|
var KEY_ENV = "ADAPTIQ_API_KEY";
|
|
@@ -1050,9 +1217,29 @@ async function envFile(context2) {
|
|
|
1050
1217
|
}
|
|
1051
1218
|
var envLine = new RegExp(`^\\s*(?:export\\s+)?${KEY_ENV}\\s*=`, "u");
|
|
1052
1219
|
var withKey = (text, key) => {
|
|
1053
|
-
const lines = text.split("\n").filter((line) => !envLine.test(line)
|
|
1220
|
+
const lines = text.replace(/\n$/u, "").split("\n").filter((line) => !envLine.test(line));
|
|
1221
|
+
if (lines.length === 1 && lines[0] === "") lines.pop();
|
|
1054
1222
|
return [...lines, ...key === void 0 ? [] : [`${KEY_ENV}=${key}`], ""].join("\n");
|
|
1055
1223
|
};
|
|
1224
|
+
async function gatewayRuns(file3) {
|
|
1225
|
+
const pid = Number(/\d+/u.exec(await readText(join10(dirname3(file3), "gateway.pid")) ?? "")?.[0]);
|
|
1226
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
1227
|
+
try {
|
|
1228
|
+
process.kill(pid, 0);
|
|
1229
|
+
return true;
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
return error.code === "EPERM";
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async function hermesHome(context2) {
|
|
1235
|
+
const file3 = await envFile(context2).catch(() => void 0);
|
|
1236
|
+
return file3 === void 0 ? context2.env.HERMES_HOME ?? join10(context2.home, ".hermes") : dirname3(file3);
|
|
1237
|
+
}
|
|
1238
|
+
var SNAPSHOT = /^hermes-snap-[A-Za-z0-9]+\.sh(?:\.tmp\.[A-Za-z0-9]+)?$/u;
|
|
1239
|
+
var snapshotFolders = (context2, home3) => [.../* @__PURE__ */ new Set([
|
|
1240
|
+
...["TERMINAL_TEMP_DIR", "TMPDIR", "TMP", "TEMP"].map((name2) => context2.env[name2] ?? "").filter((path) => isAbsolute(path)),
|
|
1241
|
+
join10(home3, "cache", "terminal")
|
|
1242
|
+
])];
|
|
1056
1243
|
var modelBlock = async (context2) => {
|
|
1057
1244
|
const said = await context2.run(hermes(context2), ["config", "get", "--json", "model"], { timeoutMs: 6e4 });
|
|
1058
1245
|
if (said.code !== 0) return {};
|
|
@@ -1067,11 +1254,21 @@ var hermesApp = {
|
|
|
1067
1254
|
id: "hermes",
|
|
1068
1255
|
name: "Hermes Agent",
|
|
1069
1256
|
install: "curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash",
|
|
1257
|
+
desktop: "https://hermes-agent.nousresearch.com/#downloads",
|
|
1070
1258
|
does: "Added AdaptiqAI to Hermes as an endpoint of its own, and made it the main model. Your other providers stay.",
|
|
1071
1259
|
signsInAs: async () => "Hermes Agent",
|
|
1072
1260
|
found: async (context2) => command(context2, "hermes", [join10(context2.home, ".local", "bin", "hermes")]) !== void 0,
|
|
1073
1261
|
async prepare(context2) {
|
|
1074
1262
|
await envFile(context2);
|
|
1263
|
+
const installed2 = await context2.run(hermes(context2), ["plugins", "list"], { timeoutMs: 6e4 });
|
|
1264
|
+
const got = /adaptiq/iu.test(installed2.stdout) ? await context2.run(hermes(context2), ["plugins", "update", "adaptiq"], { timeoutMs: 18e4 }) : await context2.run(hermes(context2), ["plugins", "install", PLUGIN2, "--enable"], { timeoutMs: 18e4 });
|
|
1265
|
+
if (await quietly(context2, "plugins", "enable", "adaptiq") !== 0) {
|
|
1266
|
+
if (/could not read Username|Repository not found|Authentication failed/u.test(`${got.stdout}
|
|
1267
|
+
${got.stderr}`)) {
|
|
1268
|
+
throw new Problem("tools_not_installed", "GitHub did not hand out AdaptiqAI's plugin for Hermes, so Hermes was left as it was. Running this again will not help until AdaptiqAI has fixed it: tell us under Help & support.");
|
|
1269
|
+
}
|
|
1270
|
+
throw new Problem("tools_not_installed", `AdaptiqAI's plugin for Hermes could not be installed, so Hermes was left as it was. Check the connection, then run this again, or install it yourself: hermes plugins install ${PLUGIN2} --enable`);
|
|
1271
|
+
}
|
|
1075
1272
|
},
|
|
1076
1273
|
async current(context2) {
|
|
1077
1274
|
const model2 = await modelBlock(context2);
|
|
@@ -1084,15 +1281,11 @@ var hermesApp = {
|
|
|
1084
1281
|
const notes = [];
|
|
1085
1282
|
const was = await modelBlock(context2);
|
|
1086
1283
|
await remember(context2, hermesApp, { model: was.provider === ENTRY ? {} : Object.fromEntries(MODEL_FIELDS.map((field) => [field, was[field] ?? null])) });
|
|
1087
|
-
|
|
1088
|
-
if (!/adaptiq/iu.test(installed2.stdout)) {
|
|
1089
|
-
const added = await context2.run(hermes(context2), ["plugins", "install", PLUGIN2], { timeoutMs: 18e4 });
|
|
1090
|
-
if (added.code !== 0) notes.push("AdaptiqAI's plugin for Hermes could not be installed, so conversations have no memory of their own yet. Run: hermes plugins install AdaptiqAI/hermes-plugin");
|
|
1091
|
-
}
|
|
1284
|
+
await quietly(context2, "config", "set", "tools.tool_search.enabled", "off");
|
|
1092
1285
|
const base = apiBase(context2);
|
|
1093
1286
|
const [model2] = facts.models;
|
|
1094
|
-
const
|
|
1095
|
-
await writeWhole(
|
|
1287
|
+
const file3 = await envFile(context2);
|
|
1288
|
+
await writeWhole(file3, withKey(await readText(file3) ?? "", facts.key), context2.backups, { secret: true });
|
|
1096
1289
|
await set(context2, `providers.${ENTRY}`, JSON.stringify({
|
|
1097
1290
|
name: "AdaptiqAI",
|
|
1098
1291
|
base_url: base,
|
|
@@ -1109,22 +1302,47 @@ var hermesApp = {
|
|
|
1109
1302
|
await set(context2, "model.api_mode", "chat_completions");
|
|
1110
1303
|
await set(context2, "model.key_env", KEY_ENV);
|
|
1111
1304
|
await set(context2, "model.default", model2.id);
|
|
1112
|
-
for (const entry2 of facts.models) await quietly(context2, "config", "set", `compression.model_thresholds.${entry2.id.replace(/\./gu, "\\.")}`, "
|
|
1305
|
+
for (const entry2 of facts.models) await quietly(context2, "config", "set", `compression.model_thresholds.${entry2.id.replace(/\./gu, "\\.")}`, "0.99");
|
|
1306
|
+
if (await gatewayRuns(file3)) {
|
|
1307
|
+
const restarted = await context2.confirm("Restart Hermes' gateway now, so its chats get AdaptiqAI's plugin?") && await quietly(context2, "gateway", "restart", "--detach") === 0;
|
|
1308
|
+
if (!restarted) notes.push("Hermes' chats get AdaptiqAI's plugin once its gateway restarts: hermes gateway restart");
|
|
1309
|
+
}
|
|
1113
1310
|
if (await hermesOpen(context2)) notes.push("Hermes is open. Quit it and open it again to read the new key, then start a new chat.");
|
|
1114
1311
|
return notes;
|
|
1115
1312
|
},
|
|
1116
1313
|
async remove(context2) {
|
|
1117
1314
|
const had = record((await recall(context2, hermesApp)).model);
|
|
1118
1315
|
await quietly(context2, "config", "unset", `providers.${ENTRY}`);
|
|
1119
|
-
const
|
|
1120
|
-
const text = await readText(
|
|
1121
|
-
if (text !== void 0) await writeWhole(
|
|
1316
|
+
const file3 = await envFile(context2);
|
|
1317
|
+
const text = await readText(file3);
|
|
1318
|
+
if (text !== void 0) await writeWhole(file3, withKey(text, void 0), context2.backups, { secret: true });
|
|
1122
1319
|
for (const field of MODEL_FIELDS) {
|
|
1123
1320
|
const value = had[field];
|
|
1124
1321
|
if (typeof value === "string" && value !== "") await set(context2, `model.${field}`, value);
|
|
1125
1322
|
else await quietly(context2, "config", "unset", `model.${field}`);
|
|
1126
1323
|
}
|
|
1127
1324
|
await forget(context2, hermesApp);
|
|
1325
|
+
const notes = [];
|
|
1326
|
+
if (await gatewayRuns(file3)) {
|
|
1327
|
+
const restarted = await context2.confirm("Restart Hermes' gateway now, so it lets go of the AdaptiqAI key?") && await quietly(context2, "gateway", "restart", "--detach") === 0;
|
|
1328
|
+
if (!restarted) notes.push("Hermes' gateway keeps the AdaptiqAI key until it restarts: hermes gateway restart");
|
|
1329
|
+
}
|
|
1330
|
+
if (await hermesOpen(context2)) notes.push("Hermes is open, and keeps the AdaptiqAI key until it closes. Quit it, and open it again.");
|
|
1331
|
+
return notes;
|
|
1332
|
+
},
|
|
1333
|
+
async clean(context2) {
|
|
1334
|
+
const home3 = await hermesHome(context2);
|
|
1335
|
+
const removed = [];
|
|
1336
|
+
const env = join10(home3, ".env");
|
|
1337
|
+
const text = await readText(env);
|
|
1338
|
+
if (text?.split("\n").some((line) => envLine.test(line)) === true && await writeWhole(env, withKey(text, void 0), context2.backups, { secret: true })) removed.push(env);
|
|
1339
|
+
for (const folder2 of snapshotFolders(context2, home3)) {
|
|
1340
|
+
for (const name2 of (await readdir2(folder2).catch(() => [])).filter((entry2) => SNAPSHOT.test(entry2))) {
|
|
1341
|
+
const path = join10(folder2, name2);
|
|
1342
|
+
if (await fileHoldsKey(path) && await removeFile(path)) removed.push(path);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return { removed, left: [] };
|
|
1128
1346
|
},
|
|
1129
1347
|
start: async (context2) => macApp(context2, "Hermes") === void 0 ? "hermes" : "open -b com.nousresearch.hermes",
|
|
1130
1348
|
async ask(context2) {
|
|
@@ -1138,7 +1356,7 @@ async function hermesOpen(context2) {
|
|
|
1138
1356
|
}
|
|
1139
1357
|
|
|
1140
1358
|
// src/apps/opencode.ts
|
|
1141
|
-
import { existsSync as
|
|
1359
|
+
import { existsSync as existsSync6, readdirSync, readFileSync } from "node:fs";
|
|
1142
1360
|
import { join as join11 } from "node:path";
|
|
1143
1361
|
var KEY_ENV2 = "ADAPTIQ_API_KEY";
|
|
1144
1362
|
var dataDir = (context2) => context2.env.XDG_DATA_HOME ?? join11(context2.home, ".local", "share");
|
|
@@ -1147,10 +1365,19 @@ async function signIns(context2, folder2) {
|
|
|
1147
1365
|
return Object.fromEntries(Object.entries(all).filter(([, entry2]) => record(entry2).key === KEY_ENV2).map(([url, entry2]) => [url, record(entry2)]));
|
|
1148
1366
|
}
|
|
1149
1367
|
async function oneSignIn(context2, binary, folder2) {
|
|
1150
|
-
|
|
1151
|
-
|
|
1368
|
+
const all = await readJson(join11(dataDir(context2), folder2, "auth.json")).catch(() => ({}));
|
|
1369
|
+
for (const [url, entry2] of Object.entries(all)) {
|
|
1370
|
+
if (url !== apiBase(context2) && (url === "adaptiqai" || record(entry2).key === KEY_ENV2)) await context2.run(binary, ["auth", "logout", url], { timeoutMs: 3e4 });
|
|
1152
1371
|
}
|
|
1153
1372
|
}
|
|
1373
|
+
async function providersLogin(context2, binary, folder2, name2, env) {
|
|
1374
|
+
const file3 = join11(dataDir(context2), folder2, "auth.json");
|
|
1375
|
+
const before = await readText(file3);
|
|
1376
|
+
const done = await context2.run(binary, ["providers", "login", apiBase(context2)], { env });
|
|
1377
|
+
if (done.code !== 0) throw failed(name2, done.stderr);
|
|
1378
|
+
if (await readText(file3) === before) throw failed(name2, `${done.stdout}
|
|
1379
|
+
${done.stderr}`);
|
|
1380
|
+
}
|
|
1154
1381
|
var signInEnv = (context2, app, facts) => ({
|
|
1155
1382
|
ADAPTIQ_CONNECT: facts.deviceCode,
|
|
1156
1383
|
ADAPTIQ_APP: app,
|
|
@@ -1161,12 +1388,30 @@ var fromSignIn = (context2, entries) => {
|
|
|
1161
1388
|
if (entry2 === void 0) return {};
|
|
1162
1389
|
return { base: apiBase(context2), ...typeof entry2.token === "string" ? { key: entry2.token } : {} };
|
|
1163
1390
|
};
|
|
1164
|
-
|
|
1391
|
+
async function cleanSignIns(context2, binary, folder2) {
|
|
1392
|
+
const removed = [];
|
|
1393
|
+
const grant = await forgetGrant(context2, join11(dataDir(context2), folder2, "adaptiqai"));
|
|
1394
|
+
if (grant !== void 0) removed.push(grant);
|
|
1395
|
+
const database = join11(dataDir(context2), folder2, `${folder2}.db`);
|
|
1396
|
+
const unreachable = binary === void 0 && (await fileHoldsKey(database) || await fileHoldsKey(`${database}-wal`)) ? [`${database} still holds an AdaptiqAI key, and ${folder2 === "kilo" ? "Kilo" : "OpenCode"} is not here to log it out. Install it again, then run this again.`] : [];
|
|
1397
|
+
const auth = join11(dataDir(context2), folder2, "auth.json");
|
|
1398
|
+
const ours = async () => Object.keys(await readJson(auth).catch(() => ({}))).filter((url) => url === "adaptiqai" || url === apiBase(context2));
|
|
1399
|
+
const had = await ours();
|
|
1400
|
+
if (had.length === 0) return { removed, left: unreachable };
|
|
1401
|
+
if (binary === void 0) await updateJson(auth, (value) => {
|
|
1402
|
+
for (const url of had) delete value[url];
|
|
1403
|
+
}, context2.backups, { secret: true });
|
|
1404
|
+
else for (const url of had) await context2.run(binary, ["auth", "logout", url], { timeoutMs: 3e4 }).catch(() => void 0);
|
|
1405
|
+
const left = await ours();
|
|
1406
|
+
if (left.length < had.length) removed.push(auth);
|
|
1407
|
+
return { removed, left: [...left.length === 0 ? [] : [`${auth} still holds an AdaptiqAI key. Run: ${folder2} auth logout ${left[0]}`], ...unreachable] };
|
|
1408
|
+
}
|
|
1409
|
+
var failed = (name2, said) => new Problem("app_sign_in_failed", `${name2} could not sign in: ${said.split("\n").map((line) => line.trim()).filter((line) => line !== "").pop() ?? "it gave no reason."}`);
|
|
1165
1410
|
var isVersion2 = (said) => /(?:^|[\sv])(?:[2-9]|[1-9]\d+)\./u.test(said.trim());
|
|
1166
1411
|
var carriedCli = (context2) => {
|
|
1167
1412
|
const app = macApp(context2, "OpenCode");
|
|
1168
1413
|
const cli = app === void 0 ? void 0 : join11(app, "Contents", "Resources", "opencode-cli");
|
|
1169
|
-
return cli !== void 0 &&
|
|
1414
|
+
return cli !== void 0 && existsSync6(cli) ? cli : void 0;
|
|
1170
1415
|
};
|
|
1171
1416
|
async function opencodeCli(context2) {
|
|
1172
1417
|
const carried = carriedCli(context2);
|
|
@@ -1175,10 +1420,39 @@ async function opencodeCli(context2) {
|
|
|
1175
1420
|
const said = await context2.run(path, ["--version"], { timeoutMs: 15e3 });
|
|
1176
1421
|
return { path, two: isVersion2(said.stdout) };
|
|
1177
1422
|
}
|
|
1423
|
+
async function v2Credentials(context2, path) {
|
|
1424
|
+
const listed = await context2.run(path, ["auth", "list", "--format=json", "--standalone"], { timeoutMs: 3e4 }).catch(() => void 0);
|
|
1425
|
+
const found = /* @__PURE__ */ new Map();
|
|
1426
|
+
try {
|
|
1427
|
+
for (const entry2 of JSON.parse(listed?.stdout ?? "")) {
|
|
1428
|
+
const { id, connections } = record(entry2);
|
|
1429
|
+
const ids = (Array.isArray(connections) ? connections : []).map(record).filter((row) => row.type === "credential" && typeof row.id === "string").map((row) => row.id);
|
|
1430
|
+
if (typeof id === "string" && ids.length > 0) found.set(id, ids);
|
|
1431
|
+
}
|
|
1432
|
+
} catch {
|
|
1433
|
+
}
|
|
1434
|
+
return found;
|
|
1435
|
+
}
|
|
1436
|
+
async function v2Logout(context2, path, integrations) {
|
|
1437
|
+
const had = await v2Credentials(context2, path);
|
|
1438
|
+
const ours = integrations.filter((integration) => had.has(integration));
|
|
1439
|
+
if (ours.length === 0) return { removed: [], left: [] };
|
|
1440
|
+
for (const integration of ours) {
|
|
1441
|
+
for (const id of had.get(integration) ?? []) await context2.run(path, ["auth", "logout", integration, id, "--standalone"], { timeoutMs: 3e4 });
|
|
1442
|
+
}
|
|
1443
|
+
await context2.run(path, ["service", "restart"], { timeoutMs: 3e4 }).catch(() => void 0);
|
|
1444
|
+
const still = await v2Credentials(context2, path);
|
|
1445
|
+
return {
|
|
1446
|
+
removed: ours.filter((integration) => !still.has(integration)).map((integration) => `OpenCode: ${integration}`),
|
|
1447
|
+
left: ours.filter((integration) => still.has(integration)).map((integration) => `OpenCode still holds an AdaptiqAI key. Run: opencode auth logout ${integration} ${still.get(integration)?.[0] ?? ""}`)
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1178
1450
|
var opencode = {
|
|
1179
1451
|
id: "opencode",
|
|
1180
1452
|
name: "OpenCode",
|
|
1181
1453
|
install: "curl -fsSL https://opencode.ai/v2/install | bash",
|
|
1454
|
+
desktop: "https://opencode.ai/desktop",
|
|
1455
|
+
collectsKey: true,
|
|
1182
1456
|
does: "Signed OpenCode in to AdaptiqAI, which turns off its automatic compaction, for every model. It reads its models from AdaptiqAI when it starts. Your other providers stay.",
|
|
1183
1457
|
signsInAs: async () => "OpenCode",
|
|
1184
1458
|
found: async (context2) => carriedCli(context2) !== void 0 || command(context2, "opencode", [join11(context2.home, ".opencode", "bin", "opencode")]) !== void 0,
|
|
@@ -1190,6 +1464,8 @@ var opencode = {
|
|
|
1190
1464
|
async current(context2) {
|
|
1191
1465
|
const { path, two } = await opencodeCli(context2);
|
|
1192
1466
|
if (!two) return fromSignIn(context2, await signIns(context2, "opencode"));
|
|
1467
|
+
const signedIn = await v2Credentials(context2, path);
|
|
1468
|
+
if (signedIn.size > 0) return signedIn.has(apiBase(context2)) ? { base: apiBase(context2) } : {};
|
|
1193
1469
|
const listed = await context2.run(path, ["auth", "list"], { timeoutMs: 2e4 });
|
|
1194
1470
|
return listed.stdout.includes(new URL(context2.origin).host) ? { base: apiBase(context2) } : {};
|
|
1195
1471
|
},
|
|
@@ -1198,25 +1474,35 @@ var opencode = {
|
|
|
1198
1474
|
const { path, two } = await opencodeCli(context2);
|
|
1199
1475
|
const env = signInEnv(context2, "OpenCode", facts);
|
|
1200
1476
|
if (two) {
|
|
1201
|
-
const
|
|
1202
|
-
if (
|
|
1477
|
+
const done = await context2.run(path, ["auth", "login", apiBase(context2), "--standalone", "--method", "login"], { env });
|
|
1478
|
+
if (done.code !== 0) throw failed("OpenCode", done.stderr);
|
|
1203
1479
|
await context2.run(path, ["service", "restart"], { timeoutMs: 3e4 }).catch(() => void 0);
|
|
1204
1480
|
return [];
|
|
1205
1481
|
}
|
|
1206
|
-
|
|
1207
|
-
if (done.code !== 0) throw failed("OpenCode", done.stderr);
|
|
1482
|
+
await providersLogin(context2, path, "opencode", "OpenCode", env);
|
|
1208
1483
|
await oneSignIn(context2, path, "opencode");
|
|
1209
1484
|
return await running(context2, "OpenCode") ? ["OpenCode is open. Quit it and open it again to see AdaptiqAI."] : [];
|
|
1210
1485
|
},
|
|
1211
1486
|
async remove(context2) {
|
|
1212
|
-
const { path } = await opencodeCli(context2);
|
|
1213
|
-
await context2
|
|
1487
|
+
const { path, two } = await opencodeCli(context2);
|
|
1488
|
+
if (two) await v2Logout(context2, path, [apiBase(context2)]);
|
|
1489
|
+
else await context2.run(path, ["auth", "logout", apiBase(context2)], { timeoutMs: 3e4 });
|
|
1490
|
+
},
|
|
1491
|
+
async clean(context2) {
|
|
1492
|
+
if (!await opencode.found(context2)) return await cleanSignIns(context2, void 0, "opencode");
|
|
1493
|
+
const { path, two } = await opencodeCli(context2);
|
|
1494
|
+
if (!two) return await cleanSignIns(context2, path, "opencode");
|
|
1495
|
+
const grant = await forgetGrant(context2, join11(dataDir(context2), "opencode", "adaptiqai"));
|
|
1496
|
+
const done = await v2Logout(context2, path, [apiBase(context2), "adaptiqai"]);
|
|
1497
|
+
return { removed: [...grant === void 0 ? [] : [grant], ...done.removed], left: done.left };
|
|
1214
1498
|
},
|
|
1215
1499
|
async start(context2, model2) {
|
|
1216
1500
|
const { two } = await opencodeCli(context2);
|
|
1217
1501
|
if (carriedCli(context2) !== void 0) return "open -b ai.opencode.desktop";
|
|
1218
1502
|
return two ? "opencode" : `opencode -m adaptiq/${model2}`;
|
|
1219
1503
|
},
|
|
1504
|
+
// The app opens where it was left, and takes no first message; the terminal UI does.
|
|
1505
|
+
greet: async (context2, model2, hello2) => carriedCli(context2) !== void 0 || context2.platform === "win32" ? void 0 : `${await opencode.start(context2, model2)} --prompt ${shellWord(hello2)}`,
|
|
1220
1506
|
async ask(context2, model2) {
|
|
1221
1507
|
const { path } = await opencodeCli(context2);
|
|
1222
1508
|
return { command: path, args: ["run", "-m", `adaptiq/${model2}`, QUESTION] };
|
|
@@ -1241,7 +1527,7 @@ function editorKilo(context2) {
|
|
|
1241
1527
|
const found = entries.filter((entry2) => entry2.startsWith("kilocode.kilo-code-") && gone[entry2] === void 0).sort(newestFirst)[0];
|
|
1242
1528
|
if (found === void 0) continue;
|
|
1243
1529
|
const path = join11(root, found, "bin", context2.platform === "win32" ? "kilo.exe" : "kilo");
|
|
1244
|
-
if (!
|
|
1530
|
+
if (!existsSync6(path)) continue;
|
|
1245
1531
|
if (context2.platform === "darwin" && macApp(context2, editor) === void 0) continue;
|
|
1246
1532
|
return { path, editor };
|
|
1247
1533
|
}
|
|
@@ -1253,6 +1539,7 @@ var kilo = {
|
|
|
1253
1539
|
aliases: ["kilo-code", "kilocode"],
|
|
1254
1540
|
name: "Kilo Code",
|
|
1255
1541
|
install: "npm install -g @kilocode/cli",
|
|
1542
|
+
collectsKey: true,
|
|
1256
1543
|
does: "Signed Kilo Code in to AdaptiqAI, which turns off its automatic compaction, for every model. It reads its models from AdaptiqAI when it starts. Your other providers stay.",
|
|
1257
1544
|
signsInAs: async () => "Kilo Code",
|
|
1258
1545
|
found: async (context2) => editorKilo(context2) !== void 0 || command(context2, "kilo") !== void 0,
|
|
@@ -1261,23 +1548,92 @@ var kilo = {
|
|
|
1261
1548
|
async apply(context2, facts) {
|
|
1262
1549
|
if (facts.deviceCode === void 0) return [];
|
|
1263
1550
|
const path = kiloCli(context2);
|
|
1264
|
-
|
|
1265
|
-
if (done.code !== 0) throw failed("Kilo Code", done.stderr);
|
|
1551
|
+
await providersLogin(context2, path, "kilo", "Kilo Code", signInEnv(context2, "Kilo Code", facts));
|
|
1266
1552
|
await oneSignIn(context2, path, "kilo");
|
|
1267
|
-
const
|
|
1268
|
-
|
|
1553
|
+
const found = editorKilo(context2);
|
|
1554
|
+
if (found === void 0) return [];
|
|
1555
|
+
const [, , scheme] = EDITORS.find(([, editor]) => editor === found.editor);
|
|
1556
|
+
const mac = context2.platform === "darwin";
|
|
1557
|
+
const open3 = mac ? (await context2.run("ps", ["-axo", "comm="], { timeoutMs: 5e3 }).catch(() => void 0))?.stdout.includes(`/${found.editor}.app/Contents/MacOS/`) === true : context2.platform === "win32" || (await context2.run("pgrep", ["-x", scheme.replace(/^vs/u, "")], { timeoutMs: 5e3 }).catch(() => void 0))?.code === 0;
|
|
1558
|
+
if (context2.person && context2.platform !== "win32") await context2.run(mac ? "open" : "xdg-open", [`${scheme}://kilocode.kilo-code/kilocode/model?agent=code`], { timeoutMs: 15e3 }).catch(() => void 0);
|
|
1559
|
+
return [
|
|
1560
|
+
...open3 ? [`Reload the ${found.editor} window to see AdaptiqAI in Kilo Code: ${mac ? "Cmd" : "Ctrl"}+Shift+P, then Developer: Reload Window.`] : [],
|
|
1561
|
+
`Start a new task, and choose AdaptiqAI \u203A ${facts.models[0].id} in Kilo's model menu.`
|
|
1562
|
+
];
|
|
1269
1563
|
},
|
|
1270
1564
|
async remove(context2) {
|
|
1271
1565
|
await context2.run(kiloCli(context2), ["auth", "logout", apiBase(context2)], { timeoutMs: 3e4 });
|
|
1272
1566
|
},
|
|
1567
|
+
clean: async (context2) => await cleanSignIns(context2, await kilo.found(context2) ? kiloCli(context2) : void 0, "kilo"),
|
|
1273
1568
|
start: async (context2, model2) => editorKilo(context2) === void 0 ? `kilo -m adaptiq/${model2}` : `open Kilo Code in ${editorKilo(context2)?.editor}`,
|
|
1569
|
+
startsByCommand: (context2) => editorKilo(context2) === void 0,
|
|
1570
|
+
greet: async (context2, model2, hello2) => editorKilo(context2) !== void 0 || context2.platform === "win32" ? void 0 : `kilo -m adaptiq/${model2} --prompt ${shellWord(hello2)}`,
|
|
1274
1571
|
ask: async (context2, model2) => ({ command: kiloCli(context2), args: ["run", "-m", `adaptiq/${model2}`, QUESTION] })
|
|
1275
1572
|
};
|
|
1276
1573
|
|
|
1277
1574
|
// src/apps/openclaw.ts
|
|
1575
|
+
import { readdir as readdir3, readFile as readFile2 } from "node:fs/promises";
|
|
1278
1576
|
import { join as join12 } from "node:path";
|
|
1279
1577
|
var PLUGIN3 = "@adaptiqai/openclaw";
|
|
1578
|
+
var PLUGIN_FLAGS = ["--force", "--accept-capabilities"];
|
|
1280
1579
|
var oc = (context2) => command(context2, "openclaw", [join12(context2.home, ".openclaw", "bin", "openclaw")]) ?? "openclaw";
|
|
1580
|
+
var state = (context2) => context2.env.OPENCLAW_STATE_DIR ?? join12(context2.home, ".openclaw");
|
|
1581
|
+
var envFile2 = (context2) => join12(state(context2), ".env");
|
|
1582
|
+
var OURS = /^\s*(export\s+)?ADAPTIQAI_(API_KEY|API_BASE|CONSOLE_URL)\s*=/u;
|
|
1583
|
+
async function writeEnv(context2, key) {
|
|
1584
|
+
const before = await readText(envFile2(context2));
|
|
1585
|
+
if (before === void 0 && key === void 0) return false;
|
|
1586
|
+
const kept = (before ?? "").replace(/\n$/u, "").split("\n").filter((line) => !OURS.test(line));
|
|
1587
|
+
if (kept.length === 1 && kept[0] === "") kept.pop();
|
|
1588
|
+
const added = key === void 0 ? [] : [`ADAPTIQAI_API_KEY=${key}`, ...Object.entries(mcpEnv(context2) ?? {}).map(([name2, value]) => `${name2}=${value}`)];
|
|
1589
|
+
return await writeWhole(envFile2(context2), [...kept, ...added, ""].join("\n"), null, { secret: true });
|
|
1590
|
+
}
|
|
1591
|
+
async function logout(context2, profile2) {
|
|
1592
|
+
if ((await context2.run(oc(context2), ["models", "auth", "logout", profile2, "--yes"], { timeoutMs: 3e4 })).code === 0) return;
|
|
1593
|
+
await context2.run(oc(context2), ["gateway", "call", "models.authLogout", "--params", JSON.stringify({ provider: "adaptiq", profileIds: [profile2] })], { timeoutMs: 3e4 });
|
|
1594
|
+
}
|
|
1595
|
+
async function databases(context2) {
|
|
1596
|
+
const agents = await readdir3(join12(state(context2), "agents")).catch(() => []);
|
|
1597
|
+
const withLogs = (path) => [path, `${path}-wal`];
|
|
1598
|
+
return [
|
|
1599
|
+
{ files: withLogs(join12(state(context2), "state", "openclaw.sqlite")), compact: ["doctor", "--state-sqlite", "compact", "--json"] },
|
|
1600
|
+
{
|
|
1601
|
+
files: agents.flatMap((agent) => withLogs(join12(state(context2), "agents", agent, "agent", "openclaw-agent.sqlite"))),
|
|
1602
|
+
compact: ["doctor", "--session-sqlite", "compact", "--session-sqlite-all-agents", "--json"]
|
|
1603
|
+
}
|
|
1604
|
+
];
|
|
1605
|
+
}
|
|
1606
|
+
var holding = async (files) => (await Promise.all(files.map(async (file3) => await fileHoldsKey(file3) ? file3 : ""))).filter((file3) => file3 !== "");
|
|
1607
|
+
var envKey = async (context2) => {
|
|
1608
|
+
const line = (await readText(envFile2(context2)) ?? "").split("\n").filter((entry2) => /^\s*(export\s+)?ADAPTIQAI_API_KEY\s*=/u.test(entry2)).pop();
|
|
1609
|
+
const key = line?.replace(/^[^=]*=/u, "").trim().replace(/^['"]|['"]$/gu, "");
|
|
1610
|
+
return key === void 0 || key === "" ? void 0 : key;
|
|
1611
|
+
};
|
|
1612
|
+
var covers = (stored, wanted) => {
|
|
1613
|
+
if (Array.isArray(wanted)) return Array.isArray(stored) && stored.length === wanted.length && wanted.every((entry2, at) => covers(stored[at], entry2));
|
|
1614
|
+
if (wanted !== null && typeof wanted === "object") {
|
|
1615
|
+
return stored !== null && typeof stored === "object" && Object.entries(wanted).every(([name2, value]) => covers(stored[name2], value));
|
|
1616
|
+
}
|
|
1617
|
+
return stored === wanted;
|
|
1618
|
+
};
|
|
1619
|
+
async function directOnly(dir2) {
|
|
1620
|
+
const entries = await readdir3(dir2, { withFileTypes: true, recursive: true }).catch(() => []);
|
|
1621
|
+
for (const entry2 of entries) {
|
|
1622
|
+
if (!entry2.isFile()) continue;
|
|
1623
|
+
const text = await readFile2(join12(entry2.parentPath, entry2.name), "utf8").catch(() => "");
|
|
1624
|
+
if (text.includes("direct-only")) return true;
|
|
1625
|
+
}
|
|
1626
|
+
return false;
|
|
1627
|
+
}
|
|
1628
|
+
var getJson = async (context2, key) => {
|
|
1629
|
+
const said = await context2.run(oc(context2), ["config", "get", key, "--json"], { timeoutMs: 3e4 });
|
|
1630
|
+
if (said.code !== 0) return void 0;
|
|
1631
|
+
try {
|
|
1632
|
+
return JSON.parse(said.stdout.slice(said.stdout.indexOf("{"), said.stdout.lastIndexOf("}") + 1));
|
|
1633
|
+
} catch {
|
|
1634
|
+
return void 0;
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1281
1637
|
var get = async (context2, key) => {
|
|
1282
1638
|
const said = await context2.run(oc(context2), ["config", "get", key], { timeoutMs: 3e4 });
|
|
1283
1639
|
const value = said.stdout.trim().split("\n").pop()?.trim();
|
|
@@ -1289,9 +1645,10 @@ var entry = (model2) => ({
|
|
|
1289
1645
|
...model2.input.includes("image") ? { input: ["text", "image"] } : {},
|
|
1290
1646
|
contextWindow: CONVERSATION_TOKENS,
|
|
1291
1647
|
...model2.output > 0 ? { maxTokens: model2.output } : {},
|
|
1292
|
-
...model2.reasoning === null ? {} : {
|
|
1293
|
-
|
|
1294
|
-
|
|
1648
|
+
...model2.reasoning === null ? {} : { reasoning: true },
|
|
1649
|
+
compat: {
|
|
1650
|
+
supportsPromptCacheKey: true,
|
|
1651
|
+
...model2.reasoning === null ? {} : {
|
|
1295
1652
|
supportsReasoningEffort: true,
|
|
1296
1653
|
supportedReasoningEfforts: [...model2.reasoning.offWire === null ? [] : [model2.reasoning.offWire], ...model2.reasoning.levels]
|
|
1297
1654
|
}
|
|
@@ -1302,21 +1659,37 @@ var openclaw = {
|
|
|
1302
1659
|
id: "openclaw",
|
|
1303
1660
|
name: "OpenClaw",
|
|
1304
1661
|
install: "npm install -g openclaw",
|
|
1305
|
-
|
|
1662
|
+
// 2026.11.3 refuses to install on an older one (>=24.16.0 <25 || >=26.1.0).
|
|
1663
|
+
installNeedsNode: "24.16.0",
|
|
1664
|
+
does: "Added AdaptiqAI and its plugin to OpenClaw, made it the default model, and turned off its automatic compaction and, where the plugin needs it, tool search, for every model. Your other providers stay.",
|
|
1306
1665
|
signsInAs: async () => "OpenClaw",
|
|
1307
1666
|
found: async (context2) => command(context2, "openclaw", [join12(context2.home, ".openclaw", "bin", "openclaw")]) !== void 0,
|
|
1308
1667
|
async prepare(context2) {
|
|
1309
1668
|
const done = await context2.run(oc(context2), ["config", "file"], { timeoutMs: 3e4 });
|
|
1310
1669
|
if (done.code !== 0) throw new Problem("app_refused", `OpenClaw would not start: ${done.stderr.trim().split("\n").pop() ?? ""}`);
|
|
1670
|
+
const inspected = await context2.run(oc(context2), ["plugins", "inspect", "adaptiq"], { timeoutMs: 6e4 });
|
|
1671
|
+
const had = inspected.code === 0 ? /^Version:\s*(\S+)/mu.exec(inspected.stdout)?.[1] ?? "?" : "";
|
|
1672
|
+
const npm = command(context2, "npm");
|
|
1673
|
+
const seen = npm === void 0 ? void 0 : await context2.run(npm, ["view", PLUGIN3, "version"], { timeoutMs: 3e4 }).catch(() => void 0);
|
|
1674
|
+
const latest = seen?.code === 0 ? seen.stdout.trim().split("\n").pop()?.trim() ?? "" : "";
|
|
1675
|
+
if (had !== "" && (!/^\d+\.\d+\.\d+/u.test(latest) || latest === had)) return;
|
|
1676
|
+
const added = await context2.run(oc(context2), ["plugins", "install", PLUGIN3, ...PLUGIN_FLAGS], { timeoutMs: 18e4 });
|
|
1677
|
+
if (added.code !== 0 && had === "") {
|
|
1678
|
+
throw new Problem("tools_not_installed", `AdaptiqAI's plugin for OpenClaw could not be installed, so OpenClaw was left as it was. Check the connection, then run this again. (${added.stderr.trim().split("\n").pop() ?? ""})`);
|
|
1679
|
+
}
|
|
1311
1680
|
},
|
|
1312
1681
|
async current(context2) {
|
|
1313
1682
|
const base = await get(context2, "models.providers.adaptiq.baseUrl");
|
|
1314
1683
|
if (base === void 0) return {};
|
|
1315
1684
|
const primary = await get(context2, "agents.defaults.model.primary");
|
|
1316
|
-
|
|
1685
|
+
const key = await envKey(context2);
|
|
1686
|
+
return {
|
|
1687
|
+
base,
|
|
1688
|
+
...primary?.startsWith("adaptiq/") === true ? { model: primary.slice("adaptiq/".length) } : {},
|
|
1689
|
+
...key === void 0 ? {} : { key }
|
|
1690
|
+
};
|
|
1317
1691
|
},
|
|
1318
1692
|
async apply(context2, facts) {
|
|
1319
|
-
const notes = [];
|
|
1320
1693
|
const was = await get(context2, "agents.defaults.model.primary");
|
|
1321
1694
|
const compaction = await get(context2, "agents.defaults.compaction.enabled");
|
|
1322
1695
|
await remember(context2, openclaw, { primary: was?.startsWith("adaptiq/") === true ? null : was ?? null, compaction: compaction ?? null });
|
|
@@ -1324,27 +1697,38 @@ var openclaw = {
|
|
|
1324
1697
|
models: { providers: { adaptiq: { baseUrl: apiBase(context2), api: "openai-completions", models: facts.models.map(entry) } } },
|
|
1325
1698
|
agents: { defaults: { model: { primary: `adaptiq/${facts.models[0].id}` } } }
|
|
1326
1699
|
};
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1700
|
+
if (was !== patch.agents.defaults.model.primary || !covers(await getJson(context2, "models.providers.adaptiq"), patch.models.providers.adaptiq)) {
|
|
1701
|
+
const patched = await context2.run(oc(context2), ["config", "patch", "--stdin", "--replace-path", "models.providers.adaptiq.models"], { input: JSON.stringify(patch), timeoutMs: 6e4 });
|
|
1702
|
+
if (patched.code !== 0) throw new Problem("app_refused", `OpenClaw refused the setup: ${patched.stderr.trim().split("\n").pop() ?? ""}`);
|
|
1703
|
+
}
|
|
1704
|
+
if (facts.key !== void 0 && facts.key !== await envKey(context2)) {
|
|
1330
1705
|
const pasted = await context2.run(oc(context2), ["models", "auth", "paste-api-key", "--provider", "adaptiq"], { input: facts.key, timeoutMs: 6e4 });
|
|
1331
1706
|
if (pasted.code !== 0) throw new Problem("app_refused", `OpenClaw refused the key: ${pasted.stderr.trim().split("\n").pop() ?? ""}`);
|
|
1332
1707
|
}
|
|
1333
1708
|
await context2.run(oc(context2), ["config", "set", "agents.defaults.compaction.enabled", "false"], { timeoutMs: 3e4 });
|
|
1334
|
-
const
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
if (
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1709
|
+
const inspected = await context2.run(oc(context2), ["plugins", "inspect", "adaptiq"], { timeoutMs: 6e4 });
|
|
1710
|
+
const where = /^Install path:\s*(.+)$/mu.exec(inspected.stdout)?.[1]?.trim().replace(/^~\//u, `${context2.home}/`);
|
|
1711
|
+
if (where !== void 0 && await directOnly(join12(where, "lib"))) {
|
|
1712
|
+
if (await get(context2, "tools.toolSearch") !== void 0) await context2.run(oc(context2), ["config", "unset", "tools.toolSearch"], { timeoutMs: 3e4 });
|
|
1713
|
+
} else {
|
|
1714
|
+
await context2.run(oc(context2), ["config", "set", "tools.toolSearch", "false"], { timeoutMs: 3e4 });
|
|
1715
|
+
}
|
|
1716
|
+
if (facts.key !== void 0) await writeEnv(context2, facts.key);
|
|
1717
|
+
await logout(context2, "adaptiq:device");
|
|
1718
|
+
await forgetGrant(context2, join12(state(context2), "adaptiqai"));
|
|
1719
|
+
if (await gatewayUp(context2)) {
|
|
1720
|
+
if (!await context2.confirm("Restart OpenClaw's Gateway now, so it uses AdaptiqAI? Answers in progress stop.")) return ["OpenClaw uses AdaptiqAI once its Gateway restarts: openclaw gateway restart"];
|
|
1721
|
+
const restarted = await context2.run(oc(context2), ["gateway", "restart"], { timeoutMs: 6e4 });
|
|
1722
|
+
if (restarted.code !== 0) {
|
|
1723
|
+
const said = `${restarted.stderr}
|
|
1724
|
+
${restarted.stdout}`.trim().split("\n").pop() ?? "";
|
|
1725
|
+
return [`OpenClaw's Gateway did not restart${said === "" ? "" : ` (${said})`}. It uses AdaptiqAI once it restarts: openclaw gateway restart`];
|
|
1341
1726
|
}
|
|
1342
1727
|
}
|
|
1343
|
-
return
|
|
1728
|
+
return [];
|
|
1344
1729
|
},
|
|
1345
1730
|
async remove(context2) {
|
|
1346
1731
|
const had = await recall(context2, openclaw);
|
|
1347
|
-
await context2.run(oc(context2), ["models", "auth", "logout", "adaptiq:manual", "--yes"], { timeoutMs: 3e4 });
|
|
1348
1732
|
await updateJson(join12(context2.env.OPENCLAW_STATE_DIR ?? join12(context2.home, ".openclaw"), "openclaw.json"), (value) => {
|
|
1349
1733
|
delete record(record(value.models).providers).adaptiq;
|
|
1350
1734
|
const defaults = record(record(value.agents).defaults);
|
|
@@ -1356,9 +1740,34 @@ var openclaw = {
|
|
|
1356
1740
|
if (had.compaction === null) delete record(defaults.compaction).enabled;
|
|
1357
1741
|
else if (had.compaction === "true") record(defaults.compaction).enabled = true;
|
|
1358
1742
|
}, context2.backups);
|
|
1743
|
+
await writeEnv(context2);
|
|
1359
1744
|
await forget(context2, openclaw);
|
|
1360
1745
|
},
|
|
1361
|
-
|
|
1746
|
+
async clean(context2) {
|
|
1747
|
+
const removed = [];
|
|
1748
|
+
if (await writeEnv(context2)) removed.push(envFile2(context2));
|
|
1749
|
+
const grant = await forgetGrant(context2, join12(state(context2), "adaptiqai"));
|
|
1750
|
+
if (grant !== void 0) removed.push(grant);
|
|
1751
|
+
const here = await openclaw.found(context2);
|
|
1752
|
+
if (here) for (const profile2 of ["adaptiq:manual", "adaptiq:device"]) await logout(context2, profile2);
|
|
1753
|
+
const all = await databases(context2);
|
|
1754
|
+
const every = all.flatMap((database) => database.files);
|
|
1755
|
+
const held = await holding(every);
|
|
1756
|
+
let up = here && held.length > 0 && await gatewayUp(context2);
|
|
1757
|
+
if (up && await context2.confirm("OpenClaw's database keeps the old key until its Gateway closes it. Restart its Gateway now? Answers in progress stop.") && (await context2.run(oc(context2), ["gateway", "restart"], { timeoutMs: 6e4 })).code === 0) up = await gatewayUp(context2);
|
|
1758
|
+
for (const { files, compact } of all) {
|
|
1759
|
+
if (here && !up && (await holding(files)).length > 0) await context2.run(oc(context2), compact, { timeoutMs: 6e5 });
|
|
1760
|
+
}
|
|
1761
|
+
const still = await holding(every);
|
|
1762
|
+
removed.push(...held.filter((file3) => !still.includes(file3)));
|
|
1763
|
+
const left = still.length === 0 ? [] : [here ? `OpenClaw's own database still holds the AdaptiqAI key (${still[0]}). Quit OpenClaw and stop its Gateway (openclaw gateway stop), then run this again: npx -y adaptiqai disconnect openclaw` : `OpenClaw's own database still holds the AdaptiqAI key (${still[0]}), and OpenClaw is not here to clear it. If you no longer use OpenClaw, delete its folder, ${state(context2)}.`];
|
|
1764
|
+
return { removed, left };
|
|
1765
|
+
},
|
|
1766
|
+
// The Mac app shows the dashboard in its own window, on the Gateway it runs itself; without it, the browser does.
|
|
1767
|
+
start: async (context2) => {
|
|
1768
|
+
const dashboard = `${/\s/u.test(oc(context2)) ? `'${oc(context2)}'` : oc(context2)} dashboard`;
|
|
1769
|
+
return context2.platform === "darwin" ? `open -b ai.openclaw.mac openclaw://dashboard 2>/dev/null || ${dashboard}` : dashboard;
|
|
1770
|
+
},
|
|
1362
1771
|
async ask(context2) {
|
|
1363
1772
|
const local = await gatewayUp(context2) ? [] : ["--local"];
|
|
1364
1773
|
return { command: oc(context2), args: ["agent", ...local, "--agent", "main", "--json", "--timeout", "120", "--message", QUESTION] };
|
|
@@ -1367,17 +1776,17 @@ var openclaw = {
|
|
|
1367
1776
|
|
|
1368
1777
|
// src/apps/pi.ts
|
|
1369
1778
|
import { join as join13 } from "node:path";
|
|
1370
|
-
var
|
|
1779
|
+
var PACKAGE2 = "npm:@adaptiqai/pi";
|
|
1371
1780
|
var PROVIDER2 = "adaptiqai";
|
|
1372
1781
|
var CONSOLE_PROVIDER = "adaptiq";
|
|
1373
|
-
var
|
|
1374
|
-
var
|
|
1782
|
+
var dir = (context2) => context2.env.PI_CODING_AGENT_DIR ?? join13(context2.home, ".pi", "agent");
|
|
1783
|
+
var file2 = (context2, name2) => join13(dir(context2), name2);
|
|
1375
1784
|
var pi = (context2) => command(context2, "pi") ?? "pi";
|
|
1376
1785
|
async function hasPackage(context2) {
|
|
1377
|
-
const { packages } = await readJson(
|
|
1786
|
+
const { packages } = await readJson(file2(context2, "settings.json"));
|
|
1378
1787
|
return Array.isArray(packages) && packages.some((entry2) => {
|
|
1379
1788
|
const source = typeof entry2 === "string" ? entry2 : record(entry2).source;
|
|
1380
|
-
return typeof source === "string" && source.startsWith(
|
|
1789
|
+
return typeof source === "string" && source.startsWith(PACKAGE2);
|
|
1381
1790
|
});
|
|
1382
1791
|
}
|
|
1383
1792
|
var piApp = {
|
|
@@ -1388,53 +1797,73 @@ var piApp = {
|
|
|
1388
1797
|
signsInAs: async () => "Pi",
|
|
1389
1798
|
found: async (context2) => command(context2, "pi") !== void 0,
|
|
1390
1799
|
async prepare(context2) {
|
|
1391
|
-
await Promise.all(["auth.json", "models.json", "settings.json"].map((name2) => readJson(
|
|
1800
|
+
await Promise.all(["auth.json", "models.json", "settings.json"].map((name2) => readJson(file2(context2, name2))));
|
|
1801
|
+
const done = await context2.run(pi(context2), ["install", PACKAGE2], { timeoutMs: 18e4 });
|
|
1802
|
+
if (done.code !== 0 && !await hasPackage(context2)) {
|
|
1803
|
+
throw new Problem("plugin_failed", `Pi could not install AdaptiqAI's package, so it was left as it was. Check the connection, then run this again: ${done.stderr.trim().split("\n").pop() ?? ""}`);
|
|
1804
|
+
}
|
|
1392
1805
|
},
|
|
1393
1806
|
async current(context2) {
|
|
1394
|
-
const { key } = record((await readJson(
|
|
1807
|
+
const { key } = record((await readJson(file2(context2, "auth.json")))[PROVIDER2]);
|
|
1395
1808
|
return typeof key === "string" ? { base: apiBase(context2), key } : {};
|
|
1396
1809
|
},
|
|
1397
1810
|
async apply(context2, facts) {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
if (done.code !== 0) throw new Problem("plugin_failed", `Pi could not install AdaptiqAI's package, so it was left as it was: ${done.stderr.trim().split("\n").pop() ?? ""}`);
|
|
1401
|
-
}
|
|
1402
|
-
const settings = await readJson(file3(context2, "settings.json"));
|
|
1811
|
+
await forgetGrant(context2, file2(context2, "adaptiqai"));
|
|
1812
|
+
const settings = await readJson(file2(context2, "settings.json"));
|
|
1403
1813
|
await remember(context2, piApp, { compaction: settings.compaction ?? null });
|
|
1404
|
-
await updateJson(
|
|
1814
|
+
await updateJson(file2(context2, "auth.json"), (value) => {
|
|
1405
1815
|
value[PROVIDER2] = { type: "api_key", key: facts.key };
|
|
1406
1816
|
if (record(value[CONSOLE_PROVIDER]).type === "api_key") delete value[CONSOLE_PROVIDER];
|
|
1407
1817
|
}, context2.backups, { secret: true });
|
|
1408
|
-
await updateJson(
|
|
1818
|
+
await updateJson(file2(context2, "models.json"), (value) => {
|
|
1409
1819
|
const all = record(value.providers);
|
|
1410
1820
|
if (all[CONSOLE_PROVIDER] === void 0) return;
|
|
1411
1821
|
delete all[CONSOLE_PROVIDER];
|
|
1412
1822
|
value.providers = all;
|
|
1413
1823
|
}, context2.backups, { secret: true });
|
|
1414
|
-
await updateJson(
|
|
1824
|
+
await updateJson(file2(context2, "settings.json"), (value) => {
|
|
1415
1825
|
value.compaction = { ...record(value.compaction), enabled: false };
|
|
1826
|
+
if (value.defaultProvider === void 0 || value.defaultProvider === CONSOLE_PROVIDER) {
|
|
1827
|
+
value.defaultProvider = PROVIDER2;
|
|
1828
|
+
value.defaultModel = facts.models[0].id;
|
|
1829
|
+
}
|
|
1416
1830
|
}, context2.backups, { secret: true });
|
|
1417
1831
|
return [];
|
|
1418
1832
|
},
|
|
1419
1833
|
async remove(context2) {
|
|
1420
1834
|
const had = await recall(context2, piApp);
|
|
1421
|
-
if (await hasPackage(context2)) await context2.run(pi(context2), ["remove",
|
|
1422
|
-
await updateJson(
|
|
1835
|
+
if (await hasPackage(context2)) await context2.run(pi(context2), ["remove", PACKAGE2], { timeoutMs: 6e4 });
|
|
1836
|
+
await updateJson(file2(context2, "auth.json"), (value) => {
|
|
1423
1837
|
delete value[PROVIDER2];
|
|
1424
1838
|
}, context2.backups, { secret: true });
|
|
1425
|
-
|
|
1426
|
-
|
|
1839
|
+
await updateJson(file2(context2, "settings.json"), (value) => {
|
|
1840
|
+
if ("compaction" in had) {
|
|
1427
1841
|
if (had.compaction === null) delete value.compaction;
|
|
1428
1842
|
else value.compaction = had.compaction;
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1843
|
+
}
|
|
1844
|
+
if (value.defaultProvider === PROVIDER2) {
|
|
1845
|
+
delete value.defaultProvider;
|
|
1846
|
+
delete value.defaultModel;
|
|
1847
|
+
}
|
|
1848
|
+
}, context2.backups, { secret: true });
|
|
1431
1849
|
await forget(context2, piApp);
|
|
1432
1850
|
},
|
|
1433
|
-
|
|
1851
|
+
async clean(context2) {
|
|
1852
|
+
const removed = [];
|
|
1853
|
+
const auth = file2(context2, "auth.json");
|
|
1854
|
+
if (await updateJson(auth, (value) => {
|
|
1855
|
+
delete value[PROVIDER2];
|
|
1856
|
+
}, context2.backups, { secret: true })) removed.push(auth);
|
|
1857
|
+
const grant = await forgetGrant(context2, file2(context2, "adaptiqai"));
|
|
1858
|
+
if (grant !== void 0) removed.push(grant);
|
|
1859
|
+
return { removed, left: [] };
|
|
1860
|
+
},
|
|
1861
|
+
start: async (_context, model2) => `pi --provider ${PROVIDER2} --model ${model2}`,
|
|
1862
|
+
greet: async (context2, model2, hello2) => context2.platform === "win32" ? void 0 : `pi --provider ${PROVIDER2} --model ${model2} ${shellWord(hello2)}`
|
|
1434
1863
|
};
|
|
1435
1864
|
|
|
1436
1865
|
// src/apps/index.ts
|
|
1437
|
-
var APPS = [
|
|
1866
|
+
var APPS = [deepseek, codex, claudeCode, opencode, kilo, piApp, hermesApp, openclaw];
|
|
1438
1867
|
function appNamed(word) {
|
|
1439
1868
|
const wanted = word.trim().toLowerCase();
|
|
1440
1869
|
return APPS.find((app) => app.id === wanted || app.aliases?.includes(wanted) === true);
|
|
@@ -1442,7 +1871,9 @@ function appNamed(word) {
|
|
|
1442
1871
|
|
|
1443
1872
|
// src/commands.ts
|
|
1444
1873
|
import { spawn as spawn2 } from "node:child_process";
|
|
1445
|
-
import {
|
|
1874
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
1875
|
+
import { access as access3, constants as constants4 } from "node:fs/promises";
|
|
1876
|
+
import { delimiter as delimiter3, dirname as dirname4, join as join15 } from "node:path";
|
|
1446
1877
|
|
|
1447
1878
|
// ../core/lib/errors.js
|
|
1448
1879
|
var CODES = {
|
|
@@ -1466,6 +1897,13 @@ var CODES = {
|
|
|
1466
1897
|
};
|
|
1467
1898
|
var RETRYABLE_CODES = [CODES.empty, CODES.busy, CODES.server, CODES.timeout, CODES.transport, CODES.preparing];
|
|
1468
1899
|
|
|
1900
|
+
// ../core/lib/tool-failures.js
|
|
1901
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1902
|
+
var PASSING = [CODES.server, CODES.timeout, CODES.transport, CODES.busy, CODES.preparing, CODES.empty, CODES.conversationUnavailable];
|
|
1903
|
+
var inCall = new AsyncLocalStorage();
|
|
1904
|
+
var UNKNOWN_TURN_MS = 2 * 6e4;
|
|
1905
|
+
var KEPT_MS = 10 * 6e4;
|
|
1906
|
+
|
|
1469
1907
|
// ../core/lib/auth.js
|
|
1470
1908
|
var REFRESH_MARGIN_MS = 2 * 6e4;
|
|
1471
1909
|
var DEVICE_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -1570,13 +2008,13 @@ async function pollDeviceCode(client2, code, extra = {}) {
|
|
|
1570
2008
|
return { state: "failed", message: detailMessage(await readJson2(response)) ?? `Sign-in failed (HTTP ${response.status}).` };
|
|
1571
2009
|
}
|
|
1572
2010
|
const body = await readJson2(response);
|
|
1573
|
-
const
|
|
2011
|
+
const access4 = String(body?.access_token ?? "");
|
|
1574
2012
|
const refresh = String(body?.refresh_token ?? "");
|
|
1575
|
-
if (!
|
|
2013
|
+
if (!access4.startsWith("ak_") || refresh !== "" && !refresh.startsWith("akr_"))
|
|
1576
2014
|
return { state: "failed", message: "AdaptiqAI returned an invalid credential." };
|
|
1577
2015
|
return {
|
|
1578
2016
|
state: "approved",
|
|
1579
|
-
access:
|
|
2017
|
+
access: access4,
|
|
1580
2018
|
refresh: refresh === "" ? null : refresh,
|
|
1581
2019
|
expiresIn: Math.max(60, Number(body?.expires_in ?? 3600)),
|
|
1582
2020
|
workspaceLabel: typeof body?.workspace_label === "string" && body.workspace_label !== "" ? body.workspace_label : "Your workspace"
|
|
@@ -1810,6 +2248,7 @@ var FOLLOW_MS = 3 * 36e5;
|
|
|
1810
2248
|
|
|
1811
2249
|
// ../core/lib/update.js
|
|
1812
2250
|
var RELEASE_AGE_MS = 24 * 36e5;
|
|
2251
|
+
var OFFER_AGE_MS = 48 * 36e5;
|
|
1813
2252
|
|
|
1814
2253
|
// ../core/lib/workspace.js
|
|
1815
2254
|
var UNDO_WINDOW_MS = 15 * 6e4;
|
|
@@ -1820,13 +2259,51 @@ var ATTACH_BACKOFF_MAX_MS = 10 * 6e4;
|
|
|
1820
2259
|
var ATTACH_DEADLINE_MS = 6 * 36e5;
|
|
1821
2260
|
var SESSION_BACKOFF_MAX_MS = 5 * 6e4;
|
|
1822
2261
|
|
|
2262
|
+
// ../core/lib/commands.js
|
|
2263
|
+
var ACTIONS = [
|
|
2264
|
+
["", "workspace, models and memory"],
|
|
2265
|
+
["signin", "connect your AdaptiqAI account in the browser"],
|
|
2266
|
+
["private", "stop saving new messages of this conversation"],
|
|
2267
|
+
["remember", "save this conversation to memory again"],
|
|
2268
|
+
["knowledge", "knowledge used in this conversation, and what else you can use"],
|
|
2269
|
+
["use <name>", "use a collection or an earlier conversation here"],
|
|
2270
|
+
["stop <name>", "stop using it here (nothing is deleted)"],
|
|
2271
|
+
["open", "open this conversation in AdaptiqAI Knowledge"],
|
|
2272
|
+
["diagnostics", "a report you can send to support (no secrets)"],
|
|
2273
|
+
["signout", "forget the connection on this computer"]
|
|
2274
|
+
];
|
|
2275
|
+
var COMMAND_HINT = ["status", ...ACTIONS.slice(1).map(([action]) => action.split(" ")[0])].join(" \xB7 ");
|
|
2276
|
+
|
|
2277
|
+
// ../core/lib/preparing.js
|
|
2278
|
+
var PREPARING_MOST_MS = 15 * 6e4;
|
|
2279
|
+
|
|
2280
|
+
// ../core/lib/reading.js
|
|
2281
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
2282
|
+
|
|
2283
|
+
// ../core/lib/series.js
|
|
2284
|
+
var MONTHS = [
|
|
2285
|
+
"januar februar marts april maj juni juli august september oktober november december",
|
|
2286
|
+
"january february march april may june july august september october november december",
|
|
2287
|
+
"jan feb mar apr maj jun jul aug sep okt nov dec",
|
|
2288
|
+
"jan feb mar apr may jun jul aug sep oct nov dec"
|
|
2289
|
+
].map((words) => words.split(" "));
|
|
2290
|
+
var TOKEN = new RegExp(`(\\d+|(?<!\\p{L})(?:${[...new Set(MONTHS.flat())].sort((a, b) => b.length - a.length).join("|")})(?!\\p{L}))`, "iu");
|
|
2291
|
+
|
|
1823
2292
|
// ../core/lib/reading.js
|
|
1824
2293
|
var READ_PER_REPLY = 13e4;
|
|
1825
|
-
var READING_NOTE = `A collection used here is in your memory, but a large one is read in parts: each reply reads what matches the conversation (about ${formatTokens(READ_PER_REPLY)} tokens of it), never all of it at once.
|
|
2294
|
+
var READING_NOTE = `A collection used here is in your memory, but a large one is read in parts: each reply reads what matches the conversation (about ${formatTokens(READ_PER_REPLY)} tokens of it), never all of it at once. contents lists its documents; open shows a document's exact lines (around a line, if given; more: true reads on). Before you say something is not there, find it; for an overview, take contents first. Answer from what these actions return, citing document:line as a result shows it, and never name a document or a line no result showed.`;
|
|
2295
|
+
var READS_IDLE_MS = 10 * 6e4;
|
|
2296
|
+
var ORIGINAL_BYTES = 16 * 1024 * 1024;
|
|
2297
|
+
var KNOWN_CHARS = 32 * 1024 * 1024;
|
|
2298
|
+
var calls = new AsyncLocalStorage2();
|
|
1826
2299
|
|
|
1827
2300
|
// ../core/lib/tools.js
|
|
1828
2301
|
var SOURCE_LIMIT = 6 * 1024 * 1024;
|
|
1829
2302
|
|
|
2303
|
+
// ../core/lib/turns.js
|
|
2304
|
+
var OFFER = new RegExp("(?<!\\p{L})(?:knowledge|collection|ncm|viden|samling)|\u77E5\u8BC6|\u96C6\u5408", "iu");
|
|
2305
|
+
var OFFER_DOES = new RegExp("(?<!\\p{L})(?:save|make|create|add|put|keep|store|turn|remember|gem|gemme|lav|lave|opret|oprette|tilf\xF8j|tilf\xF8je|l\xE6g|l\xE6gge)(?!\\p{L})|\u4FDD\u5B58|\u521B\u5EFA|\u505A\u6210|\u653E\u8FDB|\u52A0\u5165|\u5B58", "iu");
|
|
2306
|
+
|
|
1830
2307
|
// src/gateway.ts
|
|
1831
2308
|
async function call(context2, path, options = {}) {
|
|
1832
2309
|
let response;
|
|
@@ -1849,6 +2326,7 @@ async function call(context2, path, options = {}) {
|
|
|
1849
2326
|
return { status: response.status, body, headers: response.headers };
|
|
1850
2327
|
}
|
|
1851
2328
|
var SAFE_MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$/u;
|
|
2329
|
+
var namedModels = (ids) => models(ids.map((id) => ({ id })));
|
|
1852
2330
|
var models = (rows) => buildCatalog(rows, { kind: "unknown" }).filter((row) => SAFE_MODEL_ID.test(row.id)).map((row) => ({
|
|
1853
2331
|
id: row.id,
|
|
1854
2332
|
name: row.name,
|
|
@@ -1932,7 +2410,7 @@ async function testRequest(context2, key, model2) {
|
|
|
1932
2410
|
}
|
|
1933
2411
|
|
|
1934
2412
|
// src/signin.ts
|
|
1935
|
-
import { randomBytes
|
|
2413
|
+
import { randomBytes, createHash as createHash2 } from "node:crypto";
|
|
1936
2414
|
import { chmod as chmod5, mkdir as mkdir7, rm as rm4 } from "node:fs/promises";
|
|
1937
2415
|
import { join as join14 } from "node:path";
|
|
1938
2416
|
var pendingFile = (context2, app) => join14(cliDir(context2), `sign-in-${app.id}.json`);
|
|
@@ -1951,12 +2429,12 @@ var forgetSignIn = async (context2, app) => {
|
|
|
1951
2429
|
await rm4(pendingFile(context2, app), { force: true });
|
|
1952
2430
|
};
|
|
1953
2431
|
async function installationId(context2) {
|
|
1954
|
-
const
|
|
1955
|
-
const saved = (await readText(
|
|
2432
|
+
const file3 = join14(configDir(context2.env, context2.home), "installation");
|
|
2433
|
+
const saved = (await readText(file3))?.replace(/[^a-f0-9]/gu, "").slice(0, 32);
|
|
1956
2434
|
if (saved?.length === 32) return saved;
|
|
1957
|
-
const made =
|
|
2435
|
+
const made = randomBytes(16).toString("hex");
|
|
1958
2436
|
await mkdir7(configDir(context2.env, context2.home), { recursive: true, mode: 448 });
|
|
1959
|
-
await writeWhole(
|
|
2437
|
+
await writeWhole(file3, `${made}
|
|
1960
2438
|
`, null, { secret: true });
|
|
1961
2439
|
return made;
|
|
1962
2440
|
}
|
|
@@ -1968,7 +2446,7 @@ async function start(context2, app) {
|
|
|
1968
2446
|
app: await app.signsInAs(context2),
|
|
1969
2447
|
device: deviceName(),
|
|
1970
2448
|
// Nothing needs this again: the key an app stores lasts by itself and is never refreshed.
|
|
1971
|
-
binding: createHash2("sha256").update(
|
|
2449
|
+
binding: createHash2("sha256").update(randomBytes(32)).digest("hex"),
|
|
1972
2450
|
tokenLifetime: "long"
|
|
1973
2451
|
});
|
|
1974
2452
|
} catch (error) {
|
|
@@ -1988,7 +2466,7 @@ function tell(context2, app, pending) {
|
|
|
1988
2466
|
url: pending.url,
|
|
1989
2467
|
code: pending.userCode,
|
|
1990
2468
|
expires_in: Math.max(0, Math.round((pending.expiresAt - context2.now()) / 1e3)),
|
|
1991
|
-
say: "Open this link, sign in or create an account, check that the page shows the same code, and press Connect. Then add
|
|
2469
|
+
say: "Open this link, sign in or create an account, check that the page shows the same code, and press Connect. Then add funds."
|
|
1992
2470
|
}, [
|
|
1993
2471
|
...elsewhere === void 0 ? [] : ["", `Signing in at ${elsewhere}, set by ADAPTIQAI_API_BASE, not at AdaptiqAI's own address.`],
|
|
1994
2472
|
"",
|
|
@@ -2000,13 +2478,14 @@ function tell(context2, app, pending) {
|
|
|
2000
2478
|
]);
|
|
2001
2479
|
}
|
|
2002
2480
|
async function signIn(context2, app, options) {
|
|
2003
|
-
|
|
2481
|
+
const given = options.code === void 0 ? void 0 : { deviceCode: options.code, userCode: "", url: "", expiresAt: context2.now() + 6e5, interval: 2e3, app: app.id, origin: context2.origin };
|
|
2482
|
+
let pending = given ?? await pendingSignIn(context2, app);
|
|
2004
2483
|
const resumed = pending !== void 0;
|
|
2005
2484
|
if (pending === void 0) {
|
|
2006
2485
|
pending = await start(context2, app);
|
|
2007
2486
|
await options.open(pending.url);
|
|
2008
2487
|
}
|
|
2009
|
-
tell(context2, app, pending);
|
|
2488
|
+
if (given === void 0) tell(context2, app, pending);
|
|
2010
2489
|
if (!resumed && options.waitMs === 0) return { state: "waiting" };
|
|
2011
2490
|
const extra = {
|
|
2012
2491
|
client_app: await app.signsInAs(context2),
|
|
@@ -2014,17 +2493,24 @@ async function signIn(context2, app, options) {
|
|
|
2014
2493
|
model: options.model,
|
|
2015
2494
|
installation: await installationId(context2)
|
|
2016
2495
|
};
|
|
2017
|
-
const until = context2.now() + options.waitMs;
|
|
2018
|
-
context2.say.note("Waiting for you in the browser...");
|
|
2496
|
+
const until = context2.now() + (given === void 0 ? options.waitMs : Math.max(options.waitMs, 6e4));
|
|
2497
|
+
if (given === void 0) context2.say.note("Waiting for you in the browser...");
|
|
2019
2498
|
for (; ; ) {
|
|
2020
2499
|
const answer = await pollDeviceCode(client(context2), pending, extra);
|
|
2021
2500
|
if (answer.state === "approved") return { state: "approved", key: answer.access, workspace: answer.workspaceLabel, deviceCode: pending.deviceCode };
|
|
2022
2501
|
if (answer.state === "waiting") {
|
|
2023
|
-
if (context2.now() + answer.delayMs > until)
|
|
2502
|
+
if (context2.now() + answer.delayMs > until) {
|
|
2503
|
+
if (given !== void 0) throw new Problem("sign_in_failed", "AdaptiqAI did not answer for the code from the console. Check the connection, then run the command again.");
|
|
2504
|
+
return { state: "waiting" };
|
|
2505
|
+
}
|
|
2024
2506
|
await context2.sleep(answer.delayMs);
|
|
2025
2507
|
continue;
|
|
2026
2508
|
}
|
|
2027
|
-
|
|
2509
|
+
if (given !== void 0 && answer.state === "expired") {
|
|
2510
|
+
context2.say.note("The code from the console has expired, or was used. Signing in in the browser instead.");
|
|
2511
|
+
return await signIn(context2, app, { model: options.model, waitMs: options.waitMs, open: options.open });
|
|
2512
|
+
}
|
|
2513
|
+
if (given === void 0) await forgetSignIn(context2, app);
|
|
2028
2514
|
if (answer.state === "denied") throw new Problem("declined", answer.message);
|
|
2029
2515
|
if (answer.state === "expired" && resumed) return await signIn(context2, app, options);
|
|
2030
2516
|
throw new Problem(answer.state === "expired" ? "sign_in_expired" : "sign_in_failed", answer.message);
|
|
@@ -2034,13 +2520,14 @@ async function signIn(context2, app, options) {
|
|
|
2034
2520
|
// src/commands.ts
|
|
2035
2521
|
var EXIT = { connected: 0, failed: 1, usage: 2, waitingForSignIn: 3, waitingForFunds: 4 };
|
|
2036
2522
|
var FUNDS_POLL_MS = 15e3;
|
|
2523
|
+
var hello = (name2) => `Hi${name2 === void 0 || name2 === "" || name2 === "Your workspace" ? "" : `, I'm ${name2}`}. Briefly confirm that the connection to AdaptiqAI works, and ask what we should work on.`;
|
|
2037
2524
|
async function openLink(context2, url) {
|
|
2038
2525
|
const { env, platform } = context2;
|
|
2039
2526
|
if (env.SSH_CONNECTION !== void 0 || env.CI !== void 0 || !safeLink(url)) return;
|
|
2040
2527
|
if (platform !== "darwin" && platform !== "win32" && env.DISPLAY === void 0 && env.WAYLAND_DISPLAY === void 0) return;
|
|
2041
|
-
const [
|
|
2528
|
+
const [file3, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["rundll32", ["url.dll,FileProtocolHandler", new URL(url).href]] : ["xdg-open", [url]];
|
|
2042
2529
|
await new Promise((resolve) => {
|
|
2043
|
-
const child = spawn2(
|
|
2530
|
+
const child = spawn2(file3, [...args], { stdio: "ignore", detached: true, env });
|
|
2044
2531
|
child.on("error", () => resolve());
|
|
2045
2532
|
child.on("spawn", () => {
|
|
2046
2533
|
child.unref();
|
|
@@ -2048,6 +2535,75 @@ async function openLink(context2, url) {
|
|
|
2048
2535
|
});
|
|
2049
2536
|
});
|
|
2050
2537
|
}
|
|
2538
|
+
var APP_POLL_MS = 3e3;
|
|
2539
|
+
function installer(context2, app) {
|
|
2540
|
+
if (app.install.startsWith("npm install -g ")) return command(context2, "npm") === void 0 ? void 0 : app.install;
|
|
2541
|
+
return context2.platform !== "win32" && app.install.startsWith("curl ") ? app.install : void 0;
|
|
2542
|
+
}
|
|
2543
|
+
async function npmUserPrefix(context2) {
|
|
2544
|
+
const npm = command(context2, "npm");
|
|
2545
|
+
if (npm === void 0 || context2.platform === "win32") return void 0;
|
|
2546
|
+
const prefix = (await context2.run(npm, ["prefix", "-g"], { timeoutMs: 3e4 })).stdout.trim().split("\n").pop() ?? "";
|
|
2547
|
+
if (prefix === "") return void 0;
|
|
2548
|
+
const writable = async (path) => await access3(path, constants4.W_OK).then(() => true, () => false);
|
|
2549
|
+
const modules = join15(prefix, "lib", "node_modules");
|
|
2550
|
+
if (await writable(modules) || !existsSync7(modules) && await writable(prefix)) return void 0;
|
|
2551
|
+
return join15(context2.home, ".local");
|
|
2552
|
+
}
|
|
2553
|
+
async function waitForApp(context2, app, page, options) {
|
|
2554
|
+
context2.say.note(`Opening ${page}. Download ${app.name} there and install it; this goes on once it is here.`);
|
|
2555
|
+
if (options.open) await openLink(context2, page);
|
|
2556
|
+
const until = context2.now() + options.waitMs;
|
|
2557
|
+
while (!await app.found(context2)) {
|
|
2558
|
+
if (context2.now() + APP_POLL_MS > until) throw new Problem("app_not_installed", `${app.name} is not installed yet. Install it from ${page}, then run this again.`, { app: app.id, download: page });
|
|
2559
|
+
await context2.sleep(APP_POLL_MS);
|
|
2560
|
+
}
|
|
2561
|
+
context2.say.note(`Found ${app.name}.`);
|
|
2562
|
+
return true;
|
|
2563
|
+
}
|
|
2564
|
+
async function getApp(context2, app, options) {
|
|
2565
|
+
const page = context2.platform === "darwin" ? app.desktop : void 0;
|
|
2566
|
+
if (app.fetched === true && page === void 0) return true;
|
|
2567
|
+
const terminal = app.fetched === true ? "" : installer(context2, app);
|
|
2568
|
+
const ways = [...page === void 0 ? [] : ["app"], ...terminal === void 0 ? [] : ["terminal"]];
|
|
2569
|
+
if (!context2.person || ways.length === 0) return app.fetched === true;
|
|
2570
|
+
if (ways.length === 1 && terminal && app.installNeedsNode !== void 0) await needNode(context2, app.installNeedsNode, `${app.name}'s installer`, ["connect", app.id]);
|
|
2571
|
+
let way;
|
|
2572
|
+
if (ways.length === 2) {
|
|
2573
|
+
const picked = await context2.choose(`${app.name} is not on this computer. How do you want it?`, [
|
|
2574
|
+
app.installMacApp === void 0 ? "Download the Mac app" : "Install the Mac app",
|
|
2575
|
+
app.fetched === true ? "Run it from this terminal, in your browser" : `Install it in this terminal (${app.install})`
|
|
2576
|
+
]);
|
|
2577
|
+
way = picked === void 0 ? void 0 : ways[picked];
|
|
2578
|
+
} else if (await context2.confirm(ways[0] === "app" ? `${app.name} is not on this computer. Download the Mac app from ${page}?` : `${app.name} is not installed here. Install it now, with ${app.install}?`)) {
|
|
2579
|
+
way = ways[0];
|
|
2580
|
+
}
|
|
2581
|
+
if (way === void 0) return false;
|
|
2582
|
+
if (way === "app" && app.installMacApp !== void 0) {
|
|
2583
|
+
if (await app.installMacApp(context2) && await app.found(context2)) return true;
|
|
2584
|
+
if (app.fetched === true) {
|
|
2585
|
+
context2.say.note(`${app.name}'s app could not be installed here, so it runs in your browser instead.`);
|
|
2586
|
+
return true;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
if (way === "app") return await waitForApp(context2, app, page, options);
|
|
2590
|
+
if (terminal === "") return true;
|
|
2591
|
+
const local = terminal?.startsWith("npm install -g ") === true ? await npmUserPrefix(context2) : void 0;
|
|
2592
|
+
context2.say.note(`Installing ${app.name}...`);
|
|
2593
|
+
const code = await context2.handOver(local === void 0 ? terminal : terminal.replace("npm install -g ", `npm install -g --prefix=${shellWord(local)} `));
|
|
2594
|
+
if (code !== 0) throw new Problem("app_install_failed", `${app.name} could not be installed: its installer ended with ${code}. Install it with ${app.install}, then run this again.`, { app: app.id, install: app.install });
|
|
2595
|
+
if (local !== void 0) {
|
|
2596
|
+
const bin2 = join15(local, "bin");
|
|
2597
|
+
const path = context2.env.PATH ?? "";
|
|
2598
|
+
if (!path.split(delimiter3).includes(bin2)) {
|
|
2599
|
+
context2.env.PATH = `${bin2}${delimiter3}${path}`;
|
|
2600
|
+
context2.say.note(`${app.name} went in ~/.local/bin, as npm's own folder is for root here. Add that folder to your PATH to start it by name.`);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
if (!await app.found(context2)) throw new Problem("app_not_found", `${app.name} is installed, but this terminal does not find it yet. Open a new terminal, then run this again.`, { app: app.id });
|
|
2604
|
+
context2.say.note(`Installed ${app.name}.`);
|
|
2605
|
+
return true;
|
|
2606
|
+
}
|
|
2051
2607
|
async function stillGood(context2, current) {
|
|
2052
2608
|
return current.key !== void 0 && await whoami(context2, current.key) !== void 0;
|
|
2053
2609
|
}
|
|
@@ -2081,29 +2637,51 @@ async function test(context2, app, key, model2) {
|
|
|
2081
2637
|
}
|
|
2082
2638
|
async function connect(context2, app, options) {
|
|
2083
2639
|
const { say } = context2;
|
|
2084
|
-
|
|
2085
|
-
|
|
2640
|
+
try {
|
|
2641
|
+
if (!await app.found(context2) && !await getApp(context2, app, options)) {
|
|
2642
|
+
const page = context2.platform === "darwin" ? app.desktop : void 0;
|
|
2643
|
+
throw new Problem("app_not_installed", `${app.name} is not installed here. ${/^(npm|curl) /u.test(app.install) ? `Install it with: ${app.install}` : app.install}${page === void 0 ? "" : `, or get the Mac app: ${page}`}`, { app: app.id, install: app.install, ...page === void 0 ? {} : { download: page } });
|
|
2644
|
+
}
|
|
2645
|
+
await app.prepare?.(context2);
|
|
2646
|
+
} catch (error) {
|
|
2647
|
+
if (error instanceof HandedOver) return error.exitCode;
|
|
2648
|
+
throw error;
|
|
2649
|
+
}
|
|
2086
2650
|
const current = await app.current(context2);
|
|
2087
2651
|
const here = current.base === apiBase(context2);
|
|
2088
|
-
|
|
2089
|
-
|
|
2652
|
+
let given;
|
|
2653
|
+
if (options.key !== void 0) {
|
|
2654
|
+
if (app.collectsKey === true) throw new Problem("needs_sign_in", `${app.name} signs in by itself, which an owner or admin of the workspace approves. Ask one to connect it, or set it up by hand from Connect to app.`, { app: app.id });
|
|
2655
|
+
given = await whoami(context2, options.key);
|
|
2656
|
+
if (given === void 0) throw new Problem("key_refused", "AdaptiqAI does not take that key. Make a new one in the console, then run this again.", { app: app.id });
|
|
2657
|
+
}
|
|
2658
|
+
const reuse = options.code === void 0 && options.key === void 0 && options.fresh !== true && here && (current.key === void 0 ? app.ask !== void 0 && await pendingSignIn(context2, app) === void 0 : await stillGood(context2, current));
|
|
2659
|
+
let key = reuse ? current.key : options.key;
|
|
2090
2660
|
let deviceCode;
|
|
2091
2661
|
let signedInOn;
|
|
2092
|
-
|
|
2662
|
+
let workspace;
|
|
2663
|
+
if (!reuse && options.code !== void 0 && app.collectsKey === true) {
|
|
2664
|
+
signedInOn = await modelForSignIn(context2, options.model);
|
|
2665
|
+
deviceCode = options.code;
|
|
2666
|
+
} else if (options.key !== void 0) {
|
|
2667
|
+
signedInOn = await modelForSignIn(context2, options.model);
|
|
2668
|
+
workspace = given?.workspace;
|
|
2669
|
+
} else if (!reuse) {
|
|
2093
2670
|
signedInOn = await modelForSignIn(context2, options.model);
|
|
2094
2671
|
const signedIn = await signIn(context2, app, {
|
|
2095
2672
|
model: signedInOn,
|
|
2096
2673
|
waitMs: options.waitMs,
|
|
2097
2674
|
open: async (url) => {
|
|
2098
2675
|
if (options.open) await openLink(context2, url);
|
|
2099
|
-
}
|
|
2676
|
+
},
|
|
2677
|
+
...options.code === void 0 ? {} : { code: options.code }
|
|
2100
2678
|
});
|
|
2101
2679
|
if (signedIn.state === "waiting") {
|
|
2102
2680
|
say.event("waiting", { for: "sign_in", app: app.id, run_again: true }, "Still waiting. Run this again once you have pressed Connect.");
|
|
2103
2681
|
return EXIT.waitingForSignIn;
|
|
2104
2682
|
}
|
|
2105
2683
|
;
|
|
2106
|
-
({ key, deviceCode } = signedIn);
|
|
2684
|
+
({ key, deviceCode, workspace } = signedIn);
|
|
2107
2685
|
say.event("signed_in", { app: app.id, workspace: signedIn.workspace }, `Signed in to ${signedIn.workspace}.`);
|
|
2108
2686
|
} else {
|
|
2109
2687
|
await forgetSignIn(context2, app);
|
|
@@ -2114,14 +2692,16 @@ async function connect(context2, app, options) {
|
|
|
2114
2692
|
let left = [];
|
|
2115
2693
|
if (settled !== void 0) model2 = settled;
|
|
2116
2694
|
else {
|
|
2117
|
-
const
|
|
2695
|
+
const listed = await listModels(context2, key);
|
|
2696
|
+
const offered = key === void 0 && options.code !== void 0 && options.model !== void 0 && !listed.some((entry2) => entry2.id === options.model) ? [...namedModels([options.model]), ...listed] : listed;
|
|
2697
|
+
const models2 = chooseModels(offered, options.model, reuse ? current.model : signedInOn);
|
|
2118
2698
|
const facts = { ...key === void 0 ? {} : { key }, ...deviceCode === void 0 ? {} : { deviceCode }, models: models2 };
|
|
2119
2699
|
left = await app.apply(context2, facts);
|
|
2120
2700
|
await forgetSignIn(context2, app);
|
|
2121
2701
|
model2 = models2[0].id;
|
|
2122
2702
|
say.event("configured", { app: app.id, model: model2, backups: context2.backups.saved }, [
|
|
2123
2703
|
`${app.does} It starts on ${models2[0].name}.`,
|
|
2124
|
-
...context2.backups.saved.length > 0 ? [`What changed was copied first, to ${
|
|
2704
|
+
...context2.backups.saved.length > 0 ? [`What changed was copied first, to ${dirname4(context2.backups.saved[0])}.`] : []
|
|
2125
2705
|
]);
|
|
2126
2706
|
}
|
|
2127
2707
|
const until = context2.now() + options.waitMs;
|
|
@@ -2139,7 +2719,7 @@ async function connect(context2, app, options) {
|
|
|
2139
2719
|
say.event(
|
|
2140
2720
|
"insufficient_balance",
|
|
2141
2721
|
{ app: app.id, add_funds_url: result.url, run_again: true, message: result.message },
|
|
2142
|
-
[`Your balance is empty. Add
|
|
2722
|
+
[`Your balance is empty. Add funds: ${result.url}`, ...context2.now() + FUNDS_POLL_MS > until ? ["Run this again once they are in."] : ["Waiting for funds. You can stop this and run it again at any time."]]
|
|
2143
2723
|
);
|
|
2144
2724
|
told = true;
|
|
2145
2725
|
}
|
|
@@ -2148,6 +2728,9 @@ async function connect(context2, app, options) {
|
|
|
2148
2728
|
}
|
|
2149
2729
|
const start2 = await app.start(context2, model2);
|
|
2150
2730
|
say.event("connected", { app: app.id, model: model2, start: start2, notes: left }, ["Connected. AdaptiqAI answered.", ...left, `Start it: ${start2}`]);
|
|
2731
|
+
if (app.startsByCommand?.(context2) !== false && await context2.confirm(`Start ${app.name} now?`)) {
|
|
2732
|
+
await context2.handOver(await app.greet?.(context2, model2, options.hello ?? hello(workspace)) ?? start2);
|
|
2733
|
+
}
|
|
2151
2734
|
return EXIT.connected;
|
|
2152
2735
|
}
|
|
2153
2736
|
async function status(context2, only) {
|
|
@@ -2180,13 +2763,21 @@ async function status(context2, only) {
|
|
|
2180
2763
|
}
|
|
2181
2764
|
async function disconnect(context2, app) {
|
|
2182
2765
|
await forgetSignIn(context2, app);
|
|
2183
|
-
|
|
2184
|
-
|
|
2766
|
+
const connected = await app.found(context2) && (await app.current(context2)).base !== void 0;
|
|
2767
|
+
const notes = connected ? await app.remove(context2) ?? [] : [];
|
|
2768
|
+
const { removed, left } = await app.clean?.(context2) ?? { removed: [], left: [] };
|
|
2769
|
+
if (!connected) {
|
|
2770
|
+
context2.say.event("not_connected", { app: app.id, removed, notes: left }, [
|
|
2771
|
+
`${app.name} is not connected to AdaptiqAI here.`,
|
|
2772
|
+
...removed.length > 0 ? ["It still held an AdaptiqAI key, which is gone now."] : [],
|
|
2773
|
+
...left
|
|
2774
|
+
]);
|
|
2185
2775
|
return 0;
|
|
2186
2776
|
}
|
|
2187
|
-
|
|
2188
|
-
context2.say.event("disconnected", { app: app.id, console_url: context2.console, backups: context2.backups.saved }, [
|
|
2777
|
+
context2.say.event("disconnected", { app: app.id, console_url: context2.console, backups: context2.backups.saved, removed, notes: [...notes, ...left] }, [
|
|
2189
2778
|
`${app.name} no longer uses AdaptiqAI.`,
|
|
2779
|
+
...notes,
|
|
2780
|
+
...left,
|
|
2190
2781
|
`To end the connection too, disconnect it in the console: ${context2.console}`
|
|
2191
2782
|
]);
|
|
2192
2783
|
return 0;
|
|
@@ -2195,6 +2786,7 @@ async function disconnect(context2, app) {
|
|
|
2195
2786
|
// src/entry.ts
|
|
2196
2787
|
var USAGE = `Connect the AI app you use to AdaptiqAI.
|
|
2197
2788
|
|
|
2789
|
+
npx -y adaptiqai pick the app, then connect it
|
|
2198
2790
|
npx -y adaptiqai connect <app> sign in, set the app up, and test it
|
|
2199
2791
|
npx -y adaptiqai status [app] say where each app stands
|
|
2200
2792
|
npx -y adaptiqai disconnect <app> take AdaptiqAI out of an app
|
|
@@ -2218,17 +2810,14 @@ to press Connect; 4 waiting for funds. After 3 or 4, run the same command again.
|
|
|
2218
2810
|
`;
|
|
2219
2811
|
var TEN_MINUTES_MS = 6e5;
|
|
2220
2812
|
async function pick(context2) {
|
|
2221
|
-
const
|
|
2222
|
-
for (const app of APPS) if (await app.found(context2))
|
|
2223
|
-
if (found.length === 1) return found[0];
|
|
2224
|
-
if (found.length === 0) throw new Problem("no_app", `No app that AdaptiqAI connects is installed here. Apps: ${APPS.map((app) => app.id).join(", ")}.`);
|
|
2813
|
+
const here = /* @__PURE__ */ new Set();
|
|
2814
|
+
for (const app of APPS) if (await app.found(context2)) here.add(app.id);
|
|
2225
2815
|
if (!context2.person) {
|
|
2226
|
-
context2.say.event("choose_app", { apps:
|
|
2816
|
+
context2.say.event("choose_app", { apps: APPS.map((app) => app.id), installed: APPS.filter((app) => here.has(app.id)).map((app) => app.id) }, "Say which app.");
|
|
2227
2817
|
return void 0;
|
|
2228
2818
|
}
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
return found[Number(answer) - 1] ?? appNamed(answer);
|
|
2819
|
+
const index = await context2.choose("Which app do you want to use with AdaptiqAI?", APPS.map((app) => `${app.name}${here.has(app.id) ? " (on this computer)" : ""}`));
|
|
2820
|
+
return index === void 0 ? void 0 : APPS[index];
|
|
2232
2821
|
}
|
|
2233
2822
|
async function ask(question) {
|
|
2234
2823
|
const reader = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2249,14 +2838,24 @@ function context(person, json, app) {
|
|
|
2249
2838
|
platform,
|
|
2250
2839
|
person,
|
|
2251
2840
|
confirm: async (question) => person && !/^n/iu.test(await ask(`${question} [Y/n] `)),
|
|
2841
|
+
choose: async (question, options) => {
|
|
2842
|
+
if (!person) return void 0;
|
|
2843
|
+
process.stdout.write(`${question}
|
|
2844
|
+
${options.map((option, index) => ` ${index + 1}. ${option}`).join("\n")}
|
|
2845
|
+
`);
|
|
2846
|
+
const answer = Number(await ask(`Pick 1-${options.length}: `));
|
|
2847
|
+
return Number.isInteger(answer) && answer >= 1 && answer <= options.length ? answer - 1 : void 0;
|
|
2848
|
+
},
|
|
2252
2849
|
say: new Output(json),
|
|
2253
2850
|
run: runner(env, platform),
|
|
2851
|
+
handOver: handOver(env, platform),
|
|
2852
|
+
node: process.versions.node,
|
|
2254
2853
|
fetch: globalThis.fetch.bind(globalThis),
|
|
2255
2854
|
now: Date.now,
|
|
2256
2855
|
sleep: async (ms) => {
|
|
2257
2856
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
2258
2857
|
},
|
|
2259
|
-
backups: new Backups(
|
|
2858
|
+
backups: new Backups(join16(cliDir(paths), "backups", app, stamp))
|
|
2260
2859
|
};
|
|
2261
2860
|
}
|
|
2262
2861
|
var parse = (argv) => parseArgs({
|
|
@@ -2281,7 +2880,7 @@ async function main(argv) {
|
|
|
2281
2880
|
}
|
|
2282
2881
|
const { values, positionals } = parsed;
|
|
2283
2882
|
if (values.version) {
|
|
2284
|
-
process.stdout.write(`${"0.
|
|
2883
|
+
process.stdout.write(`${"0.2.0"}
|
|
2285
2884
|
`);
|
|
2286
2885
|
return 0;
|
|
2287
2886
|
}
|
|
@@ -2305,9 +2904,21 @@ async function main(argv) {
|
|
|
2305
2904
|
const seconds = values.wait === void 0 ? void 0 : Number(values.wait);
|
|
2306
2905
|
if (seconds !== void 0 && !(seconds >= 0)) throw new Problem("bad_wait", "--wait takes a number of seconds.", { exit: EXIT.usage });
|
|
2307
2906
|
const waitMs = values["no-wait"] ? 0 : seconds === void 0 ? person ? TEN_MINUTES_MS : 0 : seconds * 1e3;
|
|
2907
|
+
const code = run.env.ADAPTIQ_CONNECT?.trim();
|
|
2908
|
+
if (code !== void 0 && code !== "" && !/^[A-Za-z0-9_-]{32,128}$/u.test(code)) throw new Problem("bad_code", "ADAPTIQ_CONNECT is not a connect code from the console.", { exit: EXIT.usage });
|
|
2308
2909
|
const app = named ?? await pick(run);
|
|
2309
2910
|
if (app === void 0) return EXIT.usage;
|
|
2310
|
-
|
|
2911
|
+
const key = run.env.ADAPTIQ_KEY?.trim();
|
|
2912
|
+
if (key !== void 0 && key !== "" && !/^ak_[A-Za-z0-9._-]+$/u.test(key)) throw new Problem("bad_key", "ADAPTIQ_KEY is not an AdaptiqAI key.", { exit: EXIT.usage });
|
|
2913
|
+
const hello2 = run.env.ADAPTIQ_HELLO?.replace(new RegExp("\\p{Cc}", "gu"), "").trim().slice(0, 400);
|
|
2914
|
+
return await connect(run, app, {
|
|
2915
|
+
...values.model === void 0 ? {} : { model: values.model },
|
|
2916
|
+
...hello2 === void 0 || hello2 === "" ? {} : { hello: hello2 },
|
|
2917
|
+
...key === void 0 || key === "" ? {} : { key },
|
|
2918
|
+
...code === void 0 || code === "" ? {} : { code },
|
|
2919
|
+
waitMs,
|
|
2920
|
+
open: !values["no-open"]
|
|
2921
|
+
});
|
|
2311
2922
|
}
|
|
2312
2923
|
var quit = (code) => {
|
|
2313
2924
|
process.stdout.write("", () => process.exit(code));
|