@bridgeline-digital/hawk-app-cli 0.1.0-dev.20261020202308 → 0.1.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/LICENSE +7 -7
- package/README.md +770 -770
- package/agents/AGENTS.md +192 -192
- package/agents/project/custom.md +21 -21
- package/agents/project/next-mui.md +21 -21
- package/agents/project/next.md +24 -24
- package/agents/project/vanilla.md +22 -22
- package/agents/project/vite.md +19 -19
- package/bin/hawk-app.mjs +0 -0
- package/docs/bridge/README.md +1315 -1315
- package/docs/showcase/README.md +47 -47
- package/docs/showcase/functions/echo.ts +12 -12
- package/docs/showcase/functions/long-task.ts +31 -31
- package/docs/showcase/functions/showcase.ts +186 -186
- package/docs/showcase/hawk-app.json +73 -73
- package/docs/showcase/queries/assistant-prompts-by-day.json +6 -6
- package/docs/showcase/queries/assistant-prompts-by-day.sql +8 -8
- package/docs/showcase/queries/searches-by-day.json +6 -6
- package/docs/showcase/queries/searches-by-day.sql +7 -7
- package/docs/showcase/src/App.tsx +1436 -1436
- package/docs/showcase/src/main.tsx +10 -10
- package/docs/showcase/src/styles.css +71 -71
- package/docs/showcase/src/useHawkApp.ts +44 -44
- package/docs/showcase/src/vite-env.d.ts +1 -1
- package/docs/ui/README.md +166 -166
- package/package.json +1 -1
- package/schema/hawk-app.schema.json +611 -611
- package/src/accessibility.mjs +105 -105
- package/src/agents.mjs +145 -145
- package/src/aliases.mjs +30 -30
- package/src/app-icon.mjs +82 -82
- package/src/channel.mjs +116 -116
- package/src/cli.mjs +825 -825
- package/src/clients.mjs +221 -221
- package/src/config.mjs +148 -148
- package/src/create.mjs +186 -186
- package/src/credentials.mjs +56 -56
- package/src/data.mjs +169 -169
- package/src/deploy.mjs +349 -349
- package/src/dev.mjs +459 -459
- package/src/doctor.mjs +176 -176
- package/src/env.mjs +140 -140
- package/src/functions.mjs +313 -313
- package/src/login.mjs +166 -166
- package/src/logs.mjs +64 -64
- package/src/manifest.mjs +425 -425
- package/src/mcp-docs.mjs +168 -168
- package/src/mcp-hawksearch.mjs +342 -342
- package/src/mcp-reports.mjs +130 -130
- package/src/mcp-rules.mjs +109 -109
- package/src/mcp.mjs +539 -539
- package/src/platform.mjs +116 -116
- package/src/project-checks.mjs +153 -153
- package/src/queries.mjs +289 -289
- package/src/scope-check.mjs +91 -91
- package/src/secrets.mjs +86 -86
- package/src/slug-guard.mjs +80 -80
- package/src/sso.mjs +182 -182
- package/src/telemetry.mjs +50 -50
- package/src/upgrade.mjs +406 -406
- package/src/versions.mjs +102 -102
- package/templates/next/next.config.ts +13 -13
- package/templates/next/package.json +26 -26
- package/templates/next/src/lib/useHawkApp.ts +46 -46
- package/templates/next-mui/next.config.ts +13 -13
- package/templates/next-mui/package.json +30 -30
- package/templates/next-mui/src/app/layout.tsx +19 -19
- package/templates/next-mui/src/app/page.tsx +78 -78
- package/templates/next-mui/src/app/providers.tsx +10 -10
- package/templates/preact/package.json +26 -26
- package/templates/preact/src/useHawkApp.ts +44 -44
- package/templates/preact/src/vite-env.d.ts +1 -1
- package/templates/preact/tsconfig.json +17 -17
- package/templates/preact/vite.config.ts +11 -11
- package/templates/react/package.json +29 -29
- package/templates/react/src/useHawkApp.ts +44 -44
- package/templates/react/src/vite-env.d.ts +1 -1
- package/templates/react/tsconfig.json +16 -16
- package/templates/react/vite.config.ts +11 -11
- package/templates/vanilla/package.json +15 -15
package/src/config.mjs
CHANGED
|
@@ -1,148 +1,148 @@
|
|
|
1
|
-
import { CLIENT_ID } from './clients.mjs';
|
|
2
|
-
import { CONFIG_KEY, configValueError } from './manifest.mjs';
|
|
3
|
-
|
|
4
|
-
const USAGE = `Usage:
|
|
5
|
-
hawk-app config list [--client <id>]
|
|
6
|
-
hawk-app config set <key> <value> [--client <id>]
|
|
7
|
-
hawk-app config unset <key> [--client <id>]`;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* `hawk-app config …`: the app's config values (declared in "config" in hawk-app.json), set for the whole app (global)
|
|
11
|
-
* or one client (`--client`). Values apply within about 30 seconds, without a deploy or approval, and are not secret:
|
|
12
|
-
* they reach the app's browser code. `client` is a factory, so a usage mistake shows before any login check.
|
|
13
|
-
*/
|
|
14
|
-
export async function configCommand({ client, manifest, sub, args = [], flags = {}, log = console.log }) {
|
|
15
|
-
const slug = manifest.slug;
|
|
16
|
-
// A bare --client (no id after it) is a mistake, never the global value.
|
|
17
|
-
const clientId = Object.hasOwn(flags, 'client') ? checkClient(flags.client) : null;
|
|
18
|
-
const layer = clientId ? `client ${clientId}` : 'global';
|
|
19
|
-
switch (sub) {
|
|
20
|
-
case 'list': {
|
|
21
|
-
const { keys = [] } = await call(() => client().getConfig(slug), slug);
|
|
22
|
-
const localKeys = declared(manifest);
|
|
23
|
-
if (!keys.length) {
|
|
24
|
-
log(`${slug} has no config keys on the platform. Declare them in "config" in hawk-app.json and deploy (or \`hawk-app register\`).`);
|
|
25
|
-
} else {
|
|
26
|
-
log(`${keys.length} config key${keys.length === 1 ? '' : 's'} for ${slug}${clientId ? `, client ${clientId}` : ''} (values are not secret: they reach the browser):`);
|
|
27
|
-
for (const k of keys) {
|
|
28
|
-
const head = k.declared === false ? `(not declared: its values are ignored; hawk-app config unset ${k.key} deletes them)` :`${k.type} ${k.default === null || k.default === undefined ? 'no default' : `default ${formatValue(k.default)}`}`;
|
|
29
|
-
log(` ${k.key} ${head}`);
|
|
30
|
-
const rows = [['global', k.global], ...(k.clients ?? []).filter((c) => !clientId || c.clientId === clientId).map((c) => [c.clientId, c])];
|
|
31
|
-
if (clientId && !rows.some(([id]) => id === clientId)) rows.push([clientId, null]);
|
|
32
|
-
const width = Math.max(...rows.map(([id]) => id.length));
|
|
33
|
-
for (const [id, stored] of rows) log(` ${id.padEnd(width)} ${describeStored(stored)}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
const missing = [...localKeys.keys()].filter((key) => !keys.some((k) => k.key === key));
|
|
37
|
-
for (const key of missing) log(`! ${key} is in hawk-app.json but not on the platform yet: deploy (or \`hawk-app register\`) to declare it.`);
|
|
38
|
-
return 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
case 'set': {
|
|
42
|
-
const [key, raw] = args;
|
|
43
|
-
if (!key || raw === undefined) throw new Error(USAGE);
|
|
44
|
-
const declaration = local(manifest, key);
|
|
45
|
-
const value = parseValue(key, declaration.type, raw);
|
|
46
|
-
await write(client, slug, key, value, clientId);
|
|
47
|
-
log(`✔ Set ${key} for ${slug} (${layer}): ${formatValue(value)}. It applies within about 30 seconds, without a deploy.`);
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
case 'unset': {
|
|
52
|
-
const [key] = args;
|
|
53
|
-
if (!key) throw new Error(USAGE);
|
|
54
|
-
if (!CONFIG_KEY.test(key)) throw new Error(`"${key}" is not a valid config key: a letter, then letters, digits and "_" (at most 64 characters).`);
|
|
55
|
-
await write(client, slug, key, null, clientId);
|
|
56
|
-
log(
|
|
57
|
-
`✔ Cleared ${key} for ${slug} (${layer}). ${clientId ? `${clientId} now gets the global value, else the default.` : "Clients without their own value now get the default."}`,
|
|
58
|
-
);
|
|
59
|
-
return 0;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
default:
|
|
63
|
-
throw new Error(sub ? `Unknown config command "${sub}".\n${USAGE}` : USAGE);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function checkClient(id) {
|
|
68
|
-
if (typeof id !== 'string' || !id) throw new Error(`--client needs a client id, e.g. --client e3141.\n${USAGE}`);
|
|
69
|
-
if (!CLIENT_ID.test(id)) throw new Error(`"${id}" is not a valid client id: 1-100 letters, digits, "_", "." or "-" (e.g. e3141).`);
|
|
70
|
-
return id;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Key → declaration, from the local hawk-app.json. */
|
|
74
|
-
function declared(manifest) {
|
|
75
|
-
const config = manifest.config && typeof manifest.config === 'object' && !Array.isArray(manifest.config) ? manifest.config : {};
|
|
76
|
-
return new Map(Object.entries(config).filter(([, d]) => d && typeof d === 'object'));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function local(manifest, key) {
|
|
80
|
-
const declaration = declared(manifest).get(key);
|
|
81
|
-
if (!declaration) throw new Error(`${key} isn't declared in hawk-app.json: add it to "config" with its type (e.g. "${key}": { "type": "string" }), then deploy.`);
|
|
82
|
-
return declaration;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** The command-line text as a value of the key's declared type, checked like the platform checks it. */
|
|
86
|
-
export function parseValue(key, type, raw) {
|
|
87
|
-
let value;
|
|
88
|
-
switch (type) {
|
|
89
|
-
case 'string':
|
|
90
|
-
value = raw;
|
|
91
|
-
break;
|
|
92
|
-
case 'number':
|
|
93
|
-
value = raw.trim() === '' ? NaN : Number(raw);
|
|
94
|
-
if (!Number.isFinite(value)) throw new Error(`${key} is a number: "${raw}" isn't one.`);
|
|
95
|
-
break;
|
|
96
|
-
case 'boolean':
|
|
97
|
-
if (raw !== 'true' && raw !== 'false') throw new Error(`${key} is a boolean: use true or false, not "${raw}".`);
|
|
98
|
-
value = raw === 'true';
|
|
99
|
-
break;
|
|
100
|
-
case 'emails':
|
|
101
|
-
value = raw.split(',').map((a) => a.trim()).filter(Boolean);
|
|
102
|
-
break;
|
|
103
|
-
case 'json':
|
|
104
|
-
try {
|
|
105
|
-
value = JSON.parse(raw);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
throw new Error(`${key} is JSON, but "${raw}" isn't valid JSON (${error.message}). Quote it for your shell, e.g. '{"a":1}'.`);
|
|
108
|
-
}
|
|
109
|
-
break;
|
|
110
|
-
default:
|
|
111
|
-
throw new Error(`${key} has no valid type in hawk-app.json ("${type}"): run \`hawk-app validate\`.`);
|
|
112
|
-
}
|
|
113
|
-
const problem = configValueError(type, value, key);
|
|
114
|
-
if (problem) throw new Error(problem);
|
|
115
|
-
return value;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function write(client, slug, key, value, clientId) {
|
|
119
|
-
try {
|
|
120
|
-
return await call(() => client().setConfig(slug, key, value, clientId), slug);
|
|
121
|
-
} catch (error) {
|
|
122
|
-
if (error.data?.code === 'config_key_not_declared') {
|
|
123
|
-
throw new Error(`${key} isn't declared on the platform: deploy (or \`hawk-app register\`) hawk-app.json with it in "config" first.`);
|
|
124
|
-
}
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async function call(fn, slug) {
|
|
130
|
-
try {
|
|
131
|
-
return await fn();
|
|
132
|
-
} catch (error) {
|
|
133
|
-
if (error.data?.code === 'app_not_found') throw new Error(`${slug} isn't on the platform yet: \`npm run app:dev\` or \`npm run app:deploy\` adds it.`);
|
|
134
|
-
throw error;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function describeStored(stored) {
|
|
139
|
-
if (!stored) return 'not set';
|
|
140
|
-
const when = stored.updatedAt ? ` (${stored.updatedAt.slice(0, 16).replace('T', ' ')}${stored.updatedBy ? ` by ${stored.updatedBy}` : ''})` : '';
|
|
141
|
-
return `${formatValue(stored.value)}${stored.valid === false ? ' ! invalid for its type: ignored' : ''}${when}`;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** A value for the terminal: emails as a list, the rest as JSON, cut at 100 characters. */
|
|
145
|
-
function formatValue(value) {
|
|
146
|
-
const text = Array.isArray(value) && value.every((v) => typeof v === 'string' && v.includes('@')) && value.length ? value.join(', ') : JSON.stringify(value);
|
|
147
|
-
return text.length > 100 ? `${text.slice(0, 99)}…` : text;
|
|
148
|
-
}
|
|
1
|
+
import { CLIENT_ID } from './clients.mjs';
|
|
2
|
+
import { CONFIG_KEY, configValueError } from './manifest.mjs';
|
|
3
|
+
|
|
4
|
+
const USAGE = `Usage:
|
|
5
|
+
hawk-app config list [--client <id>]
|
|
6
|
+
hawk-app config set <key> <value> [--client <id>]
|
|
7
|
+
hawk-app config unset <key> [--client <id>]`;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `hawk-app config …`: the app's config values (declared in "config" in hawk-app.json), set for the whole app (global)
|
|
11
|
+
* or one client (`--client`). Values apply within about 30 seconds, without a deploy or approval, and are not secret:
|
|
12
|
+
* they reach the app's browser code. `client` is a factory, so a usage mistake shows before any login check.
|
|
13
|
+
*/
|
|
14
|
+
export async function configCommand({ client, manifest, sub, args = [], flags = {}, log = console.log }) {
|
|
15
|
+
const slug = manifest.slug;
|
|
16
|
+
// A bare --client (no id after it) is a mistake, never the global value.
|
|
17
|
+
const clientId = Object.hasOwn(flags, 'client') ? checkClient(flags.client) : null;
|
|
18
|
+
const layer = clientId ? `client ${clientId}` : 'global';
|
|
19
|
+
switch (sub) {
|
|
20
|
+
case 'list': {
|
|
21
|
+
const { keys = [] } = await call(() => client().getConfig(slug), slug);
|
|
22
|
+
const localKeys = declared(manifest);
|
|
23
|
+
if (!keys.length) {
|
|
24
|
+
log(`${slug} has no config keys on the platform. Declare them in "config" in hawk-app.json and deploy (or \`hawk-app register\`).`);
|
|
25
|
+
} else {
|
|
26
|
+
log(`${keys.length} config key${keys.length === 1 ? '' : 's'} for ${slug}${clientId ? `, client ${clientId}` : ''} (values are not secret: they reach the browser):`);
|
|
27
|
+
for (const k of keys) {
|
|
28
|
+
const head = k.declared === false ? `(not declared: its values are ignored; hawk-app config unset ${k.key} deletes them)` :`${k.type} ${k.default === null || k.default === undefined ? 'no default' : `default ${formatValue(k.default)}`}`;
|
|
29
|
+
log(` ${k.key} ${head}`);
|
|
30
|
+
const rows = [['global', k.global], ...(k.clients ?? []).filter((c) => !clientId || c.clientId === clientId).map((c) => [c.clientId, c])];
|
|
31
|
+
if (clientId && !rows.some(([id]) => id === clientId)) rows.push([clientId, null]);
|
|
32
|
+
const width = Math.max(...rows.map(([id]) => id.length));
|
|
33
|
+
for (const [id, stored] of rows) log(` ${id.padEnd(width)} ${describeStored(stored)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const missing = [...localKeys.keys()].filter((key) => !keys.some((k) => k.key === key));
|
|
37
|
+
for (const key of missing) log(`! ${key} is in hawk-app.json but not on the platform yet: deploy (or \`hawk-app register\`) to declare it.`);
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
case 'set': {
|
|
42
|
+
const [key, raw] = args;
|
|
43
|
+
if (!key || raw === undefined) throw new Error(USAGE);
|
|
44
|
+
const declaration = local(manifest, key);
|
|
45
|
+
const value = parseValue(key, declaration.type, raw);
|
|
46
|
+
await write(client, slug, key, value, clientId);
|
|
47
|
+
log(`✔ Set ${key} for ${slug} (${layer}): ${formatValue(value)}. It applies within about 30 seconds, without a deploy.`);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
case 'unset': {
|
|
52
|
+
const [key] = args;
|
|
53
|
+
if (!key) throw new Error(USAGE);
|
|
54
|
+
if (!CONFIG_KEY.test(key)) throw new Error(`"${key}" is not a valid config key: a letter, then letters, digits and "_" (at most 64 characters).`);
|
|
55
|
+
await write(client, slug, key, null, clientId);
|
|
56
|
+
log(
|
|
57
|
+
`✔ Cleared ${key} for ${slug} (${layer}). ${clientId ? `${clientId} now gets the global value, else the default.` : "Clients without their own value now get the default."}`,
|
|
58
|
+
);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
default:
|
|
63
|
+
throw new Error(sub ? `Unknown config command "${sub}".\n${USAGE}` : USAGE);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkClient(id) {
|
|
68
|
+
if (typeof id !== 'string' || !id) throw new Error(`--client needs a client id, e.g. --client e3141.\n${USAGE}`);
|
|
69
|
+
if (!CLIENT_ID.test(id)) throw new Error(`"${id}" is not a valid client id: 1-100 letters, digits, "_", "." or "-" (e.g. e3141).`);
|
|
70
|
+
return id;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Key → declaration, from the local hawk-app.json. */
|
|
74
|
+
function declared(manifest) {
|
|
75
|
+
const config = manifest.config && typeof manifest.config === 'object' && !Array.isArray(manifest.config) ? manifest.config : {};
|
|
76
|
+
return new Map(Object.entries(config).filter(([, d]) => d && typeof d === 'object'));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function local(manifest, key) {
|
|
80
|
+
const declaration = declared(manifest).get(key);
|
|
81
|
+
if (!declaration) throw new Error(`${key} isn't declared in hawk-app.json: add it to "config" with its type (e.g. "${key}": { "type": "string" }), then deploy.`);
|
|
82
|
+
return declaration;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The command-line text as a value of the key's declared type, checked like the platform checks it. */
|
|
86
|
+
export function parseValue(key, type, raw) {
|
|
87
|
+
let value;
|
|
88
|
+
switch (type) {
|
|
89
|
+
case 'string':
|
|
90
|
+
value = raw;
|
|
91
|
+
break;
|
|
92
|
+
case 'number':
|
|
93
|
+
value = raw.trim() === '' ? NaN : Number(raw);
|
|
94
|
+
if (!Number.isFinite(value)) throw new Error(`${key} is a number: "${raw}" isn't one.`);
|
|
95
|
+
break;
|
|
96
|
+
case 'boolean':
|
|
97
|
+
if (raw !== 'true' && raw !== 'false') throw new Error(`${key} is a boolean: use true or false, not "${raw}".`);
|
|
98
|
+
value = raw === 'true';
|
|
99
|
+
break;
|
|
100
|
+
case 'emails':
|
|
101
|
+
value = raw.split(',').map((a) => a.trim()).filter(Boolean);
|
|
102
|
+
break;
|
|
103
|
+
case 'json':
|
|
104
|
+
try {
|
|
105
|
+
value = JSON.parse(raw);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new Error(`${key} is JSON, but "${raw}" isn't valid JSON (${error.message}). Quote it for your shell, e.g. '{"a":1}'.`);
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
default:
|
|
111
|
+
throw new Error(`${key} has no valid type in hawk-app.json ("${type}"): run \`hawk-app validate\`.`);
|
|
112
|
+
}
|
|
113
|
+
const problem = configValueError(type, value, key);
|
|
114
|
+
if (problem) throw new Error(problem);
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function write(client, slug, key, value, clientId) {
|
|
119
|
+
try {
|
|
120
|
+
return await call(() => client().setConfig(slug, key, value, clientId), slug);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error.data?.code === 'config_key_not_declared') {
|
|
123
|
+
throw new Error(`${key} isn't declared on the platform: deploy (or \`hawk-app register\`) hawk-app.json with it in "config" first.`);
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function call(fn, slug) {
|
|
130
|
+
try {
|
|
131
|
+
return await fn();
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.data?.code === 'app_not_found') throw new Error(`${slug} isn't on the platform yet: \`npm run app:dev\` or \`npm run app:deploy\` adds it.`);
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function describeStored(stored) {
|
|
139
|
+
if (!stored) return 'not set';
|
|
140
|
+
const when = stored.updatedAt ? ` (${stored.updatedAt.slice(0, 16).replace('T', ' ')}${stored.updatedBy ? ` by ${stored.updatedBy}` : ''})` : '';
|
|
141
|
+
return `${formatValue(stored.value)}${stored.valid === false ? ' ! invalid for its type: ignored' : ''}${when}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A value for the terminal: emails as a list, the rest as JSON, cut at 100 characters. */
|
|
145
|
+
function formatValue(value) {
|
|
146
|
+
const text = Array.isArray(value) && value.every((v) => typeof v === 'string' && v.includes('@')) && value.length ? value.join(', ') : JSON.stringify(value);
|
|
147
|
+
return text.length > 100 ? `${text.slice(0, 99)}…` : text;
|
|
148
|
+
}
|