codeply-cli 0.5.2 → 0.5.3

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.
Files changed (2) hide show
  1. package/lib/cloud.mjs +64 -0
  2. package/package.json +1 -1
package/lib/cloud.mjs CHANGED
@@ -487,6 +487,67 @@ export async function ensureTrackKey({ api, repo, cwd, home = defaultHome(), fet
487
487
  } catch { return false; }
488
488
  }
489
489
 
490
+ /**
491
+ * Connected apps (Gmail, Slack, Vercel, Supabase, GitHub) for cloud runs. A run
492
+ * happens on GitHub's servers, which don't have the sign-ins saved on this PC,
493
+ * so Craft keeps an encrypted copy on Codeply's server for the signed-in user
494
+ * (the connections function) and the runner loads it at start with the usage
495
+ * key. Sent only when something changed. Off with "cloudConnections": false in
496
+ * ~/.codeply/config.json. Never fails a setup or a run.
497
+ */
498
+ export const CONNECTION_APPS = ['gmail', 'slack', 'vercel', 'supabase', 'github'];
499
+ export async function syncCloudConnections({ home = defaultHome(), fetchImpl = fetch } = {}) {
500
+ try {
501
+ const config = require('./config.js');
502
+ if (config.getConfig().cloudConnections === false) return false;
503
+ const connections = {};
504
+ for (const name of CONNECTION_APPS) {
505
+ const c = config.getIntegration(name); // includes the app's OAuth client, which token refresh needs
506
+ if (c.accessToken) connections[name] = c;
507
+ }
508
+ const sig = crypto.createHash('sha256').update(JSON.stringify(connections)).digest('hex');
509
+ const state = readState(home);
510
+ if (state.connectionsSig === sig) return false;
511
+ const auth = require('./auth.js');
512
+ const token = await auth.getAccessToken().catch(() => null);
513
+ if (!token) return false;
514
+ const r = await fetchImpl(auth.SUPABASE_URL + '/functions/v1/connections', {
515
+ method: 'POST',
516
+ headers: { Authorization: 'Bearer ' + token, apikey: auth.SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
517
+ body: JSON.stringify({ action: 'save', connections }),
518
+ signal: AbortSignal.timeout(15_000),
519
+ });
520
+ if (!r.ok) return false;
521
+ writeState(home, { ...readState(home), connectionsSig: sig });
522
+ return true;
523
+ } catch { return false; }
524
+ }
525
+
526
+ /** In the runner: put the user's connected apps into this machine's config, so the agent's tools can use them. */
527
+ export async function loadCloudConnections({ env = process.env, fetchImpl = fetch, log = () => {} } = {}) {
528
+ const key = String(env.CODEPLY_TRACK_KEY || '').trim();
529
+ if (!key) return [];
530
+ try {
531
+ const auth = require('./auth.js');
532
+ const r = await fetchImpl(auth.SUPABASE_URL + '/functions/v1/connections', {
533
+ method: 'POST',
534
+ headers: { 'x-codeply-track-key': key, apikey: auth.SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
535
+ body: JSON.stringify({ action: 'get' }),
536
+ signal: AbortSignal.timeout(15_000),
537
+ });
538
+ const j = await r.json().catch(() => ({}));
539
+ if (!r.ok || !j.connections) return [];
540
+ const config = require('./config.js');
541
+ const loaded = [];
542
+ for (const name of CONNECTION_APPS) {
543
+ const c = j.connections[name];
544
+ if (c && c.accessToken) { config.saveIntegration(name, c); loaded.push(name); }
545
+ }
546
+ if (loaded.length) log('Connected apps: ' + loaded.join(', '));
547
+ return loaded;
548
+ } catch (e) { log('Could not load connected apps: ' + e.message); return []; }
549
+ }
550
+
490
551
  // Bumped when CLOUD_WORKFLOW changes, so repos set up earlier get the new file once.
491
552
  export const WORKFLOW_REV = 2;
492
553
 
@@ -519,6 +580,7 @@ export async function setupCloud({ cwd, token, model, target, envText, home = de
519
580
  if (envText != null) await setProjectEnv({ api, repo, envText });
520
581
  updateProject(cwd, home, (p) => ({ ...p, modelSig: cfg.sig, modelName: model.name || model.model, enabled: true, workflowRev: WORKFLOW_REV }));
521
582
  await ensureTrackKey({ api, repo, cwd, home, fetchImpl });
583
+ await syncCloudConnections({ home, fetchImpl });
522
584
  return { repo, created, kind, base, url: `${(serverUrl || 'https://github.com').replace(/\/$/, '')}/${repo}`, skipped };
523
585
  }
524
586
 
@@ -582,6 +644,7 @@ export async function startCloudRun({ cwd, token, prompt, mode = 'Build', sessio
582
644
  updateProject(cwd, home, (q) => ({ ...q, workflowRev: WORKFLOW_REV }));
583
645
  }
584
646
  await ensureTrackKey({ api, repo: p.repo, cwd, home, fetchImpl });
647
+ await syncCloudConnections({ home, fetchImpl });
585
648
  let baseSha;
586
649
  if (p.kind === 'repo') {
587
650
  const ref = await api('GET', `/repos/${p.repo}/git/ref/heads/${encodeURIComponent(base)}`, null, { allow: [404] });
@@ -986,6 +1049,7 @@ export async function runCloudRunner({ env = process.env, cwd = process.cwd(), t
986
1049
  const base = env.CRAFT_BASE || env.GITHUB_REF_NAME || 'main';
987
1050
  const merge = String(env.CRAFT_MERGE || 'true') !== 'false';
988
1051
  await files.ensure(headSha).catch((e) => log(`Could not create ${SESSIONS_BRANCH}: ${e.message}`));
1052
+ await loadCloudConnections({ env, log });
989
1053
 
990
1054
  // The project's environment for running and testing: never committed.
991
1055
  const envFile = path.join(cwd, '.env');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeply-cli",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Codeply from the terminal: an interactive AI coding agent, a local server (codeply serve), and a GitHub agent that runs in your own Actions runner.",
5
5
  "main": "bin/codeply.js",
6
6
  "bin": {