@resolflow/cli 0.0.0-stage → 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +201 -2
- package/dist/adapters/antigravity.js +29 -0
- package/dist/adapters/claude-code.js +302 -0
- package/dist/adapters/codex.js +247 -0
- package/dist/adapters/coding-agent.js +278 -0
- package/dist/adapters/openai-compatible.js +60 -0
- package/dist/adapters/types.js +9 -0
- package/dist/agents/auth.js +109 -0
- package/dist/agents/daily-budget.js +170 -0
- package/dist/agents/errors.js +60 -0
- package/dist/agents/run-process.js +183 -0
- package/dist/agents/tools.js +175 -0
- package/dist/api.js +59 -0
- package/dist/commands.js +511 -0
- package/dist/config.js +104 -0
- package/dist/daemon.js +317 -0
- package/dist/index.js +88 -0
- package/dist/local-model.js +147 -0
- package/dist/local-ui.js +120 -0
- package/dist/log.js +35 -0
- package/dist/protocol.js +23 -0
- package/dist/run-log.js +78 -0
- package/dist/runtime.js +38 -0
- package/dist/secrets.js +207 -0
- package/dist/update-check.js +180 -0
- package/dist/urls.js +69 -0
- package/package.json +62 -4
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AGENT_TOOLS = exports.CODEX_OPT_IN_ENV = void 0;
|
|
37
|
+
exports.toolIdOf = toolIdOf;
|
|
38
|
+
exports.findOnPath = findOnPath;
|
|
39
|
+
exports.binaryOf = binaryOf;
|
|
40
|
+
exports.toolVersion = toolVersion;
|
|
41
|
+
exports.apiKeyFor = apiKeyFor;
|
|
42
|
+
exports.describeKeySource = describeKeySource;
|
|
43
|
+
exports.validateApiKeyShape = validateApiKeyShape;
|
|
44
|
+
exports.optedIn = optedIn;
|
|
45
|
+
exports.detectTools = detectTools;
|
|
46
|
+
const child_process_1 = require("child_process");
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const secrets_1 = require("../secrets");
|
|
50
|
+
const auth_1 = require("./auth");
|
|
51
|
+
/** Codex jobs from Resolflow run only when the person sets this on the device. */
|
|
52
|
+
exports.CODEX_OPT_IN_ENV = 'RESOLFLOW_ALLOW_CODEX_JOBS';
|
|
53
|
+
exports.AGENT_TOOLS = {
|
|
54
|
+
'claude-code': { id: 'claude-code', label: 'Claude Code', bins: ['claude'], keyEnv: ['ANTHROPIC_API_KEY'], supported: true },
|
|
55
|
+
codex: {
|
|
56
|
+
id: 'codex',
|
|
57
|
+
label: 'Codex CLI',
|
|
58
|
+
bins: ['codex'],
|
|
59
|
+
keyEnv: ['CODEX_API_KEY', 'OPENAI_API_KEY'],
|
|
60
|
+
supported: true,
|
|
61
|
+
optInEnv: exports.CODEX_OPT_IN_ENV,
|
|
62
|
+
optInNote: `off by default: Codex's read-only sandbox does not stop it reading files outside the job folder (for example ~/.ssh), so a job prompt could make it read and return them. Set ${exports.CODEX_OPT_IN_ENV}=1 to offer it anyway`,
|
|
63
|
+
},
|
|
64
|
+
antigravity: {
|
|
65
|
+
id: 'antigravity',
|
|
66
|
+
label: 'Antigravity CLI',
|
|
67
|
+
bins: ['agy', 'antigravity'],
|
|
68
|
+
keyEnv: ['GEMINI_API_KEY'],
|
|
69
|
+
supported: false,
|
|
70
|
+
note: 'not supported yet: no stable, officially documented headless JSON mode with an API key',
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
/** `claude`, `claude-code`, `codex`, `antigravity`, `agy`, `gemini` → the tool id. */
|
|
74
|
+
function toolIdOf(name) {
|
|
75
|
+
const n = name.trim().toLowerCase();
|
|
76
|
+
if (n === 'claude' || n === 'claude-code')
|
|
77
|
+
return 'claude-code';
|
|
78
|
+
if (n === 'codex')
|
|
79
|
+
return 'codex';
|
|
80
|
+
if (n === 'antigravity' || n === 'agy' || n === 'gemini')
|
|
81
|
+
return 'antigravity';
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/** Absolute path of an executable on PATH (no shell). */
|
|
85
|
+
function findOnPath(names, env = process.env) {
|
|
86
|
+
const dirs = (env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
87
|
+
const exts = process.platform === 'win32' ? (env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';') : [''];
|
|
88
|
+
for (const name of names) {
|
|
89
|
+
for (const dir of dirs) {
|
|
90
|
+
for (const ext of exts) {
|
|
91
|
+
const p = path.join(dir, name + ext);
|
|
92
|
+
try {
|
|
93
|
+
const st = fs.statSync(p);
|
|
94
|
+
if (st.isFile()) {
|
|
95
|
+
fs.accessSync(p, fs.constants.X_OK);
|
|
96
|
+
return p;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
/* next */
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function binaryOf(def, env = process.env) {
|
|
108
|
+
const override = env[`RESOLFLOW_${def.id.toUpperCase().replace(/-/g, '_')}_BIN`];
|
|
109
|
+
if (override)
|
|
110
|
+
return path.isAbsolute(override) && fs.existsSync(override) ? override : null;
|
|
111
|
+
return findOnPath(def.bins, env);
|
|
112
|
+
}
|
|
113
|
+
/** `<tool> --version` (5 s, no shell); the first line, trimmed. */
|
|
114
|
+
function toolVersion(bin, timeoutMs = 5_000) {
|
|
115
|
+
return new Promise((resolve) => {
|
|
116
|
+
(0, child_process_1.execFile)(bin, ['--version'], { timeout: timeoutMs, windowsHide: true, env: { PATH: process.env.PATH, HOME: process.env.HOME } }, (err, stdout) => {
|
|
117
|
+
if (err)
|
|
118
|
+
return resolve(null);
|
|
119
|
+
const line = String(stdout).split('\n').find((l) => l.trim());
|
|
120
|
+
resolve(line ? line.trim().slice(0, 80) : null);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
/** Where the tool's API key would come from — never the key itself. */
|
|
125
|
+
async function apiKeyFor(def, configDir, env = process.env) {
|
|
126
|
+
try {
|
|
127
|
+
const { store } = (0, secrets_1.chooseSecretStore)(configDir, env, (0, secrets_1.agentKeyEntry)(configDir, def.id));
|
|
128
|
+
const stored = await store.get();
|
|
129
|
+
if (stored)
|
|
130
|
+
return { key: stored, from: { source: 'keychain', store: store.kind } };
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* no store: fall back to the env */
|
|
134
|
+
}
|
|
135
|
+
for (const name of def.keyEnv) {
|
|
136
|
+
const v = env[name]?.trim();
|
|
137
|
+
if (v)
|
|
138
|
+
return { key: v, from: { source: 'env', name } };
|
|
139
|
+
}
|
|
140
|
+
return { key: null, from: { source: 'none' } };
|
|
141
|
+
}
|
|
142
|
+
function describeKeySource(k) {
|
|
143
|
+
if (k.source === 'keychain')
|
|
144
|
+
return k.store === 'file' ? 'configured (0600 file in the config directory, no OS keychain)' : 'configured (OS keychain)';
|
|
145
|
+
return k.source === 'env' ? `configured (env ${k.name})` : 'not configured';
|
|
146
|
+
}
|
|
147
|
+
/** Basic shape check of a pasted key (no vendor call): one token of safe characters. */
|
|
148
|
+
function validateApiKeyShape(key) {
|
|
149
|
+
const k = key.trim();
|
|
150
|
+
if (!k)
|
|
151
|
+
return 'The key is empty';
|
|
152
|
+
if (!secrets_1.SAFE_SECRET.test(k))
|
|
153
|
+
return 'That does not look like an API key (one word of letters, digits, "-", "_" or ".")';
|
|
154
|
+
if (k.length < 16)
|
|
155
|
+
return 'That key is too short';
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
/** Whether the person opted in to a tool that needs it (optInEnv = "1"). */
|
|
159
|
+
function optedIn(def, env = process.env) {
|
|
160
|
+
return !def.optInEnv || env[def.optInEnv] === '1';
|
|
161
|
+
}
|
|
162
|
+
async function detectTools(configDir, env = process.env, cfg = {}) {
|
|
163
|
+
const out = [];
|
|
164
|
+
for (const def of Object.values(exports.AGENT_TOOLS)) {
|
|
165
|
+
const bin = binaryOf(def, env);
|
|
166
|
+
const version = bin ? await toolVersion(bin) : null;
|
|
167
|
+
const { from } = await apiKeyFor(def, configDir, env);
|
|
168
|
+
const auth = def.supported ? (0, auth_1.authModeOf)(cfg, def.id) : 'api_key';
|
|
169
|
+
// login: no key needed (and none is passed) — the tool's own login; the device opt-in applies the same way
|
|
170
|
+
const usable = auth === 'login' ? !!bin : !!bin && def.supported && from.source !== 'none';
|
|
171
|
+
const opted = optedIn(def, env);
|
|
172
|
+
out.push({ def, bin, version, key: from, auth, ready: usable && opted, ...(usable && !opted ? { needsOptIn: true } : {}) });
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ApiError = void 0;
|
|
4
|
+
exports.pairDevice = pairDevice;
|
|
5
|
+
exports.describeDevice = describeDevice;
|
|
6
|
+
exports.unpairDevice = unpairDevice;
|
|
7
|
+
const urls_1 = require("./urls");
|
|
8
|
+
/** The few REST calls the CLI makes (pairing, own status, unpair). https only (see urls.ts), no redirects. */
|
|
9
|
+
class ApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
constructor(status, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.status = status;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ApiError = ApiError;
|
|
17
|
+
async function call(apiUrl, method, path, opts = {}) {
|
|
18
|
+
const base = (0, urls_1.normaliseApiUrl)(apiUrl);
|
|
19
|
+
let res;
|
|
20
|
+
try {
|
|
21
|
+
res = await fetch(`${base}/api${path}`, {
|
|
22
|
+
method,
|
|
23
|
+
headers: {
|
|
24
|
+
accept: 'application/json',
|
|
25
|
+
...(opts.body !== undefined ? { 'content-type': 'application/json' } : {}),
|
|
26
|
+
...(opts.token ? { authorization: `Bearer ${opts.token}` } : {}),
|
|
27
|
+
},
|
|
28
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
29
|
+
redirect: 'error',
|
|
30
|
+
signal: AbortSignal.timeout(15_000),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
throw new ApiError(0, `Cannot reach ${base} (${err.message})`);
|
|
35
|
+
}
|
|
36
|
+
const text = await res.text();
|
|
37
|
+
let json = null;
|
|
38
|
+
try {
|
|
39
|
+
json = text ? JSON.parse(text) : null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
/* not JSON */
|
|
43
|
+
}
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const msg = Array.isArray(json?.message) ? json.message.join(', ') : json?.message;
|
|
46
|
+
throw new ApiError(res.status, String(msg || `HTTP ${res.status}`).slice(0, 300));
|
|
47
|
+
}
|
|
48
|
+
return json;
|
|
49
|
+
}
|
|
50
|
+
function pairDevice(apiUrl, code, device) {
|
|
51
|
+
return call(apiUrl, 'POST', '/cli/pair', { body: { code, ...device } });
|
|
52
|
+
}
|
|
53
|
+
function describeDevice(apiUrl, token) {
|
|
54
|
+
// policy (RF-90e75d6d): the administrator's CLI login switch per tool; absent on older servers
|
|
55
|
+
return call(apiUrl, 'GET', '/cli/device', { token });
|
|
56
|
+
}
|
|
57
|
+
function unpairDevice(apiUrl, token) {
|
|
58
|
+
return call(apiUrl, 'DELETE', '/cli/device', { token });
|
|
59
|
+
}
|