getownit 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/login.js +4 -0
- package/dist/commands/model.js +38 -0
- package/dist/debrief.js +30 -4
- package/dist/index.js +11 -1
- package/package.json +1 -1
package/dist/commands/login.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ApiError, createApi } from '../api.js';
|
|
|
3
3
|
import { readConfig, serverUrl, updateConfig } from '../config.js';
|
|
4
4
|
import { describeKey, modelKey } from '../providers.js';
|
|
5
5
|
import { readSecret } from '../secret.js';
|
|
6
|
+
import { serverModels } from './model.js';
|
|
6
7
|
import { paint } from '../ui.js';
|
|
7
8
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
9
|
/** POSTs a form to github.com's OAuth endpoints. OWNIT_GITHUB_URL overrides the host in tests. */
|
|
@@ -114,5 +115,8 @@ export async function whoamiCommand() {
|
|
|
114
115
|
out.write(`GitHub: ${config.token ? (config.login ?? 'logged in') : 'not logged in (run "ownit login")'}\n`);
|
|
115
116
|
const model = modelKey(config);
|
|
116
117
|
out.write(`API key: ${model ? `set (${describeKey(model)})` : 'not set (run "ownit key set")'}\n`);
|
|
118
|
+
const current = model && (await serverModels())?.find((m) => m.id === model.provider);
|
|
119
|
+
if (current)
|
|
120
|
+
out.write(`Model: ${current.model}\n`);
|
|
117
121
|
return 0;
|
|
118
122
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createApi } from '../api.js';
|
|
2
|
+
import { readConfig } from '../config.js';
|
|
3
|
+
import { describeKey, modelKey } from '../providers.js';
|
|
4
|
+
import { paint } from '../ui.js';
|
|
5
|
+
/** The model each provider runs on the server, or null when the server doesn't say (older servers). */
|
|
6
|
+
export async function serverModels() {
|
|
7
|
+
try {
|
|
8
|
+
const { providers } = await createApi().request('GET', '/models', undefined, { timeoutMs: 5_000 });
|
|
9
|
+
return providers;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `ownit model`: which provider and model your debriefs use. The key is saved
|
|
17
|
+
* for your user, so this is the same in every repo on this computer.
|
|
18
|
+
*/
|
|
19
|
+
export async function modelCommand() {
|
|
20
|
+
const out = process.stderr;
|
|
21
|
+
const c = paint(out);
|
|
22
|
+
const key = modelKey(readConfig());
|
|
23
|
+
const models = await serverModels();
|
|
24
|
+
const current = key && models?.find((m) => m.id === key.provider);
|
|
25
|
+
if (!key)
|
|
26
|
+
out.write(`Provider: ${c.yellow('no key set')} (run "ownit key set")\n`);
|
|
27
|
+
else {
|
|
28
|
+
out.write(`Provider: ${describeKey(key)}\n`);
|
|
29
|
+
out.write(`Model: ${current ? c.bold(current.model) : c.dim("this server doesn't report its models")}\n`);
|
|
30
|
+
}
|
|
31
|
+
for (const other of models ?? []) {
|
|
32
|
+
if (other.id === key?.provider)
|
|
33
|
+
continue;
|
|
34
|
+
out.write(c.dim(`With ${other.name}: ${other.model} (ownit key set ${other.id})\n`));
|
|
35
|
+
}
|
|
36
|
+
out.write(c.dim('Your key is saved for your user, so this applies in every repo on this computer.\n'));
|
|
37
|
+
return 0;
|
|
38
|
+
}
|
package/dist/debrief.js
CHANGED
|
@@ -19,6 +19,32 @@ async function withSpinner(out, text, work) {
|
|
|
19
19
|
spin.stop();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/** A provider error worth retrying: the model did not answer in time or failed on its side. */
|
|
23
|
+
const isUnavailable = (error) => error instanceof ApiError && error.isProviderError && error.body.reason === 'unavailable';
|
|
24
|
+
/**
|
|
25
|
+
* Runs a model request. When the model doesn't answer, it tries once more by
|
|
26
|
+
* itself, then asks before each further try, so a slow moment never throws
|
|
27
|
+
* away a debrief in progress.
|
|
28
|
+
*/
|
|
29
|
+
async function withRetry(prompter, request) {
|
|
30
|
+
const c = paint(prompter.out);
|
|
31
|
+
for (let attempt = 1;; attempt++) {
|
|
32
|
+
try {
|
|
33
|
+
return await request();
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
if (!isUnavailable(error))
|
|
37
|
+
throw error;
|
|
38
|
+
if (attempt === 1) {
|
|
39
|
+
prompter.out.write(c.dim('The model did not answer in time. Trying again…\n'));
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const again = await prompter.ask(`${c.yellow(error.message)} Try again? [Y/n] `);
|
|
43
|
+
if (again === null || /^n/i.test(again.trim()))
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
22
48
|
function printQuestion(out, question, total, followUp) {
|
|
23
49
|
const c = paint(out);
|
|
24
50
|
const label = `Question ${question.idx + 1}/${total}`;
|
|
@@ -55,12 +81,12 @@ async function askUntilPassed(debrief, first) {
|
|
|
55
81
|
return later(out);
|
|
56
82
|
let result;
|
|
57
83
|
try {
|
|
58
|
-
result = await withSpinner(out, 'Checking your answer…', () => api.request('POST', '/api/interview/answer', {
|
|
84
|
+
result = await withRetry(prompter, () => withSpinner(out, 'Checking your answer…', () => api.request('POST', '/api/interview/answer', {
|
|
59
85
|
session: debrief.session,
|
|
60
86
|
idx: question.idx,
|
|
61
87
|
answer: answer.slice(0, MAX_ANSWER_CHARS),
|
|
62
88
|
...debrief.payload,
|
|
63
|
-
}));
|
|
89
|
+
})));
|
|
64
90
|
}
|
|
65
91
|
catch (error) {
|
|
66
92
|
return failure(out, error);
|
|
@@ -92,14 +118,14 @@ export async function runDebrief(api, prompter, target) {
|
|
|
92
118
|
};
|
|
93
119
|
let start;
|
|
94
120
|
try {
|
|
95
|
-
start = await withSpinner(out, 'Nemotron is reading your push…', () => api.request('POST', '/api/interview/start', {
|
|
121
|
+
start = await withRetry(prompter, () => withSpinner(out, 'Nemotron is reading your push…', () => api.request('POST', '/api/interview/start', {
|
|
96
122
|
repo: target.repo,
|
|
97
123
|
base_sha: target.baseSha,
|
|
98
124
|
head_sha: target.headSha,
|
|
99
125
|
changed_lines: target.diff.changedLines,
|
|
100
126
|
push_id: target.pushId,
|
|
101
127
|
...payload,
|
|
102
|
-
}));
|
|
128
|
+
})));
|
|
103
129
|
}
|
|
104
130
|
catch (error) {
|
|
105
131
|
return failure(out, error);
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { initCommand, uninstallCommand } from './commands/init.js';
|
|
|
6
6
|
import { interviewCommand } from './commands/interview.js';
|
|
7
7
|
import { keyCommand } from './commands/key.js';
|
|
8
8
|
import { loginCommand, logoutCommand, whoamiCommand } from './commands/login.js';
|
|
9
|
+
import { modelCommand } from './commands/model.js';
|
|
9
10
|
import { settingsCommand } from './commands/settings.js';
|
|
10
11
|
import { closestMatch, errorMessage } from './ui.js';
|
|
11
12
|
/** Every command, in the order `ownit help` lists them. */
|
|
@@ -54,6 +55,15 @@ Never type the key after the command: it would end up in your shell history.
|
|
|
54
55
|
NVIDIA_API_KEY or NEBIUS_API_KEY in the environment work too.`,
|
|
55
56
|
run: keyCommand,
|
|
56
57
|
},
|
|
58
|
+
model: {
|
|
59
|
+
help: [['ownit model', 'Show the provider and model your debriefs use']],
|
|
60
|
+
details: `Shows which provider your saved key is for and which Nemotron model this server runs
|
|
61
|
+
for it, plus what the other provider would use. The key is saved for your user
|
|
62
|
+
(~/.ownit/config.json), so it applies in every repo on this computer. Switch with
|
|
63
|
+
"ownit key set nebius" or "ownit key set nvidia". NVIDIA_API_KEY or NEBIUS_API_KEY in the
|
|
64
|
+
environment override it for that terminal.`,
|
|
65
|
+
run: modelCommand,
|
|
66
|
+
},
|
|
57
67
|
badge: {
|
|
58
68
|
help: [['ownit badge', "Print this repo's badge Markdown for your README"]],
|
|
59
69
|
details: `Paste the output into your README. To copy it straight away:
|
|
@@ -85,7 +95,7 @@ Private repos never show questions or answers.`,
|
|
|
85
95
|
hook: { run: (args) => hookCommand(args) },
|
|
86
96
|
version: { run: () => print(`${version()}\n`) },
|
|
87
97
|
};
|
|
88
|
-
const ALIASES = { interviews: 'interview', setting: 'settings', page: 'settings', status: 'whoami', '--help': 'help', '-h': 'help', '--version': 'version', '-v': 'version' };
|
|
98
|
+
const ALIASES = { plan: 'model', currentplan: 'model', models: 'model', interviews: 'interview', setting: 'settings', page: 'settings', status: 'whoami', '--help': 'help', '-h': 'help', '--version': 'version', '-v': 'version' };
|
|
89
99
|
const commandName = (name) => (Object.hasOwn(ALIASES, name) ? ALIASES[name] : name);
|
|
90
100
|
function findCommand(name) {
|
|
91
101
|
const key = commandName(name);
|