arkaos 2.71.0 → 2.73.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/VERSION CHANGED
@@ -1 +1 @@
1
- 2.71.0
1
+ 2.73.0
@@ -16,16 +16,27 @@ const ingestError = ref<string | null>(null)
16
16
  const isDragging = ref(false)
17
17
  const pasteText = ref('')
18
18
  const pasteTitle = ref('')
19
+ // PR56 v2.73.0 — bulk URL ingest mode. Paste a list of URLs (one per
20
+ // line) and the backend queues one job per source.
21
+ const bulkUrls = ref('')
19
22
 
20
- const activeInputMode = ref<'url' | 'file' | 'text' | 'research'>('url')
23
+ const activeInputMode = ref<'url' | 'file' | 'text' | 'research' | 'bulk'>('url')
21
24
 
22
25
  const inputModes = [
23
26
  { label: 'URL', value: 'url' as const, icon: 'i-lucide-link' },
27
+ { label: 'Bulk', value: 'bulk' as const, icon: 'i-lucide-list' },
24
28
  { label: 'File', value: 'file' as const, icon: 'i-lucide-upload' },
25
29
  { label: 'Text', value: 'text' as const, icon: 'i-lucide-type' },
26
30
  { label: 'Research', value: 'research' as const, icon: 'i-lucide-search' },
27
31
  ]
28
32
 
33
+ const bulkUrlCount = computed(() =>
34
+ bulkUrls.value
35
+ .split('\n')
36
+ .map((s) => s.trim())
37
+ .filter((s) => s.length > 0).length
38
+ )
39
+
29
40
  function handleDrop(e: DragEvent) {
30
41
  isDragging.value = false
31
42
  const file = e.dataTransfer?.files?.[0]
@@ -87,6 +98,7 @@ function clearFile() {
87
98
  }
88
99
 
89
100
  const canIngest = computed(() => {
101
+ if (activeInputMode.value === 'bulk') return bulkUrlCount.value > 0
90
102
  return detectedType.value !== null
91
103
  })
92
104
 
@@ -178,7 +190,11 @@ onUnmounted(() => {
178
190
  })
179
191
 
180
192
  async function handleIngest() {
181
- if (!detectedType.value && activeInputMode.value !== 'text') return
193
+ if (
194
+ !detectedType.value
195
+ && activeInputMode.value !== 'text'
196
+ && activeInputMode.value !== 'bulk'
197
+ ) return
182
198
 
183
199
  ingestError.value = null
184
200
 
@@ -199,6 +215,17 @@ async function handleIngest() {
199
215
  body: { source: pasteText.value.slice(0, 100), type: 'markdown', text: pasteText.value, title: pasteTitle.value },
200
216
  })
201
217
  }
218
+ // Bulk URL paste — one job per non-blank line, server caps at 50
219
+ else if (activeInputMode.value === 'bulk' && bulkUrlCount.value > 0) {
220
+ const sources = bulkUrls.value
221
+ .split('\n')
222
+ .map((s) => s.trim())
223
+ .filter((s) => s.length > 0)
224
+ await $fetch(`${apiBase}/api/knowledge/ingest-bulk`, {
225
+ method: 'POST',
226
+ body: { sources },
227
+ })
228
+ }
202
229
  // URL or Research — standard ingest
203
230
  else {
204
231
  const source = ingestUrl.value.trim()
@@ -215,6 +242,7 @@ async function handleIngest() {
215
242
  clearFile()
216
243
  pasteText.value = ''
217
244
  pasteTitle.value = ''
245
+ bulkUrls.value = ''
218
246
 
219
247
  // Refresh jobs table + connect WebSocket
220
248
  fetchJobs()
@@ -415,6 +443,23 @@ function formatScore(score: number): string {
415
443
  />
416
444
  </div>
417
445
 
446
+ <!-- Mode: Bulk URLs (PR56 v2.73.0) -->
447
+ <div v-if="activeInputMode === 'bulk'" class="space-y-3">
448
+ <UTextarea
449
+ v-model="bulkUrls"
450
+ placeholder="Paste one URL per line. Up to 50 sources per batch.&#10;&#10;https://www.youtube.com/watch?v=...&#10;https://example.com/article&#10;https://example.com/paper.pdf"
451
+ :rows="8"
452
+ size="lg"
453
+ class="w-full font-mono text-sm"
454
+ />
455
+ <div class="flex items-center justify-between text-xs text-muted">
456
+ <span>{{ bulkUrlCount }} source{{ bulkUrlCount === 1 ? '' : 's' }} detected</span>
457
+ <span v-if="bulkUrlCount > 50" class="text-red-400">
458
+ Over the 50-source cap — extras will be rejected.
459
+ </span>
460
+ </div>
461
+ </div>
462
+
418
463
  <!-- Mode: Research -->
419
464
  <div v-if="activeInputMode === 'research'" class="space-y-3">
420
465
  <UInput
@@ -447,7 +492,11 @@ function formatScore(score: number): string {
447
492
  </div>
448
493
 
449
494
  <UButton
450
- :label="activeInputMode === 'research' ? 'Research & Index' : 'Ingest'"
495
+ :label="
496
+ activeInputMode === 'research' ? 'Research & Index'
497
+ : activeInputMode === 'bulk' ? `Ingest ${bulkUrlCount} source${bulkUrlCount === 1 ? '' : 's'}`
498
+ : 'Ingest'
499
+ "
451
500
  icon="i-lucide-zap"
452
501
  size="md"
453
502
  :disabled="!canIngest && !(activeInputMode === 'text' && pasteText.length > 50)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.71.0",
3
+ "version": "2.73.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "2.71.0"
3
+ version = "2.73.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -487,6 +487,48 @@ def knowledge_ingest(body: dict):
487
487
  return {"job_id": job.id, "source_type": source_type, "status": "queued"}
488
488
 
489
489
 
490
+ @app.post("/api/knowledge/ingest-bulk")
491
+ def knowledge_ingest_bulk(body: dict):
492
+ """PR56 v2.73.0 — bulk URL ingest.
493
+
494
+ Accepts ``{"sources": ["url1", "url2", ...]}`` and queues one
495
+ background job per source. Returns ``{"jobs": [{...}, ...]}`` so the
496
+ dashboard can subscribe to each via the existing /ws/tasks stream.
497
+ Empty / whitespace-only lines are filtered. Duplicates collapse on
498
+ the JobManager side (one job per unique source).
499
+ """
500
+ raw_sources = body.get("sources") or []
501
+ if not isinstance(raw_sources, list):
502
+ return {"error": "sources must be a list"}
503
+ cleaned = []
504
+ seen: set[str] = set()
505
+ for raw in raw_sources:
506
+ if not isinstance(raw, str):
507
+ continue
508
+ s = raw.strip()
509
+ if not s or s in seen:
510
+ continue
511
+ seen.add(s)
512
+ cleaned.append(s)
513
+ if not cleaned:
514
+ return {"error": "no valid sources provided"}
515
+ if len(cleaned) > 50:
516
+ return {"error": "bulk ingest is capped at 50 sources per request"}
517
+ jobs = []
518
+ for source in cleaned:
519
+ result = knowledge_ingest({"source": source})
520
+ if "error" in result:
521
+ jobs.append({"source": source, "error": result["error"]})
522
+ else:
523
+ jobs.append({
524
+ "source": source,
525
+ "job_id": result["job_id"],
526
+ "source_type": result.get("source_type"),
527
+ "status": result.get("status", "queued"),
528
+ })
529
+ return {"jobs": jobs, "count": len(jobs)}
530
+
531
+
490
532
  @app.get("/api/tasks/{task_id}")
491
533
  def task_detail(task_id: str):
492
534
  """Get a single task by ID. Also checks jobs."""
@@ -125,12 +125,26 @@ def write_index(results: list[ExportResult]) -> Path:
125
125
  "",
126
126
  "Open-spec exports of ArkaOS's outward-facing development skills.",
127
127
  "Compatible with any Agent Skills runtime "
128
- "(see https://agentskills.io).",
128
+ "(see https://agentskills.io) — Claude Code, Codex CLI, Cursor, "
129
+ "VS Code Copilot, Atlassian, Figma.",
130
+ "",
131
+ "## Install via Claude Code Plugin Marketplace",
132
+ "",
133
+ "Register this repository as a marketplace, then install the bundle:",
134
+ "",
135
+ "```",
136
+ "/plugin marketplace add andreagroferreira/arka-os",
137
+ "/plugin install arkaos-dev-skills@arkaos",
138
+ "```",
139
+ "",
140
+ "After install, the ten skills below are available in your Claude "
141
+ "Code session. Mention a skill by name (e.g., *\"use code-review on "
142
+ "this PR\"*) and Claude loads the relevant `SKILL.md`.",
129
143
  "",
130
144
  "## Catalog",
131
145
  "",
132
146
  ]
133
- for r in results:
147
+ for r in sorted(results, key=lambda x: x.slug):
134
148
  lines.append(f"- [{r.slug}]({r.slug}/SKILL.md)")
135
149
  lines.append("")
136
150
  lines.append(