toolaby 1.8.1 → 1.8.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.
- package/bin.mjs +14 -4
- package/lib/manifest.js +24 -3
- package/lib/test-address.js +43 -0
- package/package.json +1 -1
package/bin.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import { runMcp } from './lib/mcp.js';
|
|
|
44
44
|
import { runCheck } from './lib/check.js';
|
|
45
45
|
import { inside, signInPage, devicePolling, opener, privateKind, privateContent, privateCopies, googleKey, projectRoot, gitDirectory, gitObject, gitObjects, gitFile, notAnImage, unpacked, COMPRESSED, publicLinks, privateLinks, oneLine, fetchBounded, fetchHere, readBounded, readText, SCAFFOLD_FILES } from './lib/safe.js';
|
|
46
46
|
|
|
47
|
-
const VERSION = '1.8.
|
|
47
|
+
const VERSION = '1.8.3';
|
|
48
48
|
const CLIENT_ID = 'toolaby-cli';
|
|
49
49
|
const argv = process.argv.slice(2);
|
|
50
50
|
// `--version` and `--help` are commands of their own; every other command is the first bare word.
|
|
@@ -312,7 +312,7 @@ async function wire() {
|
|
|
312
312
|
// The background the manifest names, read from inside the folder only: whether it starts the Wall, or is the Wall's.
|
|
313
313
|
const namedWorker = workerIn(manifest) ?? '';
|
|
314
314
|
const workerSource = namedWorker && has(namedWorker) && !inside(dir, namedWorker).why ? readOr(join(dir, namedWorker), '') : null;
|
|
315
|
-
const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, has, remembered, rememberedSidePanel, manifestKey, ownInFront, workerSource }) : null;
|
|
315
|
+
const wiring = project.manifestFile && manifest ? wireManifest(manifest, { site, apex: new URL(wall).host, has, remembered, rememberedSidePanel, manifestKey, ownInFront, workerSource }) : null;
|
|
316
316
|
const appPopup = wiring ? wiring.appPopup : project.wrapper ? (project.appPopup ?? remembered) : null;
|
|
317
317
|
const appSidePanel = wiring ? wiring.appSidePanel : project.wrapper ? (project.appSidePanel ?? rememberedSidePanel) : null;
|
|
318
318
|
const targets = filesToWrite(project, appPopup, appSidePanel, { popup: !!wiring?.ownPopup, sidePanel: !!wiring?.ownSidePanel });
|
|
@@ -743,6 +743,15 @@ function pack() {
|
|
|
743
743
|
const hosts = manifest.host_permissions ?? []; const ec = manifest.externally_connectable?.matches ?? [];
|
|
744
744
|
if (!hosts.includes(sitePattern(site)) || !ec.includes(sitePattern(site))) problems.push(`The manifest does not name ${oneLine(sitePattern(site))} in both host_permissions and externally_connectable: sign-in and the purchase would not reach the extension. \`npx -y toolaby@latest wire <tool-id> --live --force\` adds them.`);
|
|
745
745
|
}
|
|
746
|
+
// A host under the Wall's address that no key in the build names: another workspace's, or the other side's Test
|
|
747
|
+
// address, left by an earlier wiring or pasted by hand. The store build asks its buyers for it at install, and gains
|
|
748
|
+
// nothing: the extension talks to its own key's host alone (30 Sep 2026).
|
|
749
|
+
const walls = [...new Set(['toolaby.app', new URL(wall).hostname])];
|
|
750
|
+
const hostOf = (m) => /^(?:\*|https?|wss?):\/\/([^/]+)\//.exec(m)?.[1]?.replace(/^\*\./, '').toLowerCase() ?? null;
|
|
751
|
+
const keys = new Set([...sites].map(sitePattern));
|
|
752
|
+
const strangers = [...new Set([...(manifest.host_permissions ?? []), ...(manifest.externally_connectable?.matches ?? [])])]
|
|
753
|
+
.filter((m) => typeof m === 'string' && !keys.has(m) && walls.some((w) => { const h = hostOf(m); return !!h && (h === w || h.endsWith(`.${w}`)); }));
|
|
754
|
+
if (strangers.length && !flag('--allow-wall-hosts')) problems.push(`The manifest names ${strangers.map(oneLine).join(', ')}: the Wall's, and no key's in this build — another workspace, or the other side's Test address. A store build asks its buyers for ${strangers.length > 1 ? 'these' : 'it'} at install for nothing: take ${strangers.length > 1 ? 'them' : 'it'} out of host_permissions and externally_connectable. (--allow-wall-hosts packs it anyway.)`);
|
|
746
755
|
if (problems.length) fail(`Not packed:\n\n${problems.map((p) => ` · ${p}`).join('\n\n')}`);
|
|
747
756
|
|
|
748
757
|
const hadKey = typeof manifest.key === 'string';
|
|
@@ -785,7 +794,7 @@ function mcp() {
|
|
|
785
794
|
}
|
|
786
795
|
|
|
787
796
|
async function check() {
|
|
788
|
-
const passed = await runCheck({ mark: (tone, text) => say(` ${tone === 'ok' ? green('✓') : tone === 'warn' ? yellow('!') : red('✗')} ${plain(text)}`), dir: opt('--into') ?? positional[0] ?? '.', out, api: (path, o = {}) => api(path, o), token: storedToken, detectProject, wireManifest, listTree, readJson, browser: flag('--browser') });
|
|
797
|
+
const passed = await runCheck({ mark: (tone, text) => say(` ${tone === 'ok' ? green('✓') : tone === 'warn' ? yellow('!') : red('✗')} ${plain(text)}`), dir: opt('--into') ?? positional[0] ?? '.', out, api: (path, o = {}) => api(path, o), token: storedToken, detectProject, wireManifest: (m, o) => wireManifest(m, { apex: new URL(wall).host, ...o }), listTree, readJson, browser: flag('--browser') });
|
|
789
798
|
if (!passed) process.exit(1);
|
|
790
799
|
}
|
|
791
800
|
|
|
@@ -804,7 +813,8 @@ function help() {
|
|
|
804
813
|
--surfaces both|popup|side-panel|none what it opens (default: both)
|
|
805
814
|
tools your tools, with their ids
|
|
806
815
|
pack [folder] the zip for the Chrome Web Store: the built extension, its manifest's "key" left out;
|
|
807
|
-
refuses a Test key or
|
|
816
|
+
refuses a Test key, localhost, or another of the Wall's hosts inside
|
|
817
|
+
--out <file.zip>
|
|
808
818
|
leaves out keys, certificates, .env and credential files, source maps, secrets,
|
|
809
819
|
git's files, a build's copy of a private file (compressed, UTF-16 or base64),
|
|
810
820
|
an image that is none; refuses a build if one is a file the manifest names
|
package/lib/manifest.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Generated from wall/src/packages/wiring/manifest.ts by wall/scripts/pack-create.mjs — edit there.
|
|
2
|
+
import { isTestAddress } from './test-address.js';
|
|
2
3
|
/**
|
|
3
4
|
* What wiring an extension does to its manifest — the same in the browser
|
|
4
5
|
* (Wire my extension, from the tool's page) and in the terminal
|
|
@@ -70,9 +71,9 @@ export const loadableName = (file) => {
|
|
|
70
71
|
/** The background the manifest names, as the folder spells it: "./background.js" is background.js. */
|
|
71
72
|
export const namedWorker = (manifest) => typeof manifest?.background?.service_worker === 'string' ? manifest.background.service_worker.trim().replace(/^(\.\/)+/, '') || null : null;
|
|
72
73
|
/**
|
|
73
|
-
* The match pattern a manifest names the Wall by: the origin of the key's site, whatever path follows it
|
|
74
|
-
*
|
|
75
|
-
*
|
|
74
|
+
* The match pattern a manifest names the Wall by: the origin of the key's site, whatever path follows it —
|
|
75
|
+
* `https://northwind.toolaby.app/*` on Live; on Test, the address the Wall made for the workspace
|
|
76
|
+
* (`https://quiet-otter-4172.toolaby.app/*`, ./test-address). A wiring to one side takes the other's Test address out.
|
|
76
77
|
*/
|
|
77
78
|
export function sitePattern(site) {
|
|
78
79
|
try {
|
|
@@ -124,6 +125,26 @@ export function wireManifest(input, o) {
|
|
|
124
125
|
added.push('storage');
|
|
125
126
|
}
|
|
126
127
|
const pattern = sitePattern(o.site);
|
|
128
|
+
// A Test address of the Wall's that is not this key's (./test-address): left by an earlier wiring to the other side.
|
|
129
|
+
// Test's workspaces have hosts of their own, and a build wired for Test and then for Live went on asking its buyers,
|
|
130
|
+
// at install, for access to Test's host too (29 Sep 2026). Only under the Wall's own address: a host of the
|
|
131
|
+
// developer's that reads like one, on a domain of theirs, was taken out of their build too (30 Sep 2026).
|
|
132
|
+
const apex = (o.apex ?? host.split('.').slice(1).join('.')).toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
133
|
+
const underWall = new RegExp(`^https://([a-z0-9-]+)\\.${apex}/\\*$`);
|
|
134
|
+
const stale = (p) => typeof p === 'string' && p !== pattern && isTestAddress(underWall.exec(p)?.[1]);
|
|
135
|
+
for (const field of ['host_permissions', 'externally_connectable']) {
|
|
136
|
+
const list = field === 'host_permissions' ? manifest.host_permissions : manifest.externally_connectable?.matches;
|
|
137
|
+
const gone = Array.isArray(list) ? list.filter(stale) : [];
|
|
138
|
+
if (!gone.length)
|
|
139
|
+
continue;
|
|
140
|
+
const kept = list.filter((p) => !stale(p));
|
|
141
|
+
if (field === 'host_permissions')
|
|
142
|
+
manifest.host_permissions = kept;
|
|
143
|
+
else
|
|
144
|
+
manifest.externally_connectable = { ...manifest.externally_connectable, matches: kept };
|
|
145
|
+
for (const p of gone)
|
|
146
|
+
did.push(`${field === 'host_permissions' ? 'the host permission' : 'externally_connectable'} for ${p.slice('https://'.length, -2)} taken out: a Test address that is not this key's`);
|
|
147
|
+
}
|
|
127
148
|
if (!(Array.isArray(manifest.host_permissions) && manifest.host_permissions.includes(pattern))) {
|
|
128
149
|
manifest.host_permissions = add(manifest.host_permissions, pattern);
|
|
129
150
|
did.push(`a host permission for ${host}`);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Generated from wall/src/packages/wiring/test-address.ts by wall/scripts/pack-create.mjs — edit there.
|
|
2
|
+
/**
|
|
3
|
+
* A Test workspace's address, as Clerk gives a development instance one (`<slug>.clerk.accounts.dev`): made by the Wall,
|
|
4
|
+
* never chosen, never changed — `quiet-otter-4172.toolaby.app/test`. Test and Live share the Wall's addresses, and a
|
|
5
|
+
* name is Live's: a Test workspace served under its chosen name sat at the address that name has on Live, which
|
|
6
|
+
* someone else could hold (29 Sep 2026). A Test address is of a shape no workspace may take as its name, on either side
|
|
7
|
+
* (isReservedSlug), so no Live workspace ever answers at one.
|
|
8
|
+
*
|
|
9
|
+
* The database computes it, from the workspace's id, which never changes (the column `test_address`, generated; its
|
|
10
|
+
* migration writes testAddressSql() out as it stands here). So neither list may ever change: every Test workspace's
|
|
11
|
+
* address, and every Test key that names one, would move.
|
|
12
|
+
*/
|
|
13
|
+
export const TEST_ADDRESS_ADJECTIVES = [
|
|
14
|
+
'amber', 'bold', 'brave', 'bright', 'brisk', 'calm', 'clever', 'cosmic', 'crisp', 'curious', 'daring', 'eager', 'early', 'fancy', 'fluffy', 'gentle',
|
|
15
|
+
'glad', 'golden', 'happy', 'hardy', 'honest', 'humble', 'jolly', 'keen', 'kind', 'lively', 'lucky', 'lunar', 'mellow', 'merry', 'mighty', 'misty',
|
|
16
|
+
'modest', 'nimble', 'noble', 'patient', 'plucky', 'polite', 'proud', 'quick', 'quiet', 'rapid', 'ready', 'rosy', 'rustic', 'shiny', 'silent', 'silver',
|
|
17
|
+
'smooth', 'snowy', 'solar', 'spry', 'steady', 'sturdy', 'sunny', 'swift', 'tidy', 'tranquil', 'trusty', 'vivid', 'warm', 'wise', 'witty', 'zesty',
|
|
18
|
+
];
|
|
19
|
+
export const TEST_ADDRESS_ANIMALS = [
|
|
20
|
+
'badger', 'beaver', 'bison', 'bobcat', 'camel', 'cheetah', 'crane', 'dingo', 'dolphin', 'eagle', 'falcon', 'ferret', 'finch', 'fox', 'gazelle', 'gecko',
|
|
21
|
+
'heron', 'hippo', 'ibis', 'jaguar', 'koala', 'lemur', 'leopard', 'llama', 'lynx', 'magpie', 'marmot', 'marten', 'meerkat', 'mink', 'moose', 'narwhal',
|
|
22
|
+
'newt', 'ocelot', 'orca', 'osprey', 'otter', 'owl', 'panda', 'panther', 'parrot', 'pelican', 'penguin', 'puffin', 'quail', 'rabbit', 'raven', 'robin',
|
|
23
|
+
'salmon', 'seal', 'sparrow', 'stork', 'swan', 'tapir', 'tiger', 'toucan', 'turtle', 'walrus', 'weasel', 'whale', 'wombat', 'wren', 'yak', 'zebra',
|
|
24
|
+
];
|
|
25
|
+
const ADJECTIVES = new Set(TEST_ADDRESS_ADJECTIVES);
|
|
26
|
+
const ANIMALS = new Set(TEST_ADDRESS_ANIMALS);
|
|
27
|
+
const SHAPE = /^([a-z]+)-([a-z]+)-([1-9]\d{3})$/;
|
|
28
|
+
/** Whether a name is of a Test address's shape: one of the adjectives, one of the animals, a number of four digits. */
|
|
29
|
+
export function isTestAddress(s) {
|
|
30
|
+
if (typeof s !== 'string')
|
|
31
|
+
return false;
|
|
32
|
+
const m = SHAPE.exec(s);
|
|
33
|
+
return !!m && ADJECTIVES.has(m[1]) && ANIMALS.has(m[2]);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The address from the four first bytes of md5 of the id: an adjective, an animal, 1000 + a number below 9000. As SQL,
|
|
37
|
+
* for the column the database generates; a test holds the migration to it, and computes the same in Node.
|
|
38
|
+
*/
|
|
39
|
+
export function testAddressSql(id = 'id') {
|
|
40
|
+
const h = `decode(md5(${id}), 'hex')`;
|
|
41
|
+
const list = (words) => `(array[${words.map((w) => `'${w}'`).join(', ')}]::text[])`;
|
|
42
|
+
return `${list(TEST_ADDRESS_ADJECTIVES)}[1 + get_byte(${h}, 0) % ${TEST_ADDRESS_ADJECTIVES.length}] || '-' || ${list(TEST_ADDRESS_ANIMALS)}[1 + get_byte(${h}, 1) % ${TEST_ADDRESS_ANIMALS.length}] || '-' || (1000 + (get_byte(${h}, 2) * 256 + get_byte(${h}, 3)) % 9000)::text`;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolaby",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.3",
|
|
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; check it, and give coding agents the Wall as an MCP server.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|