@motionode/cli 0.1.0 → 0.1.2

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 CHANGED
@@ -20,5 +20,5 @@ motionode finish T11-518 # Motionode checks your pushed commits and moves the
20
20
  - It runs only fixed git operations Motionode can ask for (check out a ticket
21
21
  branch, show a diff, commit and push, move changes onto a ticket branch) —
22
22
  never arbitrary commands.
23
- - `--api URL` (or `MOTIONODE_API_URL`) points it at another Motionode API.
23
+ - `motionode use <url>` points every terminal at another Motionode API until `motionode use production`; `--api URL` or `MOTIONODE_API_URL` overrides it for one run. `motionode where` shows the current one.
24
24
  - Revoke a computer in Motionode → Settings → Development workflow.
package/bin/motionode.js CHANGED
@@ -15,9 +15,13 @@ const HELP = `motionode ${version} — work Motionode tickets with your own loca
15
15
  motionode work <ticket> start a ticket locally and check out its branches (e.g. T11-518)
16
16
  motionode finish <ticket> [--force] [--note] you're done: Motionode checks your pushed commits and moves it on
17
17
  motionode status what each linked folder has checked out and changed
18
+ motionode where which Motionode this terminal talks to
19
+ motionode use <url|production> switch which Motionode the CLI talks to (remembered)
18
20
  motionode logout disconnect this computer
19
21
 
20
- Git stays yours: fetch, commit and push use your own git credentials and identity.`;
22
+ Git stays yours: fetch, commit and push use your own git credentials and identity.
23
+ Talks to production (https://api.motionode.com) unless "motionode use", MOTIONODE_API_URL or --api says otherwise;
24
+ each Motionode keeps its own connection.`;
21
25
 
22
26
  function flags(argv) {
23
27
  const out = { _: [] };
@@ -37,8 +41,8 @@ function flags(argv) {
37
41
  async function main() {
38
42
  const [command, ...rest] = process.argv.slice(2);
39
43
  const args = flags(rest);
40
- const config = Config.load();
41
- if (args.api) config.apiUrl = String(args.api).replace(/\/+$/, '');
44
+ // --api (or MOTIONODE_API_URL) picks the Motionode — and its own saved connection.
45
+ const config = Config.load(typeof args.api === 'string' ? args.api : null);
42
46
  switch (command) {
43
47
  case 'connect':
44
48
  return Commands.connect(config, { apiUrl: args.api || null, name: args.name || null, service: args.service || null, relogin: !!args.relogin });
@@ -56,6 +60,15 @@ async function main() {
56
60
  return Commands.finish(config, args._[0], { force: !!args.force, note: typeof args.note === 'string' ? args.note : null });
57
61
  case 'status':
58
62
  return Commands.status(config);
63
+ case 'use': {
64
+ const api = Config.useApi(args._[0]);
65
+ const next = Config.load();
66
+ console.log(`✓ Using ${api}${next.token ? '' : ' — run "motionode connect" in a repository folder to connect this computer to it'}`);
67
+ return null;
68
+ }
69
+ case 'where':
70
+ console.log(`${config.apiUrl}${config.token ? ` — connected as ${config.user?.name || config.user?.email || 'you'} (${config.machine?.name || 'this computer'})` : ' — not connected'}`);
71
+ return null;
59
72
  case 'logout':
60
73
  return Commands.logout(config);
61
74
  case '--version':
package/lib/commands.js CHANGED
@@ -21,8 +21,8 @@ function openBrowser(url) {
21
21
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
22
22
 
23
23
  /** Device-code login: approve this computer in the browser once. */
24
- async function login(config, { apiUrl = null, name = null } = {}) {
25
- if (apiUrl) config.apiUrl = apiUrl.replace(/\/+$/, '');
24
+ async function login(config, { name = null } = {}) {
25
+ // config.apiUrl was already chosen (--api / MOTIONODE_API_URL / production).
26
26
  const api = client({ ...config, token: null });
27
27
  const machineName = name || os.hostname();
28
28
  const start = await api.post('/device/start', { machineName, platform: `${process.platform}-${process.arch}` });
package/lib/config.js CHANGED
@@ -1,9 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * ~/.motionode/config.json — the machine token, the API it talks to, and which
5
- * local folders are which Motionode repositories. Folder paths never leave this
4
+ * ~/.motionode/ — the machine token, the API it talks to, and which local
5
+ * folders are which Motionode repositories. Folder paths never leave this
6
6
  * machine except as the label shown to their owner in Motionode.
7
+ *
8
+ * One connection per Motionode: production lives in config.json; any other API
9
+ * (MOTIONODE_API_URL or --api, e.g. a local API behind a tunnel) gets its own
10
+ * config.<host>.json, so switching between them never disconnects the other.
7
11
  */
8
12
 
9
13
  const fs = require('fs');
@@ -11,27 +15,67 @@ const os = require('os');
11
15
  const path = require('path');
12
16
 
13
17
  const DIR = process.env.MOTIONODE_HOME || path.join(os.homedir(), '.motionode');
14
- const FILE = path.join(DIR, 'config.json');
15
- const DEFAULT_API = process.env.MOTIONODE_API_URL || 'https://api.motionode.com';
18
+ const PRODUCTION_API = 'https://api.motionode.com';
16
19
 
17
- function load() {
20
+ function normalizeApi(url) {
21
+ return String(url || '').trim().replace(/\/+$/, '') || null;
22
+ }
23
+
24
+ const CURRENT_FILE = path.join(DIR, 'current.json');
25
+
26
+ /** The Motionode chosen with `motionode use` (stays until changed). */
27
+ function savedApi() {
28
+ try {
29
+ return normalizeApi(JSON.parse(fs.readFileSync(CURRENT_FILE, 'utf8')).apiUrl);
30
+ } catch (_e) {
31
+ return null;
32
+ }
33
+ }
34
+
35
+ /** `motionode use <url|production>` — which Motionode every terminal talks to. */
36
+ function useApi(url) {
37
+ const next = String(url || '').trim().toLowerCase() === 'production' ? PRODUCTION_API : normalizeApi(url);
38
+ if (!next || !/^https?:\/\//.test(next)) throw new Error('Give a Motionode API address (https://…) or "production".');
39
+ fs.mkdirSync(DIR, { recursive: true });
40
+ if (next === PRODUCTION_API) {
41
+ try { fs.unlinkSync(CURRENT_FILE); } catch (_e) { /* already production */ }
42
+ } else {
43
+ fs.writeFileSync(CURRENT_FILE, JSON.stringify({ apiUrl: next }, null, 2));
44
+ }
45
+ return next;
46
+ }
47
+
48
+ /** The API this run talks to: --api, then MOTIONODE_API_URL, then `motionode use`, then production. */
49
+ function resolveApi(override = null) {
50
+ return normalizeApi(override) || normalizeApi(process.env.MOTIONODE_API_URL) || savedApi() || PRODUCTION_API;
51
+ }
52
+
53
+ function fileFor(apiUrl) {
54
+ if (normalizeApi(apiUrl) === PRODUCTION_API) return path.join(DIR, 'config.json');
55
+ let host = 'custom';
56
+ try { host = new URL(apiUrl).host; } catch (_e) { /* keep "custom" */ }
57
+ return path.join(DIR, `config.${host.replace(/[^a-zA-Z0-9.-]/g, '_')}.json`);
58
+ }
59
+
60
+ function load(apiOverride = null) {
61
+ const apiUrl = resolveApi(apiOverride);
18
62
  try {
19
- const raw = JSON.parse(fs.readFileSync(FILE, 'utf8'));
63
+ const raw = JSON.parse(fs.readFileSync(fileFor(apiUrl), 'utf8'));
20
64
  return {
21
- apiUrl: raw.apiUrl || DEFAULT_API,
65
+ apiUrl,
22
66
  token: raw.token || null,
23
67
  machine: raw.machine || null,
24
68
  user: raw.user || null,
25
69
  workspaces: Array.isArray(raw.workspaces) ? raw.workspaces : [],
26
70
  };
27
71
  } catch (_e) {
28
- return { apiUrl: DEFAULT_API, token: null, machine: null, user: null, workspaces: [] };
72
+ return { apiUrl, token: null, machine: null, user: null, workspaces: [] };
29
73
  }
30
74
  }
31
75
 
32
76
  function save(config) {
33
77
  fs.mkdirSync(DIR, { recursive: true });
34
- fs.writeFileSync(FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
78
+ fs.writeFileSync(fileFor(config.apiUrl), JSON.stringify(config, null, 2), { mode: 0o600 });
35
79
  }
36
80
 
37
81
  function samePath(a, b) {
@@ -43,4 +87,4 @@ function workspaceForPath(config, dir) {
43
87
  return (config.workspaces || []).find((w) => samePath(w.path, dir)) || null;
44
88
  }
45
89
 
46
- module.exports = { DIR, FILE, DEFAULT_API, load, save, samePath, workspaceForPath };
90
+ module.exports = { DIR, PRODUCTION_API, resolveApi, useApi, fileFor, load, save, samePath, workspaceForPath };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motionode/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Connect your local repositories to Motionode: work tickets in Cursor, VS Code or any editor while Motionode keeps the work tied to the ticket, its git branches, verification and delivery.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {