toolaby 1.1.0 → 1.3.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 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 # once (or run every command through `npx -y toolaby …`)
7
- toolaby login # once: a code to approve in your browser
8
- toolaby wire <tool-id> # in your extension's folder; `toolaby tools` lists your ids
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 another Wall (the default is `https://toolaby.app`).
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
@@ -5,14 +5,20 @@
5
5
  // toolaby wire <tool-id> wire the extension in this folder to your tool — plain, WXT, CRXJS or Plasmo
6
6
  // toolaby create <tool-id> make a new, minimal extension for your tool in <tool-id>/
7
7
  // toolaby tools your tools, with the ids the commands want
8
+ // toolaby webhooks events the six events, with an example of each
9
+ // toolaby webhooks trigger <event> send an example delivery: to your registered endpoints, or with --to straight to a local server
8
10
  // toolaby whoami · logout · help
9
11
  //
12
+ // Test and live, as Stripe's command line has them: every command acts on
13
+ // the TEST Wall (test.toolaby.app) unless told `--live`, so the everyday
14
+ // command is the safe one and going live is a word you type on purpose.
10
15
  // Signed in, the command acts as you and only on your tools, as Clerk's and
11
16
  // Stripe's do: nothing is fetched for a tool your workspaces do not own. The
12
17
  // session lives in ~/.config/toolaby/credentials.json (mode 600), one per
13
- // Wall; `logout` revokes it on the server before forgetting it. TOOLABY_TOKEN
14
- // stands in for the file where there is no home (CI); TOOLABY_WALL for the
15
- // Wall's address (https://toolaby.app unless told).
18
+ // side; a command that needs one and has none signs you in on the spot.
19
+ // `logout` revokes it on the server before forgetting it. TOOLABY_TOKEN
20
+ // stands in for the file where there is no home (CI); TOOLABY_WALL or
21
+ // `--wall <url>` names any other Wall (a laptop's, a self-hosted one).
16
22
  //
17
23
  // The wiring rules — what a folder is and what its manifest needs — are the
18
24
  // platform's own (wall/src/packages/wiring), carried here transpiled in lib/,
@@ -21,10 +27,11 @@ import { mkdirSync, writeFileSync, existsSync, readFileSync, readdirSync, statSy
21
27
  import { join, resolve, dirname } from 'node:path';
22
28
  import { homedir } from 'node:os';
23
29
  import { spawn } from 'node:child_process';
30
+ import { createHmac, randomBytes } from 'node:crypto';
24
31
  import { wireManifest, WORKER_LINES } from './lib/manifest.js';
25
32
  import { detectProject, CODE_FILES, filesToWrite, BUILD_COPY_NOTE } from './lib/project.js';
26
33
 
27
- const VERSION = '1.1.0';
34
+ const VERSION = '1.3.0';
28
35
  const CLIENT_ID = 'toolaby-cli';
29
36
  const argv = process.argv.slice(2);
30
37
  // `--version` and `--help` are commands of their own; every other command is the first bare word.
@@ -33,7 +40,11 @@ const rest = argv.filter((a) => a !== command);
33
40
  const positional = rest.filter((a) => !a.startsWith('--'));
34
41
  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
42
  const flag = (k) => rest.includes(k);
36
- const wall = (opt('--wall') ?? process.env.TOOLABY_WALL ?? 'https://toolaby.app').replace(/\/$/, '');
43
+ const WALLS = { test: 'https://test.toolaby.app', live: 'https://toolaby.app' };
44
+ const live = flag('--live');
45
+ const wall = (opt('--wall') ?? process.env.TOOLABY_WALL ?? (live ? WALLS.live : WALLS.test)).replace(/\/$/, '');
46
+ /** The side, for every message: what a developer must never be unsure of. */
47
+ const side = wall === WALLS.live ? 'Live' : wall === WALLS.test ? 'Test' : wall;
37
48
  const out = (s = '') => console.log(s);
38
49
  const fail = (s) => { console.error(`\n ${s}\n`); process.exit(1); };
39
50
 
@@ -72,7 +83,7 @@ async function login() {
72
83
  const { device_code, user_code, verification_uri, verification_uri_complete, interval = 5, expires_in = 600 } = start.json;
73
84
  const link = verification_uri_complete || `${verification_uri}?user_code=${encodeURIComponent(user_code)}`;
74
85
  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`);
86
+ 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
87
  openBrowser(page);
77
88
  let every = Math.max(1, Number(interval) || 5) * 1000;
78
89
  const until = Date.now() + Number(expires_in) * 1000;
@@ -88,7 +99,7 @@ async function login() {
88
99
  const all = readCredentials();
89
100
  all[wall] = { token, email, at: new Date().toISOString() };
90
101
  writeCredentials(all);
91
- out(`\n\n Signed in${email ? ` as ${email}` : ''}.\n`);
102
+ out(`\n\n Signed in${email ? ` as ${email}` : ''} · ${side}.\n`);
92
103
  return token;
93
104
  }
94
105
  const why = poll.json.error;
@@ -107,11 +118,11 @@ async function tokenOrLogin() {
107
118
 
108
119
  async function whoami() {
109
120
  const token = storedToken();
110
- if (!token) fail(`Not signed in to ${wall}. Run \`toolaby login\`.`);
121
+ if (!token) fail(`Not signed in to ${side} (${wall}). Run \`toolaby login${live ? ' --live' : ''}\`.`);
111
122
  const me = await api('/api/cli/me', { token });
112
123
  if (me.status === 401) fail(`The session for ${wall} is no longer valid. Run \`toolaby login\`.`);
113
124
  if (!me.ok) fail(`The Wall answered HTTP ${me.status}.`);
114
- out(`\n ${me.json.email} · ${wall}\n`);
125
+ out(`\n ${me.json.email} · ${side} · ${wall}\n`);
115
126
  for (const w of me.json.workspaces) out(` ${w.name} (${w.slug}, ${w.role}) ${w.site}`);
116
127
  out();
117
128
  }
@@ -119,22 +130,22 @@ async function whoami() {
119
130
  async function logout() {
120
131
  const all = readCredentials();
121
132
  const entry = all[wall];
122
- if (!entry && !process.env.TOOLABY_TOKEN) { out(`\n Not signed in to ${wall}.\n`); return; }
133
+ if (!entry && !process.env.TOOLABY_TOKEN) { out(`\n Not signed in to ${side} (${wall}).\n`); return; }
123
134
  // Revoked on the server first; forgotten here whatever the server said — but the difference is reported.
124
135
  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
136
  delete all[wall];
126
137
  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`);
138
+ 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
139
  if (process.env.TOOLABY_TOKEN) out(' TOOLABY_TOKEN is set in this shell and still authenticates.\n');
129
140
  }
130
141
 
131
142
  async function tools() {
132
143
  const token = await tokenOrLogin();
133
144
  const res = await api('/api/cli/tools', { token });
134
- if (res.status === 401) fail(`The session for ${wall} is no longer valid. Run \`toolaby login\`.`);
145
+ if (res.status === 401) fail(`The session for ${side} is no longer valid. Run \`toolaby login${live ? ' --live' : ''}\`.`);
135
146
  if (!res.ok) fail(`The Wall answered HTTP ${res.status}.`);
136
- if (!res.json.tools.length) { out('\n No tools yet. Make one in the dashboard, then `toolaby wire <tool-id>` in your extension\'s folder.\n'); return; }
137
- out();
147
+ 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; }
148
+ out(`\n ${side}\n`);
138
149
  for (const t of res.json.tools) out(` ${t.slug.padEnd(40)} ${t.name}${t.bundle ? ' (bundle)' : ''} ${t.site}`);
139
150
  out();
140
151
  }
@@ -212,7 +223,7 @@ async function wire() {
212
223
  ownWorker = { file: project.worker, classic: false };
213
224
  }
214
225
 
215
- out(`\n ${plan.name} → ${dir}${project.kind === 'plain' ? '' : ` (${project.label})`}\n`);
226
+ out(`\n ${plan.name} → ${dir}${project.kind === 'plain' ? '' : ` (${project.label})`} · ${side}${side === 'Test' ? ' (the tk_test_ key; `--live` for the store build)' : ''}\n`);
216
227
  out(` Written: ${targets.join(', ')}${did.length ? `,\n ${did.join(',\n ')}` : ''}.\n`);
217
228
  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
229
  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`);
@@ -238,22 +249,82 @@ async function create() {
238
249
  out(`\n ${plan.name} → ${dir}\n\n 1. chrome://extensions → Developer mode → Load unpacked → ${dir}\n 2. Click the icon: the popup shows what the Free plan holds; Buy opens ${plan.site}/tools/${slug}\n\n Edit background.js: your work goes behind toolaby.gate().\n`);
239
250
  }
240
251
 
252
+ // ── webhooks: the catalogue, and a delivery on demand ───────────────────────────────────────────────────────────────
253
+ //
254
+ // `trigger <event>` sends the catalogue's example of the event through the
255
+ // Wall to every endpoint the workspace registered — signed, logged, retried,
256
+ // as a real one. `trigger <event> --to <url>` skips the Wall: the command
257
+ // signs the example itself, Standard Webhooks style, with a secret you name
258
+ // (or one it prints) and POSTs it to the URL — a server on localhost, no
259
+ // tunnel, no deploy — so a handler can be written and run before anything
260
+ // is registered.
261
+ const DEV_SECRET = 'whsec_dG9vbGFieS1kZXYtd2ViaG9vay1zZWNyZXQtMDAwMQ==';
262
+ async function catalogue() {
263
+ const res = await api('/api/cli/webhooks/events', { token: await tokenOrLogin() });
264
+ if (res.status === 401) fail(`The session for ${side} is no longer valid. Run \`toolaby login${live ? ' --live' : ''}\`.`);
265
+ if (!res.ok) fail(`The Wall answered HTTP ${res.status}.`);
266
+ return res.json.events;
267
+ }
268
+ async function webhooks() {
269
+ const sub = positional[0];
270
+ if (sub === 'events') {
271
+ const events = await catalogue();
272
+ out(`\n ${side}\n`);
273
+ for (const e of events) out(` ${e.type.padEnd(26)} ${e.description}`);
274
+ out(`\n toolaby webhooks trigger <event> → your registered endpoints\n toolaby webhooks trigger <event> --to <url> → a local server, signed with a secret you name\n`);
275
+ return;
276
+ }
277
+ if (sub === 'trigger') {
278
+ const type = positional[1];
279
+ if (!type) fail('usage: toolaby webhooks trigger <event> [--to <url>] [--secret <whsec_…>] — `toolaby webhooks events` lists them');
280
+ const to = opt('--to');
281
+ if (!to) {
282
+ const res = await api('/api/cli/webhooks/trigger', { method: 'POST', body: { type }, token: await tokenOrLogin() });
283
+ if (!res.ok) fail(res.json.message ?? `The Wall answered HTTP ${res.status}.`);
284
+ out(`\n Sent ${res.json.sent} to every endpoint of ${res.json.workspace} · ${side}. Its id is ${res.json.id}; the delivery log is under Configure → API.\n`);
285
+ return;
286
+ }
287
+ const events = await catalogue();
288
+ const event = events.find((e) => e.type === type);
289
+ if (!event) fail(`No event "${type}". \`toolaby webhooks events\` lists them.`);
290
+ const secret = opt('--secret') ?? DEV_SECRET;
291
+ if (!/^whsec_[A-Za-z0-9+/=]+$/.test(secret)) fail('--secret must be a Standard Webhooks secret: whsec_ followed by base64.');
292
+ const id = `msg_${randomBytes(12).toString('base64url')}`;
293
+ const timestamp = Math.floor(Date.now() / 1000);
294
+ const body = JSON.stringify({ id: `evt_${type.replace('.', '_')}_${Date.now().toString(36)}`, type, created: timestamp, livemode: false, data: event.example });
295
+ const signature = 'v1,' + createHmac('sha256', Buffer.from(secret.slice(6), 'base64')).update(`${id}.${timestamp}.${body}`).digest('base64');
296
+ let res;
297
+ try {
298
+ res = await fetch(to, { method: 'POST', headers: { 'Content-Type': 'application/json', 'webhook-id': id, 'webhook-timestamp': String(timestamp), 'webhook-signature': signature, 'User-Agent': `toolaby-cli/${VERSION}` }, body });
299
+ } catch (e) { fail(`Could not reach ${to}: ${e.message}`); }
300
+ const text = (await res.text()).slice(0, 200);
301
+ out(`\n POST ${to} → ${res.status}${text ? ` ${text.replace(/\s+/g, ' ')}` : ''}`);
302
+ out(` ${type}, signed with ${opt('--secret') ? 'your secret' : `${DEV_SECRET} — set WALL_WEBHOOK_SECRET to it to verify`}.\n`);
303
+ if (res.status >= 300) out(' A real delivery would be retried until your server answers 2xx.\n');
304
+ return;
305
+ }
306
+ fail('usage: toolaby webhooks events | trigger <event> [--to <url>] [--secret <whsec_…>]');
307
+ }
308
+
241
309
  function help() {
242
310
  out(`
243
311
  toolaby — the Toolaby command line (${VERSION})
244
312
 
245
- toolaby login sign in: a code to approve in the browser
246
- toolaby wire <tool-id> wire the extension in this folder to your tool
313
+ toolaby wire <tool-id> wire the extension in this folder to your tool, on Test
314
+ toolaby wire <tool-id> --live the same, on Live: the store build
247
315
  --into <folder> another folder --force replace the platform's files
248
316
  toolaby create <tool-id> a new, minimal extension for your tool, in <tool-id>/ --dir <folder>
249
317
  toolaby tools your tools, with their ids
250
- toolaby whoami · logout
318
+ toolaby webhooks events the six events, with an example of each
319
+ toolaby webhooks trigger <event> an example delivery to your endpoints; --to <url> sends it to a local server instead
320
+ toolaby login · whoami · logout the session, per side; a command that needs one signs you in itself
251
321
 
252
- --wall <url> the Wall to talk to (https://toolaby.app; TOOLABY_WALL) TOOLABY_TOKEN a session, for CI
322
+ Every command acts on Test (test.toolaby.app) unless told --live (toolaby.app).
323
+ --wall <url> or TOOLABY_WALL names any other Wall; TOOLABY_TOKEN a session, for CI.
253
324
  `);
254
325
  }
255
326
 
256
- const commands = { login: async () => { await login(); }, logout, whoami, tools, wire, create, help, '--help': help, '-h': help, '--version': () => out(VERSION), '-v': () => out(VERSION) };
327
+ const commands = { login: async () => { await login(); }, logout, whoami, tools, wire, create, webhooks, help, '--help': help, '-h': help, '--version': () => out(VERSION), '-v': () => out(VERSION) };
257
328
  const run = commands[command];
258
329
  if (!run) { console.error(`\n Unknown command "${command}".`); help(); process.exit(1); }
259
330
  Promise.resolve().then(run).catch((e) => fail(e.message));
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 and the platform knows it');
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/lib/project.js CHANGED
@@ -3,12 +3,15 @@
3
3
  export const BUILD_COPY_NOTE = 'A build ships this extension: make sure it copies the platform\'s files to its output — toolaby.js, toolaby.config.js, toolaby-client.module.js, paywall.js, paywall.css and toolaby-popup.* — the way it copies your popup.html.';
4
4
  /** Always written, where the build imports from: the client, its config and types, and the panel a popup of the developer's own can draw. */
5
5
  export const CODE_FILES = ['toolaby.config.js', 'toolaby.js', 'toolaby-client.module.js', 'toolaby.d.ts', 'paywall.js', 'paywall.css'];
6
+ /** Beside the client when the build generates the manifest: the fragment it spreads in, derived from the key (boilerplate/toolaby.manifest.js). */
7
+ export const MANIFEST_HELPER = ['toolaby.manifest.js', 'toolaby.manifest.d.ts'];
6
8
  /** The Wall's popup, where the build copies files as they are; it imports the panel beside it, so the panel goes there too when that is elsewhere. */
7
9
  export const POPUP_FILES = ['toolaby-popup.html', 'toolaby-popup.js', 'toolaby-popup.css'];
8
10
  export const PANEL_FILES = ['paywall.js', 'paywall.css'];
9
11
  /** Every file a wiring writes, with its path: the code files, and — with a popup to stand in front of — the Wall's popup and, beside it, the panel. */
10
12
  export const filesToWrite = (p, appPopup) => [
11
13
  ...CODE_FILES.map((f) => `${p.codeDir}${f}`),
14
+ ...(p.manifestFile ? [] : MANIFEST_HELPER.map((f) => `${p.codeDir}${f}`)),
12
15
  ...(appPopup ? [...POPUP_FILES, ...(p.popupDir !== p.codeDir ? PANEL_FILES : [])].map((f) => `${p.popupDir}${f}`) : []),
13
16
  ];
14
17
  /** The import of toolaby.js from a file at `from`, both relative to the root. */
@@ -21,7 +24,6 @@ const dep = (pkg, name) => !!(pkg?.dependencies?.[name] || pkg?.devDependencies?
21
24
  const first = (has, candidates) => candidates.find(has) ?? null;
22
25
  export function detectProject(o) {
23
26
  const { has, packageJson: pkg, manifest, site } = o;
24
- const keyLine = o.manifestKey ? `key: '${o.manifestKey}',` : null;
25
27
  const scripts = pkg?.scripts ?? {};
26
28
  const built = !!scripts.build || has('dist') || has('build');
27
29
  const wxt = dep(pkg, 'wxt') || !!first(has, ['wxt.config.ts', 'wxt.config.js', 'wxt.config.mts', 'wxt.config.mjs']);
@@ -36,14 +38,11 @@ export function detectProject(o) {
36
38
  const workerFile = worker ?? `${src}entrypoints/background.ts`;
37
39
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\nexport default defineBackground(() => {\n toolaby.startBackground();\n});\n`;
38
40
  const code = [
39
- `// wxt.config.ts — what the Wall needs in the manifest${appPopup ? ', and its popup in front of yours' : ''}`,
41
+ `// wxt.config.ts — the Wall's needs in the manifest come from toolaby.config.js${appPopup ? ', and its popup goes in front of yours' : ''}`,
42
+ `import { manifest as toolaby } from './toolaby.manifest.js';`,
43
+ ``,
40
44
  `export default defineConfig({`,
41
- ` manifest: {`,
42
- ...(keyLine ? [` ${keyLine} // the extension's id, known to the platform`] : []),
43
- ` permissions: ['storage'],`,
44
- ` host_permissions: ['${site}/*'],`,
45
- ` externally_connectable: { matches: ['${site}/*'] },`,
46
- ` },`,
45
+ ` manifest: { ...toolaby }, // the extension's id, the storage permission, the Wall's address`,
47
46
  ...(appPopup ? [
48
47
  ` hooks: {`,
49
48
  ` // WXT names your popup entrypoint after merging the manifest above; the hook runs after that.`,
@@ -85,11 +84,10 @@ export function detectProject(o) {
85
84
  const workerFile = worker ?? 'src/background.ts';
86
85
  const workerLines = `import { toolaby } from '${importPath(workerFile)}';\n\ntoolaby.startBackground();\n`;
87
86
  const code = [
88
- `// ${configFile ?? 'manifest.config.ts'} — add inside defineManifest({ … })`,
89
- ...(keyLine && !/\bkey:/.test(source) ? [`${keyLine} // the extension's id, known to the platform`] : []),
90
- `permissions: ['storage'],`,
91
- `host_permissions: ['${site}/*'],`,
92
- `externally_connectable: { matches: ['${site}/*'] },`,
87
+ `// ${configFile ?? 'manifest.config.ts'} — the Wall's needs come from toolaby.config.js: import once, spread inside defineManifest({ … })`,
88
+ `import { manifest as toolaby } from '${importPath(configFile ?? 'manifest.config.ts').replace(/toolaby\.js$/, 'toolaby.manifest.js')}';`,
89
+ ``,
90
+ `...toolaby, // the extension's id, the storage permission, the Wall's address`,
93
91
  ...(appPopup ? [`action: { default_popup: 'toolaby-popup.html' },`] : []),
94
92
  ...(worker ? [] : [`background: { service_worker: '${workerFile}', type: 'module' },`]),
95
93
  ].join('\n');
package/package.json CHANGED
@@ -1,13 +1,30 @@
1
1
  {
2
2
  "name": "toolaby",
3
- "version": "1.1.0",
3
+ "version": "1.3.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
- "bin": { "toolaby": "./bin.mjs" },
7
- "files": ["bin.mjs", "lib", "README.md"],
8
- "engines": { "node": ">=18" },
9
- "keywords": ["chrome-extension", "toolaby", "licensing", "cli"],
6
+ "bin": {
7
+ "toolaby": "bin.mjs"
8
+ },
9
+ "files": [
10
+ "bin.mjs",
11
+ "lib",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "chrome-extension",
19
+ "toolaby",
20
+ "licensing",
21
+ "cli"
22
+ ],
10
23
  "homepage": "https://toolaby.app",
11
- "repository": { "type": "git", "url": "https://github.com/miltostave/Toolaby.git", "directory": "packages/toolaby-cli" },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/miltostave/Toolaby.git",
27
+ "directory": "packages/toolaby-cli"
28
+ },
12
29
  "license": "MIT"
13
30
  }