@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.
Files changed (80) hide show
  1. package/LICENSE +7 -7
  2. package/README.md +770 -770
  3. package/agents/AGENTS.md +192 -192
  4. package/agents/project/custom.md +21 -21
  5. package/agents/project/next-mui.md +21 -21
  6. package/agents/project/next.md +24 -24
  7. package/agents/project/vanilla.md +22 -22
  8. package/agents/project/vite.md +19 -19
  9. package/bin/hawk-app.mjs +0 -0
  10. package/docs/bridge/README.md +1315 -1315
  11. package/docs/showcase/README.md +47 -47
  12. package/docs/showcase/functions/echo.ts +12 -12
  13. package/docs/showcase/functions/long-task.ts +31 -31
  14. package/docs/showcase/functions/showcase.ts +186 -186
  15. package/docs/showcase/hawk-app.json +73 -73
  16. package/docs/showcase/queries/assistant-prompts-by-day.json +6 -6
  17. package/docs/showcase/queries/assistant-prompts-by-day.sql +8 -8
  18. package/docs/showcase/queries/searches-by-day.json +6 -6
  19. package/docs/showcase/queries/searches-by-day.sql +7 -7
  20. package/docs/showcase/src/App.tsx +1436 -1436
  21. package/docs/showcase/src/main.tsx +10 -10
  22. package/docs/showcase/src/styles.css +71 -71
  23. package/docs/showcase/src/useHawkApp.ts +44 -44
  24. package/docs/showcase/src/vite-env.d.ts +1 -1
  25. package/docs/ui/README.md +166 -166
  26. package/package.json +1 -1
  27. package/schema/hawk-app.schema.json +611 -611
  28. package/src/accessibility.mjs +105 -105
  29. package/src/agents.mjs +145 -145
  30. package/src/aliases.mjs +30 -30
  31. package/src/app-icon.mjs +82 -82
  32. package/src/channel.mjs +116 -116
  33. package/src/cli.mjs +825 -825
  34. package/src/clients.mjs +221 -221
  35. package/src/config.mjs +148 -148
  36. package/src/create.mjs +186 -186
  37. package/src/credentials.mjs +56 -56
  38. package/src/data.mjs +169 -169
  39. package/src/deploy.mjs +349 -349
  40. package/src/dev.mjs +459 -459
  41. package/src/doctor.mjs +176 -176
  42. package/src/env.mjs +140 -140
  43. package/src/functions.mjs +313 -313
  44. package/src/login.mjs +166 -166
  45. package/src/logs.mjs +64 -64
  46. package/src/manifest.mjs +425 -425
  47. package/src/mcp-docs.mjs +168 -168
  48. package/src/mcp-hawksearch.mjs +342 -342
  49. package/src/mcp-reports.mjs +130 -130
  50. package/src/mcp-rules.mjs +109 -109
  51. package/src/mcp.mjs +539 -539
  52. package/src/platform.mjs +116 -116
  53. package/src/project-checks.mjs +153 -153
  54. package/src/queries.mjs +289 -289
  55. package/src/scope-check.mjs +91 -91
  56. package/src/secrets.mjs +86 -86
  57. package/src/slug-guard.mjs +80 -80
  58. package/src/sso.mjs +182 -182
  59. package/src/telemetry.mjs +50 -50
  60. package/src/upgrade.mjs +406 -406
  61. package/src/versions.mjs +102 -102
  62. package/templates/next/next.config.ts +13 -13
  63. package/templates/next/package.json +26 -26
  64. package/templates/next/src/lib/useHawkApp.ts +46 -46
  65. package/templates/next-mui/next.config.ts +13 -13
  66. package/templates/next-mui/package.json +30 -30
  67. package/templates/next-mui/src/app/layout.tsx +19 -19
  68. package/templates/next-mui/src/app/page.tsx +78 -78
  69. package/templates/next-mui/src/app/providers.tsx +10 -10
  70. package/templates/preact/package.json +26 -26
  71. package/templates/preact/src/useHawkApp.ts +44 -44
  72. package/templates/preact/src/vite-env.d.ts +1 -1
  73. package/templates/preact/tsconfig.json +17 -17
  74. package/templates/preact/vite.config.ts +11 -11
  75. package/templates/react/package.json +29 -29
  76. package/templates/react/src/useHawkApp.ts +44 -44
  77. package/templates/react/src/vite-env.d.ts +1 -1
  78. package/templates/react/tsconfig.json +16 -16
  79. package/templates/react/vite.config.ts +11 -11
  80. 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
+ }