toolaby 1.1.0 → 1.2.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/README.md +6 -4
- package/bin.mjs +28 -19
- package/lib/manifest.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
The Toolaby command line. It puts the Wall — sign-in, a Free plan, paid plans, licences and subscriptions — into a Chrome extension you already have, or makes a new one for you.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npm i -g toolaby
|
|
7
|
-
toolaby
|
|
8
|
-
toolaby wire <tool-id>
|
|
6
|
+
npm i -g toolaby # once (or run every command through `npx -y toolaby …`)
|
|
7
|
+
toolaby wire <tool-id> # in your extension's folder, on Test; signs you in first if it must
|
|
8
|
+
toolaby wire <tool-id> --live # the same, on Live: the store build
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Every command acts on the test Wall (`test.toolaby.app`) unless told `--live` (`toolaby.app`), as Stripe's command line does. `toolaby tools` lists your ids on either side.
|
|
12
|
+
|
|
11
13
|
## What it does
|
|
12
14
|
|
|
13
15
|
**`toolaby login`** signs you in. It shows a code and opens the Toolaby dashboard; you approve the code there — as the account the dashboard is signed in to — and the command is signed in as that account from then on. It works the way GitHub's and Stripe's command lines sign in, and only ever acts on that account's own tools.
|
|
@@ -27,6 +29,6 @@ If the extension is built with WXT, CRXJS or Plasmo, the command puts the same f
|
|
|
27
29
|
## Where things live
|
|
28
30
|
|
|
29
31
|
- Your session: `~/.config/toolaby/credentials.json`, readable by you alone (mode 600). `toolaby logout` deletes it after revoking it.
|
|
30
|
-
- `TOOLABY_TOKEN` stands in for the file where there is no home directory (CI). `TOOLABY_WALL` or `--wall <url>` names
|
|
32
|
+
- The session is per side. `TOOLABY_TOKEN` stands in for the file where there is no home directory (CI). `TOOLABY_WALL` or `--wall <url>` names any other Wall.
|
|
31
33
|
|
|
32
34
|
The files the command writes into your extension contain nothing secret: your tool's id, your Wall's address and the *public* key your extension verifies licences with — the same things every copy of your extension carries once it is published.
|
package/bin.mjs
CHANGED
|
@@ -7,12 +7,16 @@
|
|
|
7
7
|
// toolaby tools your tools, with the ids the commands want
|
|
8
8
|
// toolaby whoami · logout · help
|
|
9
9
|
//
|
|
10
|
+
// Test and live, as Stripe's command line has them: every command acts on
|
|
11
|
+
// the TEST Wall (test.toolaby.app) unless told `--live`, so the everyday
|
|
12
|
+
// command is the safe one and going live is a word you type on purpose.
|
|
10
13
|
// Signed in, the command acts as you and only on your tools, as Clerk's and
|
|
11
14
|
// Stripe's do: nothing is fetched for a tool your workspaces do not own. The
|
|
12
15
|
// session lives in ~/.config/toolaby/credentials.json (mode 600), one per
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
+
// side; a command that needs one and has none signs you in on the spot.
|
|
17
|
+
// `logout` revokes it on the server before forgetting it. TOOLABY_TOKEN
|
|
18
|
+
// stands in for the file where there is no home (CI); TOOLABY_WALL or
|
|
19
|
+
// `--wall <url>` names any other Wall (a laptop's, a self-hosted one).
|
|
16
20
|
//
|
|
17
21
|
// The wiring rules — what a folder is and what its manifest needs — are the
|
|
18
22
|
// platform's own (wall/src/packages/wiring), carried here transpiled in lib/,
|
|
@@ -24,7 +28,7 @@ import { spawn } from 'node:child_process';
|
|
|
24
28
|
import { wireManifest, WORKER_LINES } from './lib/manifest.js';
|
|
25
29
|
import { detectProject, CODE_FILES, filesToWrite, BUILD_COPY_NOTE } from './lib/project.js';
|
|
26
30
|
|
|
27
|
-
const VERSION = '1.
|
|
31
|
+
const VERSION = '1.2.0';
|
|
28
32
|
const CLIENT_ID = 'toolaby-cli';
|
|
29
33
|
const argv = process.argv.slice(2);
|
|
30
34
|
// `--version` and `--help` are commands of their own; every other command is the first bare word.
|
|
@@ -33,7 +37,11 @@ const rest = argv.filter((a) => a !== command);
|
|
|
33
37
|
const positional = rest.filter((a) => !a.startsWith('--'));
|
|
34
38
|
const opt = (k, d = null) => { const i = rest.indexOf(k); return i >= 0 && rest[i + 1] && !rest[i + 1].startsWith('--') ? rest[i + 1] : d; };
|
|
35
39
|
const flag = (k) => rest.includes(k);
|
|
36
|
-
const
|
|
40
|
+
const WALLS = { test: 'https://test.toolaby.app', live: 'https://toolaby.app' };
|
|
41
|
+
const live = flag('--live');
|
|
42
|
+
const wall = (opt('--wall') ?? process.env.TOOLABY_WALL ?? (live ? WALLS.live : WALLS.test)).replace(/\/$/, '');
|
|
43
|
+
/** The side, for every message: what a developer must never be unsure of. */
|
|
44
|
+
const side = wall === WALLS.live ? 'Live' : wall === WALLS.test ? 'Test' : wall;
|
|
37
45
|
const out = (s = '') => console.log(s);
|
|
38
46
|
const fail = (s) => { console.error(`\n ${s}\n`); process.exit(1); };
|
|
39
47
|
|
|
@@ -72,7 +80,7 @@ async function login() {
|
|
|
72
80
|
const { device_code, user_code, verification_uri, verification_uri_complete, interval = 5, expires_in = 600 } = start.json;
|
|
73
81
|
const link = verification_uri_complete || `${verification_uri}?user_code=${encodeURIComponent(user_code)}`;
|
|
74
82
|
const page = link.startsWith('http') ? link : `${wall}${link}`;
|
|
75
|
-
out(`\n Sign in to the Wall to continue.\n\n Open ${page}\n Code ${user_code} (approve it there — it expires in ${Math.round(expires_in / 60)} minutes)\n`);
|
|
83
|
+
out(`\n Sign in to the ${side} Wall to continue.\n\n Open ${page}\n Code ${user_code} (approve it there — it expires in ${Math.round(expires_in / 60)} minutes)\n`);
|
|
76
84
|
openBrowser(page);
|
|
77
85
|
let every = Math.max(1, Number(interval) || 5) * 1000;
|
|
78
86
|
const until = Date.now() + Number(expires_in) * 1000;
|
|
@@ -88,7 +96,7 @@ async function login() {
|
|
|
88
96
|
const all = readCredentials();
|
|
89
97
|
all[wall] = { token, email, at: new Date().toISOString() };
|
|
90
98
|
writeCredentials(all);
|
|
91
|
-
out(`\n\n Signed in${email ? ` as ${email}` : ''}.\n`);
|
|
99
|
+
out(`\n\n Signed in${email ? ` as ${email}` : ''} · ${side}.\n`);
|
|
92
100
|
return token;
|
|
93
101
|
}
|
|
94
102
|
const why = poll.json.error;
|
|
@@ -107,11 +115,11 @@ async function tokenOrLogin() {
|
|
|
107
115
|
|
|
108
116
|
async function whoami() {
|
|
109
117
|
const token = storedToken();
|
|
110
|
-
if (!token) fail(`Not signed in to ${wall}. Run \`toolaby login\`.`);
|
|
118
|
+
if (!token) fail(`Not signed in to ${side} (${wall}). Run \`toolaby login${live ? ' --live' : ''}\`.`);
|
|
111
119
|
const me = await api('/api/cli/me', { token });
|
|
112
120
|
if (me.status === 401) fail(`The session for ${wall} is no longer valid. Run \`toolaby login\`.`);
|
|
113
121
|
if (!me.ok) fail(`The Wall answered HTTP ${me.status}.`);
|
|
114
|
-
out(`\n ${me.json.email} · ${wall}\n`);
|
|
122
|
+
out(`\n ${me.json.email} · ${side} · ${wall}\n`);
|
|
115
123
|
for (const w of me.json.workspaces) out(` ${w.name} (${w.slug}, ${w.role}) ${w.site}`);
|
|
116
124
|
out();
|
|
117
125
|
}
|
|
@@ -119,22 +127,22 @@ async function whoami() {
|
|
|
119
127
|
async function logout() {
|
|
120
128
|
const all = readCredentials();
|
|
121
129
|
const entry = all[wall];
|
|
122
|
-
if (!entry && !process.env.TOOLABY_TOKEN) { out(`\n Not signed in to ${wall}.\n`); return; }
|
|
130
|
+
if (!entry && !process.env.TOOLABY_TOKEN) { out(`\n Not signed in to ${side} (${wall}).\n`); return; }
|
|
123
131
|
// Revoked on the server first; forgotten here whatever the server said — but the difference is reported.
|
|
124
132
|
const res = await api('/api/dev-auth/sign-out', { method: 'POST', body: {}, token: entry?.token ?? process.env.TOOLABY_TOKEN }).catch(() => ({ ok: false, status: 0 }));
|
|
125
133
|
delete all[wall];
|
|
126
134
|
writeCredentials(all);
|
|
127
|
-
out(res.ok ? `\n Signed out of ${wall}: the session was revoked and forgotten.\n` : `\n The session was forgotten here, but the Wall could not be reached to revoke it (HTTP ${res.status}); it expires on its own, or sign it out from the dashboard.\n`);
|
|
135
|
+
out(res.ok ? `\n Signed out of ${side} (${wall}): the session was revoked and forgotten.\n` : `\n The session was forgotten here, but the Wall could not be reached to revoke it (HTTP ${res.status}); it expires on its own, or sign it out from the dashboard.\n`);
|
|
128
136
|
if (process.env.TOOLABY_TOKEN) out(' TOOLABY_TOKEN is set in this shell and still authenticates.\n');
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
async function tools() {
|
|
132
140
|
const token = await tokenOrLogin();
|
|
133
141
|
const res = await api('/api/cli/tools', { token });
|
|
134
|
-
if (res.status === 401) fail(`The session for ${
|
|
142
|
+
if (res.status === 401) fail(`The session for ${side} is no longer valid. Run \`toolaby login${live ? ' --live' : ''}\`.`);
|
|
135
143
|
if (!res.ok) fail(`The Wall answered HTTP ${res.status}.`);
|
|
136
|
-
if (!res.json.tools.length) { out(
|
|
137
|
-
out();
|
|
144
|
+
if (!res.json.tools.length) { out(`\n No tools on ${side} yet. Make one in the dashboard, then \`toolaby wire <tool-id>${live ? ' --live' : ''}\` in your extension's folder.\n`); return; }
|
|
145
|
+
out(`\n ${side}\n`);
|
|
138
146
|
for (const t of res.json.tools) out(` ${t.slug.padEnd(40)} ${t.name}${t.bundle ? ' (bundle)' : ''} ${t.site}`);
|
|
139
147
|
out();
|
|
140
148
|
}
|
|
@@ -212,7 +220,7 @@ async function wire() {
|
|
|
212
220
|
ownWorker = { file: project.worker, classic: false };
|
|
213
221
|
}
|
|
214
222
|
|
|
215
|
-
out(`\n ${plan.name} → ${dir}${project.kind === 'plain' ? '' : ` (${project.label})`}\n`);
|
|
223
|
+
out(`\n ${plan.name} → ${dir}${project.kind === 'plain' ? '' : ` (${project.label})`} · ${side}${side === 'Test' ? ' (the tk_test_ key; `--live` for the store build)' : ''}\n`);
|
|
216
224
|
out(` Written: ${targets.join(', ')}${did.length ? `,\n ${did.join(',\n ')}` : ''}.\n`);
|
|
217
225
|
for (const sn of project.snippets) out(` Add to ${sn.file} — yours to paste, the manifest being generated:\n\n${sn.code.split('\n').map((l) => ` ${l}`).join('\n')}\n`);
|
|
218
226
|
if (ownWorker) out(` Your background, ${ownWorker.file}: add${ownWorker.classic ? ' — after giving "background" a "type": "module" in manifest.json (importScripts() is not available in a module worker)' : ''}:\n\n${project.workerLines.trimEnd().split('\n').map((l) => ` ${l}`).join('\n')}\n`);
|
|
@@ -242,14 +250,15 @@ function help() {
|
|
|
242
250
|
out(`
|
|
243
251
|
toolaby — the Toolaby command line (${VERSION})
|
|
244
252
|
|
|
245
|
-
toolaby
|
|
246
|
-
toolaby wire <tool-id>
|
|
253
|
+
toolaby wire <tool-id> wire the extension in this folder to your tool, on Test
|
|
254
|
+
toolaby wire <tool-id> --live the same, on Live: the store build
|
|
247
255
|
--into <folder> another folder --force replace the platform's files
|
|
248
256
|
toolaby create <tool-id> a new, minimal extension for your tool, in <tool-id>/ --dir <folder>
|
|
249
257
|
toolaby tools your tools, with their ids
|
|
250
|
-
toolaby whoami · logout
|
|
258
|
+
toolaby login · whoami · logout the session, per side; a command that needs one signs you in itself
|
|
251
259
|
|
|
252
|
-
|
|
260
|
+
Every command acts on Test (test.toolaby.app) unless told --live (toolaby.app).
|
|
261
|
+
--wall <url> or TOOLABY_WALL names any other Wall; TOOLABY_TOKEN a session, for CI.
|
|
253
262
|
`);
|
|
254
263
|
}
|
|
255
264
|
|
package/lib/manifest.js
CHANGED
|
@@ -22,7 +22,7 @@ export function wireManifest(input, o) {
|
|
|
22
22
|
const own = typeof manifest.key === 'string' && manifest.key.trim() ? manifest.key.trim() : null;
|
|
23
23
|
if (!own && o.manifestKey) {
|
|
24
24
|
manifest.key = o.manifestKey;
|
|
25
|
-
did.push('a "key", so the extension keeps one id
|
|
25
|
+
did.push('a "key", so the extension keeps one id across reloads and machines');
|
|
26
26
|
}
|
|
27
27
|
const manifestKey = own ?? o.manifestKey ?? null;
|
|
28
28
|
const add = (list, value) => { const arr = Array.isArray(list) ? [...list] : []; if (!arr.includes(value))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolaby",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "The Toolaby command line: sign in once, then wire any Chrome extension to your tool on the Wall — sign-in, the Free plan, licences and subscriptions — or start a new one.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": { "toolaby": "./bin.mjs" },
|