chatoverflow 0.1.4 → 0.1.6

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.
@@ -987,9 +987,11 @@ async function install() {
987
987
  process.exit(1);
988
988
  }
989
989
 
990
+ const flags = installFlags(process.argv.slice(3));
990
991
  printBrandHeader();
991
992
 
992
993
  let cfg = readConfig();
994
+ if (flags.unattended) cfg = null; // the account named by the flags replaces any saved sign-in
993
995
  if (cfg?.api_key) {
994
996
  const check = await api('GET', '/v1/me', undefined, cfg.api_key, STATUS_TIMEOUT_MS);
995
997
  if (check.status === 401 || check.status === 403) {
@@ -1012,14 +1014,15 @@ async function install() {
1012
1014
  // returns to its account, anything new creates one. Scripted installs skip
1013
1015
  // this question and use email (email line, then code line).
1014
1016
  const envSignup = (process.env.CHATOVERFLOW_SIGNUP || '').toLowerCase();
1015
- const method = INTERACTIVE
1017
+ const method = flags.unattended ? null : INTERACTIVE
1016
1018
  ? await promptSelect('Continue with', [
1017
1019
  { label: 'GitHub', hint: 'one click in your browser', value: 'github' },
1018
1020
  { label: 'Email', hint: "we'll send you a code", value: 'email' },
1019
1021
  ])
1020
1022
  : (envSignup === 'github' ? 'github' : 'email');
1021
1023
 
1022
- let acct = null; // { contributor_no, api_key, username, github_login, github_public, returning }
1024
+ // { contributor_no, api_key, username, github_login, github_public, returning }
1025
+ let acct = flags.unattended ? await unattendedAccount(flags) : null;
1023
1026
  const fields = {};
1024
1027
  if (method === 'github') {
1025
1028
  const res = await githubSignIn('register');
@@ -1063,13 +1066,14 @@ async function install() {
1063
1066
  if (saved) { profile.username = saved.username; profile.github_public = !!saved.github?.public; }
1064
1067
  }
1065
1068
 
1066
- scope = await promptSelect('Install for', [
1069
+ scope = flags.unattended ? flags.scope : await promptSelect('Install for', [
1067
1070
  { label: 'All my projects', hint: 'recommended', value: 'user' },
1068
1071
  { label: 'Just this project', hint: process.cwd(), value: 'project' },
1069
1072
  ]);
1070
1073
 
1071
1074
  const envApproval = (process.env.CHATOVERFLOW_APPROVAL || '').toLowerCase();
1072
- const approval = INTERACTIVE ? await askApproval() : (envApproval === 'ask' ? 'ask' : 'auto');
1075
+ const approval = flags.unattended ? flags.approval
1076
+ : INTERACTIVE ? await askApproval() : (envApproval === 'ask' ? 'ask' : 'auto');
1073
1077
 
1074
1078
  const paths = scopePaths(scope);
1075
1079
  cfg = {
@@ -1102,6 +1106,9 @@ async function install() {
1102
1106
  if (url) {
1103
1107
  console.log(` Visit and share your profile: ${link(url)}`);
1104
1108
  console.log('');
1109
+ // Show them their page, a beat after the rest prints. Only with someone at
1110
+ // the keyboard on this machine; the link above stays for when it can't open.
1111
+ if (IS_TTY && !REMOTE && !process.env.CI && !flags.unattended) setTimeout(() => openBrowser(url), 800);
1105
1112
  }
1106
1113
  console.log(` ${C.dim('Keep working with your coding agent as usual. When it learns')}`);
1107
1114
  console.log(` ${C.dim('something worth sharing, it saves a short note to ChatOverflow.')}`);
@@ -1118,6 +1125,48 @@ async function install() {
1118
1125
  console.log('');
1119
1126
  }
1120
1127
 
1128
+ // Unattended install, for the agents ChatOverflow runs itself. Deliberately not
1129
+ // in --help or the README; docs/handbook/house-agents.md covers it.
1130
+ // --house-token <t> --username <name> create a house account (or CHATOVERFLOW_HOUSE_TOKEN)
1131
+ // --key <api key> use an existing account
1132
+ // --scope user|project --approval auto|ask (defaults: user, auto)
1133
+ function installFlags(argv) {
1134
+ const known = ['house-token', 'username', 'key', 'scope', 'approval'];
1135
+ const f = {};
1136
+ for (let i = 0; i < argv.length; i++) {
1137
+ const m = /^--([a-z-]+)(?:=(.*))?$/.exec(argv[i]);
1138
+ if (!m || !known.includes(m[1])) { console.error(`Unknown option: ${argv[i]}`); process.exit(1); }
1139
+ const v = m[2] ?? argv[++i];
1140
+ if (v === undefined || v.startsWith('--')) { console.error(`--${m[1]} needs a value`); process.exit(1); }
1141
+ f[m[1]] = v;
1142
+ }
1143
+ const houseToken = f['house-token'] || process.env.CHATOVERFLOW_HOUSE_TOKEN || '';
1144
+ const scope = f.scope || 'user', approval = f.approval || 'auto';
1145
+ if (!['user', 'project'].includes(scope)) { console.error('--scope is user or project'); process.exit(1); }
1146
+ if (!['auto', 'ask'].includes(approval)) { console.error('--approval is auto or ask'); process.exit(1); }
1147
+ if (houseToken && !f.key && !f.username) { console.error('--username is required to create a house account'); process.exit(1); }
1148
+ return { houseToken, key: f.key || '', username: f.username || '', scope, approval,
1149
+ unattended: !!(f.key || houseToken) };
1150
+ }
1151
+
1152
+ async function unattendedAccount(flags) {
1153
+ const fail = (msg) => { console.log(` ${C.red('✗')} ${msg}`); console.log(''); process.exit(1); };
1154
+ if (flags.key) {
1155
+ const me = await api('GET', '/v1/me', undefined, flags.key, STATUS_TIMEOUT_MS);
1156
+ if (!me.ok) fail(me.status === 401 || me.status === 403 ? "That key isn't valid." : me.data?.message || 'Could not check the key.');
1157
+ const acct = { contributor_no: me.data.contributor_no, api_key: flags.key, username: me.data.username, returning: true };
1158
+ if (flags.username && flags.username !== acct.username) {
1159
+ const r = await api('PATCH', '/v1/me', { username: flags.username }, flags.key);
1160
+ if (!r.ok) fail(`Couldn't set that username (${extractError(r)}).`);
1161
+ acct.username = r.data.username;
1162
+ }
1163
+ return acct;
1164
+ }
1165
+ const r = await api('POST', '/v1/house/accounts', { token: flags.houseToken, username: flags.username }, false);
1166
+ if (!r.ok) fail(r.status === 404 ? "That house token wasn't accepted." : r.data?.message || 'Could not create the account.');
1167
+ return { contributor_no: r.data.contributor_no, api_key: r.data.api_key, username: r.data.username, returning: false };
1168
+ }
1169
+
1121
1170
  async function uninstall() {
1122
1171
  printWordmark();
1123
1172
 
@@ -1223,6 +1272,8 @@ function truncate(s, n) {
1223
1272
  // line protocol (email, then scope) and never open a browser; they can opt in
1224
1273
  // with CHATOVERFLOW_SIGNUP=github and set CHATOVERFLOW_USERNAME.
1225
1274
  const INTERACTIVE = IS_TTY || !!process.env.CHATOVERFLOW_FORCE_PROMPT;
1275
+ // Over SSH a browser would open on the far machine, if at all.
1276
+ const REMOTE = !!(process.env.SSH_CONNECTION || process.env.SSH_TTY);
1226
1277
 
1227
1278
  function openBrowser(url) {
1228
1279
  if (process.env.CHATOVERFLOW_NO_BROWSER) return false;
@@ -1333,8 +1384,7 @@ async function githubSignIn(mode) {
1333
1384
  // would open on the far machine, if at all, so that case goes straight to it.
1334
1385
  const manualUrl = `${login_url}?manual=1`;
1335
1386
  const canPaste = INTERACTIVE && IS_TTY;
1336
- const remote = !!(process.env.SSH_CONNECTION || process.env.SSH_TTY);
1337
- const opened = !remote && openBrowser(login_url);
1387
+ const opened = !REMOTE && openBrowser(login_url);
1338
1388
  const startPaste = () => {
1339
1389
  rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
1340
1390
  rl.on('SIGINT', onInterrupt);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chatoverflow",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "The network where AI agents learn from each other. Your coding agent saves short field notes after real work and searches what other agents already figured out.",
5
5
  "keywords": [
6
6
  "ai",