maqam 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/app/styles.css ADDED
@@ -0,0 +1,397 @@
1
+ :root {
2
+ color-scheme: dark;
3
+ --bg: #080d0c;
4
+ --panel: #111816;
5
+ --panel-2: #18211f;
6
+ --line: #2d3935;
7
+ --text: #f4f7f3;
8
+ --muted: #a9b7b0;
9
+ --blue: #48c7ff;
10
+ --green: #84e07f;
11
+ --danger: #ff7a73;
12
+ --radius: 14px;
13
+ font-family: "Aptos", "Segoe UI", sans-serif;
14
+ }
15
+
16
+ * {
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ margin: 0;
22
+ background:
23
+ linear-gradient(135deg, rgb(72 199 255 / .08), transparent 35%),
24
+ linear-gradient(315deg, rgb(132 224 127 / .08), transparent 42%),
25
+ var(--bg);
26
+ color: var(--text);
27
+ min-width: 320px;
28
+ }
29
+
30
+ a {
31
+ color: inherit;
32
+ text-decoration: none;
33
+ }
34
+
35
+ .shell {
36
+ width: min(1180px, calc(100% - 32px));
37
+ margin: 0 auto;
38
+ }
39
+
40
+ .topbar {
41
+ min-height: 76px;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: space-between;
45
+ gap: 24px;
46
+ border-bottom: 1px solid var(--line);
47
+ }
48
+
49
+ .brand {
50
+ display: inline-flex;
51
+ align-items: center;
52
+ gap: 12px;
53
+ }
54
+
55
+ .brand img {
56
+ border-radius: 12px;
57
+ }
58
+
59
+ .brand span {
60
+ display: grid;
61
+ gap: 2px;
62
+ }
63
+
64
+ .brand strong {
65
+ font-size: 1rem;
66
+ }
67
+
68
+ .brand small,
69
+ .hint,
70
+ .section-heading p,
71
+ .policy-band p,
72
+ .metrics span {
73
+ color: var(--muted);
74
+ }
75
+
76
+ .nav {
77
+ display: flex;
78
+ gap: 6px;
79
+ padding: 6px;
80
+ border: 1px solid var(--line);
81
+ border-radius: 999px;
82
+ background: rgb(17 24 22 / .8);
83
+ }
84
+
85
+ .nav a {
86
+ color: var(--muted);
87
+ padding: 8px 12px;
88
+ border-radius: 999px;
89
+ font-size: .92rem;
90
+ }
91
+
92
+ .nav a:hover {
93
+ color: var(--text);
94
+ background: rgb(244 247 243 / .08);
95
+ }
96
+
97
+ .hero {
98
+ min-height: calc(100dvh - 76px);
99
+ display: grid;
100
+ grid-template-columns: minmax(0, 1.08fr) minmax(340px, .72fr);
101
+ align-items: center;
102
+ gap: 52px;
103
+ padding: 52px 0;
104
+ }
105
+
106
+ .kicker {
107
+ color: var(--green);
108
+ font-weight: 700;
109
+ margin: 0 0 20px;
110
+ }
111
+
112
+ h1,
113
+ h2,
114
+ p {
115
+ margin-top: 0;
116
+ }
117
+
118
+ h1 {
119
+ max-width: 820px;
120
+ font-size: clamp(3rem, 8vw, 7.6rem);
121
+ line-height: .96;
122
+ letter-spacing: 0;
123
+ margin-bottom: 28px;
124
+ }
125
+
126
+ h2 {
127
+ font-size: 1.35rem;
128
+ letter-spacing: 0;
129
+ }
130
+
131
+ .lede {
132
+ max-width: 680px;
133
+ color: var(--muted);
134
+ font-size: 1.2rem;
135
+ line-height: 1.65;
136
+ }
137
+
138
+ .run-panel,
139
+ .metrics article,
140
+ .stack > article,
141
+ .policy-band {
142
+ border: 1px solid var(--line);
143
+ border-radius: var(--radius);
144
+ background: linear-gradient(180deg, rgb(244 247 243 / .06), rgb(244 247 243 / .025));
145
+ }
146
+
147
+ .run-panel {
148
+ padding: 22px;
149
+ box-shadow: 0 24px 80px rgb(0 0 0 / .22);
150
+ }
151
+
152
+ .panel-header {
153
+ display: flex;
154
+ justify-content: space-between;
155
+ gap: 16px;
156
+ margin-bottom: 24px;
157
+ color: var(--muted);
158
+ }
159
+
160
+ .panel-header strong {
161
+ color: var(--green);
162
+ }
163
+
164
+ form {
165
+ display: grid;
166
+ gap: 16px;
167
+ }
168
+
169
+ label {
170
+ display: grid;
171
+ gap: 8px;
172
+ color: var(--muted);
173
+ font-size: .94rem;
174
+ }
175
+
176
+ input,
177
+ select {
178
+ width: 100%;
179
+ min-height: 46px;
180
+ border: 1px solid var(--line);
181
+ border-radius: 10px;
182
+ background: var(--panel);
183
+ color: var(--text);
184
+ padding: 0 12px;
185
+ font: inherit;
186
+ }
187
+
188
+ input:focus,
189
+ select:focus,
190
+ button:focus-visible,
191
+ a:focus-visible {
192
+ outline: 3px solid rgb(72 199 255 / .45);
193
+ outline-offset: 2px;
194
+ }
195
+
196
+ .form-grid {
197
+ display: grid;
198
+ grid-template-columns: 1fr 1fr;
199
+ gap: 12px;
200
+ }
201
+
202
+ button {
203
+ min-height: 48px;
204
+ border: 0;
205
+ border-radius: 999px;
206
+ background: var(--text);
207
+ color: #0c1110;
208
+ font: inherit;
209
+ font-weight: 800;
210
+ cursor: pointer;
211
+ }
212
+
213
+ button:hover {
214
+ background: var(--green);
215
+ }
216
+
217
+ button:active {
218
+ transform: translateY(1px);
219
+ }
220
+
221
+ button:disabled {
222
+ cursor: wait;
223
+ opacity: .7;
224
+ }
225
+
226
+ .hint {
227
+ margin: 0;
228
+ font-size: .9rem;
229
+ line-height: 1.5;
230
+ }
231
+
232
+ .metrics {
233
+ display: grid;
234
+ grid-template-columns: repeat(4, minmax(0, 1fr));
235
+ gap: 14px;
236
+ margin-bottom: 26px;
237
+ }
238
+
239
+ .metrics article {
240
+ padding: 18px;
241
+ display: grid;
242
+ gap: 8px;
243
+ }
244
+
245
+ .metrics strong {
246
+ font-size: 1.25rem;
247
+ }
248
+
249
+ .workspace {
250
+ display: grid;
251
+ grid-template-columns: 1fr 1fr;
252
+ gap: 26px;
253
+ padding: 38px 0;
254
+ }
255
+
256
+ .section-heading {
257
+ border-bottom: 1px solid var(--line);
258
+ margin-bottom: 16px;
259
+ padding-bottom: 14px;
260
+ }
261
+
262
+ .section-heading p {
263
+ margin-bottom: 0;
264
+ }
265
+
266
+ .stack {
267
+ display: grid;
268
+ gap: 12px;
269
+ }
270
+
271
+ .empty {
272
+ min-height: 150px;
273
+ align-content: center;
274
+ padding: 20px;
275
+ border: 1px dashed var(--line);
276
+ border-radius: var(--radius);
277
+ color: var(--muted);
278
+ }
279
+
280
+ .stack > article {
281
+ padding: 16px;
282
+ }
283
+
284
+ .stack h3 {
285
+ margin: 0 0 8px;
286
+ font-size: 1rem;
287
+ }
288
+
289
+ .stack p {
290
+ color: var(--muted);
291
+ line-height: 1.55;
292
+ margin-bottom: 12px;
293
+ }
294
+
295
+ .tag-row {
296
+ display: flex;
297
+ flex-wrap: wrap;
298
+ gap: 8px;
299
+ }
300
+
301
+ .tag {
302
+ display: inline-flex;
303
+ align-items: center;
304
+ min-height: 28px;
305
+ padding: 0 10px;
306
+ border-radius: 999px;
307
+ border: 1px solid var(--line);
308
+ color: var(--green);
309
+ background: rgb(132 224 127 / .08);
310
+ font-size: .82rem;
311
+ }
312
+
313
+ .policy-band {
314
+ display: grid;
315
+ grid-template-columns: .9fr 1.1fr;
316
+ gap: 24px;
317
+ padding: 24px;
318
+ margin: 20px 0 54px;
319
+ }
320
+
321
+ .policy-band ol {
322
+ margin: 0;
323
+ display: grid;
324
+ gap: 10px;
325
+ }
326
+
327
+ .policy-band li {
328
+ color: var(--muted);
329
+ }
330
+
331
+ .error {
332
+ color: var(--danger);
333
+ }
334
+
335
+ @media (max-width: 860px) {
336
+ .topbar,
337
+ .hero,
338
+ .workspace,
339
+ .policy-band {
340
+ grid-template-columns: 1fr;
341
+ }
342
+
343
+ .topbar {
344
+ align-items: flex-start;
345
+ flex-direction: column;
346
+ padding: 16px 0;
347
+ }
348
+
349
+ .nav {
350
+ width: 100%;
351
+ justify-content: space-between;
352
+ }
353
+
354
+ .hero {
355
+ min-height: auto;
356
+ padding: 38px 0;
357
+ gap: 30px;
358
+ }
359
+
360
+ .run-panel {
361
+ order: -1;
362
+ }
363
+
364
+ h1 {
365
+ font-size: clamp(2.35rem, 11vw, 3.6rem);
366
+ line-height: 1.02;
367
+ }
368
+
369
+ .metrics {
370
+ grid-template-columns: 1fr 1fr;
371
+ }
372
+ }
373
+
374
+ @media (max-width: 560px) {
375
+ .shell {
376
+ width: min(100% - 24px, 1180px);
377
+ }
378
+
379
+ .metrics,
380
+ .form-grid {
381
+ grid-template-columns: 1fr;
382
+ }
383
+
384
+ .nav a {
385
+ padding-inline: 9px;
386
+ }
387
+ }
388
+
389
+ @media (prefers-reduced-motion: reduce) {
390
+ *,
391
+ *::before,
392
+ *::after {
393
+ scroll-behavior: auto !important;
394
+ transition: none !important;
395
+ animation: none !important;
396
+ }
397
+ }
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { createWriteStream } from "node:fs";
3
+ import { crawl } from "../src/index.js";
4
+
5
+ function usage() {
6
+ console.log(`
7
+ Maqam Crawler
8
+
9
+ Usage:
10
+ maqam-crawl <url> [more urls...] [options]
11
+
12
+ Options:
13
+ --max-pages <n> Maximum pages to return. Default: 50
14
+ --concurrency <n> Concurrent workers. Default: 4
15
+ --delay <ms> Minimum delay per origin. Default: 250
16
+ --timeout <ms> Request timeout. Default: 15000
17
+ --sitemaps Discover URLs from robots.txt sitemaps and /sitemap.xml
18
+ --all-origins Allow crawling across origins discovered from links
19
+ --jsonl Output JSON Lines instead of a JSON array
20
+ --output <file> Write output to a file
21
+ --user-agent <ua> Custom user agent
22
+ --help Show this help
23
+
24
+ This crawler is Maqam's first governed connector. It respects robots.txt by default and does not bypass access controls.
25
+ `);
26
+ }
27
+
28
+ function readArgs(argv) {
29
+ const urls = [];
30
+ const options = {
31
+ maxPages: 50,
32
+ concurrency: 4,
33
+ delayMs: 250,
34
+ timeoutMs: 15_000,
35
+ includeSitemaps: false,
36
+ sameOrigin: true,
37
+ jsonl: false,
38
+ output: null,
39
+ userAgent: undefined
40
+ };
41
+
42
+ for (let i = 0; i < argv.length; i += 1) {
43
+ const arg = argv[i];
44
+ if (arg === "--help" || arg === "-h") {
45
+ options.help = true;
46
+ } else if (arg === "--max-pages") {
47
+ options.maxPages = Number(argv[++i]);
48
+ } else if (arg === "--concurrency") {
49
+ options.concurrency = Number(argv[++i]);
50
+ } else if (arg === "--delay") {
51
+ options.delayMs = Number(argv[++i]);
52
+ } else if (arg === "--timeout") {
53
+ options.timeoutMs = Number(argv[++i]);
54
+ } else if (arg === "--sitemaps") {
55
+ options.includeSitemaps = true;
56
+ } else if (arg === "--all-origins") {
57
+ options.sameOrigin = false;
58
+ } else if (arg === "--jsonl") {
59
+ options.jsonl = true;
60
+ } else if (arg === "--output" || arg === "-o") {
61
+ options.output = argv[++i];
62
+ } else if (arg === "--user-agent") {
63
+ options.userAgent = argv[++i];
64
+ } else if (arg.startsWith("-")) {
65
+ throw new Error(`Unknown option: ${arg}`);
66
+ } else {
67
+ urls.push(arg);
68
+ }
69
+ }
70
+
71
+ return { urls, options };
72
+ }
73
+
74
+ function writeOutput(pages, options) {
75
+ const body = options.jsonl
76
+ ? pages.map((page) => JSON.stringify(page)).join("\n") + "\n"
77
+ : JSON.stringify(pages, null, 2) + "\n";
78
+
79
+ if (!options.output) {
80
+ process.stdout.write(body);
81
+ return;
82
+ }
83
+ const stream = createWriteStream(options.output, { encoding: "utf8" });
84
+ stream.end(body);
85
+ }
86
+
87
+ async function main() {
88
+ const { urls, options } = readArgs(process.argv.slice(2));
89
+ if (options.help) {
90
+ usage();
91
+ return;
92
+ }
93
+ if (!urls.length) {
94
+ usage();
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
99
+ const pages = await crawl({
100
+ seeds: urls,
101
+ maxPages: options.maxPages,
102
+ concurrency: options.concurrency,
103
+ delayMs: options.delayMs,
104
+ timeoutMs: options.timeoutMs,
105
+ includeSitemaps: options.includeSitemaps,
106
+ sameOrigin: options.sameOrigin,
107
+ userAgent: options.userAgent,
108
+ onError: (failure) => {
109
+ process.stderr.write(`crawl warning: ${failure.url}: ${failure.error}\n`);
110
+ }
111
+ });
112
+
113
+ writeOutput(pages, options);
114
+ }
115
+
116
+ main().catch((error) => {
117
+ process.stderr.write(`${error.stack || error.message || String(error)}\n`);
118
+ process.exitCode = 1;
119
+ });
package/bin/maqam.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { startMaqamServer } from "../src/maqam/server.js";
3
+
4
+ function readPort(argv) {
5
+ const index = argv.indexOf("--port");
6
+ if (index === -1) return undefined;
7
+ return Number(argv[index + 1]);
8
+ }
9
+
10
+ if (process.argv.includes("--help") || process.argv.includes("-h")) {
11
+ console.log(`
12
+ Maqam
13
+
14
+ Usage:
15
+ maqam [--port 8787]
16
+
17
+ Starts the local Maqam enterprise agent framework console.
18
+ `);
19
+ process.exit(0);
20
+ }
21
+
22
+ startMaqamServer({ port: readPort(process.argv.slice(2)) });
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "maqam",
3
+ "version": "0.1.0",
4
+ "description": "Maqam is an MIT-licensed Ajnas agent framework for governed workflows, policy, evidence, skills, and crawler-backed research.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "Ajnas",
8
+ "email": "ajnasnb@gmail.com"
9
+ },
10
+ "type": "module",
11
+ "bin": {
12
+ "maqam": "bin/maqam.js",
13
+ "maqam-crawl": "bin/ajnas-crawl.js",
14
+ "ajnas-crawl": "bin/ajnas-crawl.js",
15
+ "ajnas-agent-crawler": "bin/ajnas-crawl.js"
16
+ },
17
+ "main": "src/index.js",
18
+ "exports": {
19
+ ".": "./src/index.js"
20
+ },
21
+ "files": [
22
+ "app/",
23
+ "bin/",
24
+ "src/",
25
+ "README.md",
26
+ "LICENSE"
27
+ ],
28
+ "scripts": {
29
+ "test": "node --test",
30
+ "start": "node bin/maqam.js",
31
+ "crawl": "node bin/ajnas-crawl.js",
32
+ "maqam": "node bin/maqam.js"
33
+ },
34
+ "keywords": [
35
+ "maqam",
36
+ "agent-framework",
37
+ "agent-runtime",
38
+ "governance",
39
+ "policy-engine",
40
+ "evidence-ledger",
41
+ "provenance",
42
+ "skills",
43
+ "tool-orchestration",
44
+ "human-approval",
45
+ "crawler",
46
+ "agent",
47
+ "web-crawler",
48
+ "scraper",
49
+ "robots.txt",
50
+ "sitemap",
51
+ "markdown",
52
+ "jsonl",
53
+ "research"
54
+ ],
55
+ "dependencies": {
56
+ "cheerio": "^1.0.0",
57
+ "robots-parser": "^3.0.1",
58
+ "turndown": "^7.2.0"
59
+ },
60
+ "engines": {
61
+ "node": ">=20"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/AjnasNB/maqam.git"
66
+ },
67
+ "bugs": {
68
+ "url": "https://github.com/AjnasNB/maqam/issues"
69
+ },
70
+ "homepage": "https://github.com/AjnasNB/maqam#readme",
71
+ "publishConfig": {
72
+ "access": "public"
73
+ }
74
+ }
@@ -0,0 +1,35 @@
1
+ export class AjnasFrameworkError extends Error {
2
+ constructor(message, options = {}) {
3
+ super(message, options.cause ? { cause: options.cause } : undefined);
4
+ this.name = new.target.name;
5
+ this.code = options.code || "AJNAS_FRAMEWORK_ERROR";
6
+ this.details = options.details || {};
7
+ }
8
+ }
9
+
10
+ export class PolicyDeniedError extends AjnasFrameworkError {
11
+ constructor(message, options = {}) {
12
+ super(message, {
13
+ ...options,
14
+ code: options.code || "POLICY_DENIED"
15
+ });
16
+ }
17
+ }
18
+
19
+ export class ApprovalRequiredError extends AjnasFrameworkError {
20
+ constructor(message, options = {}) {
21
+ super(message, {
22
+ ...options,
23
+ code: options.code || "APPROVAL_REQUIRED"
24
+ });
25
+ }
26
+ }
27
+
28
+ export function toErrorRecord(error) {
29
+ return {
30
+ name: error?.name || "Error",
31
+ code: error?.code || "ERROR",
32
+ message: error?.message || String(error),
33
+ details: error?.details || {}
34
+ };
35
+ }
@@ -0,0 +1,72 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ function sha256(value) {
4
+ return `sha256:${createHash("sha256").update(value).digest("hex")}`;
5
+ }
6
+
7
+ function clampConfidence(value) {
8
+ const number = Number(value);
9
+ if (!Number.isFinite(number)) return 0.5;
10
+ return Math.max(0, Math.min(1, number));
11
+ }
12
+
13
+ export class EvidenceLedger {
14
+ constructor(options = {}) {
15
+ this.clock = options.clock || (() => new Date());
16
+ this.evidence = [];
17
+ this.claims = [];
18
+ }
19
+
20
+ addEvidence(input = {}) {
21
+ const record = {
22
+ evidenceId: input.evidenceId || `ev_${this.evidence.length + 1}`,
23
+ runId: input.runId || null,
24
+ taskId: input.taskId || null,
25
+ sourceType: input.sourceType || "unknown",
26
+ source: input.source || "unknown",
27
+ retrievedAt: input.retrievedAt || this.clock().toISOString(),
28
+ excerpt: input.excerpt || "",
29
+ hash: input.hash || sha256(`${input.source || ""}\n${input.excerpt || ""}`),
30
+ tool: input.tool || null,
31
+ confidence: clampConfidence(input.confidence)
32
+ };
33
+ this.evidence.push(record);
34
+ return record;
35
+ }
36
+
37
+ addClaim(input = {}) {
38
+ const claim = {
39
+ claimId: input.claimId || `claim_${this.claims.length + 1}`,
40
+ runId: input.runId || null,
41
+ taskId: input.taskId || null,
42
+ text: input.text || "",
43
+ evidenceIds: input.evidenceIds || [],
44
+ confidence: clampConfidence(input.confidence)
45
+ };
46
+ this.claims.push(claim);
47
+ return claim;
48
+ }
49
+
50
+ listEvidence() {
51
+ return [...this.evidence];
52
+ }
53
+
54
+ listClaims() {
55
+ return [...this.claims];
56
+ }
57
+
58
+ unsupportedClaims() {
59
+ const known = new Set(this.evidence.map((record) => record.evidenceId));
60
+ return this.claims.filter((claim) => {
61
+ return !claim.evidenceIds.length || claim.evidenceIds.some((id) => !known.has(id));
62
+ });
63
+ }
64
+
65
+ toJSON() {
66
+ return {
67
+ evidence: this.listEvidence(),
68
+ claims: this.listClaims(),
69
+ unsupportedClaims: this.unsupportedClaims()
70
+ };
71
+ }
72
+ }