@nebulacomponents/citable 1.4.0 → 1.5.1

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 (137) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +34 -6
  3. package/dist/universal/.agents/skills/citable/SKILL.md +8 -3
  4. package/dist/universal/.agents/skills/citable/VERSION +1 -1
  5. package/dist/universal/.agents/skills/citable/commands/README.md +4 -1
  6. package/dist/universal/.agents/skills/citable/commands/measurement.md +17 -0
  7. package/dist/universal/.agents/skills/citable/manifest.json +12 -8
  8. package/dist/universal/.agents/skills/citable/schemas/connection.schema.json +32 -0
  9. package/dist/universal/.agents/skills/citable/schemas/intervention.schema.json +33 -0
  10. package/dist/universal/.agents/skills/citable/schemas/metric.schema.json +42 -0
  11. package/dist/universal/.agents/skills/citable/schemas/objective.schema.json +65 -0
  12. package/dist/universal/.agents/skills/citable/schemas/observation.schema.json +1 -1
  13. package/dist/universal/.claude/skills/citable/SKILL.md +8 -3
  14. package/dist/universal/.claude/skills/citable/VERSION +1 -1
  15. package/dist/universal/.claude/skills/citable/commands/README.md +4 -1
  16. package/dist/universal/.claude/skills/citable/commands/measurement.md +17 -0
  17. package/dist/universal/.claude/skills/citable/manifest.json +12 -8
  18. package/dist/universal/.claude/skills/citable/schemas/connection.schema.json +32 -0
  19. package/dist/universal/.claude/skills/citable/schemas/intervention.schema.json +33 -0
  20. package/dist/universal/.claude/skills/citable/schemas/metric.schema.json +42 -0
  21. package/dist/universal/.claude/skills/citable/schemas/objective.schema.json +65 -0
  22. package/dist/universal/.claude/skills/citable/schemas/observation.schema.json +1 -1
  23. package/dist/universal/.cursor/skills/citable/SKILL.md +8 -3
  24. package/dist/universal/.cursor/skills/citable/VERSION +1 -1
  25. package/dist/universal/.cursor/skills/citable/commands/README.md +4 -1
  26. package/dist/universal/.cursor/skills/citable/commands/measurement.md +17 -0
  27. package/dist/universal/.cursor/skills/citable/manifest.json +12 -8
  28. package/dist/universal/.cursor/skills/citable/schemas/connection.schema.json +32 -0
  29. package/dist/universal/.cursor/skills/citable/schemas/intervention.schema.json +33 -0
  30. package/dist/universal/.cursor/skills/citable/schemas/metric.schema.json +42 -0
  31. package/dist/universal/.cursor/skills/citable/schemas/objective.schema.json +65 -0
  32. package/dist/universal/.cursor/skills/citable/schemas/observation.schema.json +1 -1
  33. package/dist/universal/.gemini/skills/citable/SKILL.md +8 -3
  34. package/dist/universal/.gemini/skills/citable/VERSION +1 -1
  35. package/dist/universal/.gemini/skills/citable/commands/README.md +4 -1
  36. package/dist/universal/.gemini/skills/citable/commands/measurement.md +17 -0
  37. package/dist/universal/.gemini/skills/citable/manifest.json +12 -8
  38. package/dist/universal/.gemini/skills/citable/schemas/connection.schema.json +32 -0
  39. package/dist/universal/.gemini/skills/citable/schemas/intervention.schema.json +33 -0
  40. package/dist/universal/.gemini/skills/citable/schemas/metric.schema.json +42 -0
  41. package/dist/universal/.gemini/skills/citable/schemas/objective.schema.json +65 -0
  42. package/dist/universal/.gemini/skills/citable/schemas/observation.schema.json +1 -1
  43. package/dist/universal/.github/skills/citable/SKILL.md +8 -3
  44. package/dist/universal/.github/skills/citable/VERSION +1 -1
  45. package/dist/universal/.github/skills/citable/commands/README.md +4 -1
  46. package/dist/universal/.github/skills/citable/commands/measurement.md +17 -0
  47. package/dist/universal/.github/skills/citable/manifest.json +12 -8
  48. package/dist/universal/.github/skills/citable/schemas/connection.schema.json +32 -0
  49. package/dist/universal/.github/skills/citable/schemas/intervention.schema.json +33 -0
  50. package/dist/universal/.github/skills/citable/schemas/metric.schema.json +42 -0
  51. package/dist/universal/.github/skills/citable/schemas/objective.schema.json +65 -0
  52. package/dist/universal/.github/skills/citable/schemas/observation.schema.json +1 -1
  53. package/dist/universal/.kiro/skills/citable/SKILL.md +8 -3
  54. package/dist/universal/.kiro/skills/citable/VERSION +1 -1
  55. package/dist/universal/.kiro/skills/citable/commands/README.md +4 -1
  56. package/dist/universal/.kiro/skills/citable/commands/measurement.md +17 -0
  57. package/dist/universal/.kiro/skills/citable/manifest.json +12 -8
  58. package/dist/universal/.kiro/skills/citable/schemas/connection.schema.json +32 -0
  59. package/dist/universal/.kiro/skills/citable/schemas/intervention.schema.json +33 -0
  60. package/dist/universal/.kiro/skills/citable/schemas/metric.schema.json +42 -0
  61. package/dist/universal/.kiro/skills/citable/schemas/objective.schema.json +65 -0
  62. package/dist/universal/.kiro/skills/citable/schemas/observation.schema.json +1 -1
  63. package/dist/universal/.opencode/skills/citable/SKILL.md +8 -3
  64. package/dist/universal/.opencode/skills/citable/VERSION +1 -1
  65. package/dist/universal/.opencode/skills/citable/commands/README.md +4 -1
  66. package/dist/universal/.opencode/skills/citable/commands/measurement.md +17 -0
  67. package/dist/universal/.opencode/skills/citable/manifest.json +12 -8
  68. package/dist/universal/.opencode/skills/citable/schemas/connection.schema.json +32 -0
  69. package/dist/universal/.opencode/skills/citable/schemas/intervention.schema.json +33 -0
  70. package/dist/universal/.opencode/skills/citable/schemas/metric.schema.json +42 -0
  71. package/dist/universal/.opencode/skills/citable/schemas/objective.schema.json +65 -0
  72. package/dist/universal/.opencode/skills/citable/schemas/observation.schema.json +1 -1
  73. package/dist/universal/.pi/agent/skills/citable/SKILL.md +8 -3
  74. package/dist/universal/.pi/agent/skills/citable/VERSION +1 -1
  75. package/dist/universal/.pi/agent/skills/citable/commands/README.md +4 -1
  76. package/dist/universal/.pi/agent/skills/citable/commands/measurement.md +17 -0
  77. package/dist/universal/.pi/agent/skills/citable/manifest.json +12 -8
  78. package/dist/universal/.pi/agent/skills/citable/schemas/connection.schema.json +32 -0
  79. package/dist/universal/.pi/agent/skills/citable/schemas/intervention.schema.json +33 -0
  80. package/dist/universal/.pi/agent/skills/citable/schemas/metric.schema.json +42 -0
  81. package/dist/universal/.pi/agent/skills/citable/schemas/objective.schema.json +65 -0
  82. package/dist/universal/.pi/agent/skills/citable/schemas/observation.schema.json +1 -1
  83. package/dist/universal/.qoder/skills/citable/SKILL.md +8 -3
  84. package/dist/universal/.qoder/skills/citable/VERSION +1 -1
  85. package/dist/universal/.qoder/skills/citable/commands/README.md +4 -1
  86. package/dist/universal/.qoder/skills/citable/commands/measurement.md +17 -0
  87. package/dist/universal/.qoder/skills/citable/manifest.json +12 -8
  88. package/dist/universal/.qoder/skills/citable/schemas/connection.schema.json +32 -0
  89. package/dist/universal/.qoder/skills/citable/schemas/intervention.schema.json +33 -0
  90. package/dist/universal/.qoder/skills/citable/schemas/metric.schema.json +42 -0
  91. package/dist/universal/.qoder/skills/citable/schemas/objective.schema.json +65 -0
  92. package/dist/universal/.qoder/skills/citable/schemas/observation.schema.json +1 -1
  93. package/dist/universal/.rovodev/skills/citable/SKILL.md +8 -3
  94. package/dist/universal/.rovodev/skills/citable/VERSION +1 -1
  95. package/dist/universal/.rovodev/skills/citable/commands/README.md +4 -1
  96. package/dist/universal/.rovodev/skills/citable/commands/measurement.md +17 -0
  97. package/dist/universal/.rovodev/skills/citable/manifest.json +12 -8
  98. package/dist/universal/.rovodev/skills/citable/schemas/connection.schema.json +32 -0
  99. package/dist/universal/.rovodev/skills/citable/schemas/intervention.schema.json +33 -0
  100. package/dist/universal/.rovodev/skills/citable/schemas/metric.schema.json +42 -0
  101. package/dist/universal/.rovodev/skills/citable/schemas/objective.schema.json +65 -0
  102. package/dist/universal/.rovodev/skills/citable/schemas/observation.schema.json +1 -1
  103. package/dist/universal/.trae/skills/citable/SKILL.md +8 -3
  104. package/dist/universal/.trae/skills/citable/VERSION +1 -1
  105. package/dist/universal/.trae/skills/citable/commands/README.md +4 -1
  106. package/dist/universal/.trae/skills/citable/commands/measurement.md +17 -0
  107. package/dist/universal/.trae/skills/citable/manifest.json +12 -8
  108. package/dist/universal/.trae/skills/citable/schemas/connection.schema.json +32 -0
  109. package/dist/universal/.trae/skills/citable/schemas/intervention.schema.json +33 -0
  110. package/dist/universal/.trae/skills/citable/schemas/metric.schema.json +42 -0
  111. package/dist/universal/.trae/skills/citable/schemas/objective.schema.json +65 -0
  112. package/dist/universal/.trae/skills/citable/schemas/observation.schema.json +1 -1
  113. package/dist/universal/.trae-cn/skills/citable/SKILL.md +8 -3
  114. package/dist/universal/.trae-cn/skills/citable/VERSION +1 -1
  115. package/dist/universal/.trae-cn/skills/citable/commands/README.md +4 -1
  116. package/dist/universal/.trae-cn/skills/citable/commands/measurement.md +17 -0
  117. package/dist/universal/.trae-cn/skills/citable/manifest.json +12 -8
  118. package/dist/universal/.trae-cn/skills/citable/schemas/connection.schema.json +32 -0
  119. package/dist/universal/.trae-cn/skills/citable/schemas/intervention.schema.json +33 -0
  120. package/dist/universal/.trae-cn/skills/citable/schemas/metric.schema.json +42 -0
  121. package/dist/universal/.trae-cn/skills/citable/schemas/objective.schema.json +65 -0
  122. package/dist/universal/.trae-cn/skills/citable/schemas/observation.schema.json +1 -1
  123. package/dist/universal/manifest.json +147 -99
  124. package/package.json +4 -1
  125. package/schemas/connection.schema.json +32 -0
  126. package/schemas/intervention.schema.json +33 -0
  127. package/schemas/metric.schema.json +42 -0
  128. package/schemas/objective.schema.json +65 -0
  129. package/schemas/observation.schema.json +1 -1
  130. package/skill/SKILL.md +8 -3
  131. package/skill/commands/README.md +4 -1
  132. package/skill/commands/measurement.md +17 -0
  133. package/src/cli/index.js +28 -0
  134. package/src/commands/measurement.js +205 -0
  135. package/src/installer/index.js +35 -7
  136. package/src/installer/providers.js +1 -1
  137. package/src/registries/index.js +13 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebulacomponents/citable",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "description": "Production-grade SEO, AEO, and GEO agent skill with auditable detection, claim governance, evidence validation, and multi-agent installation.",
5
5
  "keywords": [
6
6
  "seo",
@@ -52,6 +52,8 @@
52
52
  "test:integration": "node --test --test-concurrency=1 --test-reporter=spec 'tests/integration/**/*.test.js'",
53
53
  "test:e2e": "node --test --test-concurrency=1 'tests/e2e/**/*.test.js'",
54
54
  "build:dist": "node scripts/build-dist.js",
55
+ "release:prepare": "node scripts/release-process.js prepare",
56
+ "release:validate": "node scripts/release-process.js validate",
55
57
  "prepack": "npm run build:dist"
56
58
  },
57
59
  "bin": {
@@ -86,6 +88,7 @@
86
88
  "dependencies": {
87
89
  "ajv": "^8.20.0",
88
90
  "ajv-formats": "^3.0.1",
91
+ "csv-parse": "^7.0.1",
89
92
  "js-yaml": "^5.2.1",
90
93
  "node-html-parser": "^9.0.0"
91
94
  },
@@ -0,0 +1,32 @@
1
+ {
2
+ "$id": "citable://schemas/connection.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Optional connector state registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "connections" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["connection_id", "provider", "state", "authentication", "scopes", "limitations"],
18
+ "properties": {
19
+ "connection_id": { "type": "string", "pattern": "^CONNECTION-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "provider": { "type": "string", "minLength": 1 },
21
+ "property_id": { "type": ["string", "null"] },
22
+ "state": { "type": "string", "enum": ["not_configured", "configured", "synchronized", "partial", "authorization_expired", "permission_denied", "quota_limited", "temporarily_unavailable"] },
23
+ "authentication": { "type": "string", "enum": ["none", "environment", "os_keychain", "external_secret_manager", "owner_import"] },
24
+ "scopes": { "type": "array", "items": { "type": "string" } },
25
+ "last_synchronized_at": { "type": ["string", "null"], "format": "date-time" },
26
+ "cursor": { "type": ["string", "null"] },
27
+ "limitations": { "type": "array", "items": { "type": "string" } }
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "$id": "citable://schemas/intervention.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Intervention registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "interventions" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["intervention_id", "name", "status", "deployed_at", "affected_urls", "finding_ids", "objective_ids", "limitations"],
18
+ "properties": {
19
+ "intervention_id": { "type": "string", "pattern": "^INTERVENTION-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "status": { "type": "string", "enum": ["planned", "deployed", "rolled_back", "superseded"] },
22
+ "deployed_at": { "type": ["string", "null"], "format": "date-time" },
23
+ "commit": { "type": ["string", "null"] },
24
+ "pull_request": { "type": ["string", "null"] },
25
+ "affected_urls": { "type": "array", "items": { "type": "string" } },
26
+ "finding_ids": { "type": "array", "items": { "type": "string" } },
27
+ "objective_ids": { "type": "array", "items": { "type": "string", "pattern": "^OBJECTIVE-" } },
28
+ "limitations": { "type": "array", "items": { "type": "string" } }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,42 @@
1
+ {
2
+ "$id": "citable://schemas/metric.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Metric registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "metrics" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["metric_id", "name", "provider", "external_name", "unit", "value_type", "aggregation", "direction", "dimensions", "limitations"],
18
+ "properties": {
19
+ "metric_id": { "type": "string", "pattern": "^METRIC-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "description": { "type": "string" },
22
+ "provider": { "type": "string", "minLength": 1 },
23
+ "external_name": { "type": "string", "minLength": 1 },
24
+ "unit": { "type": "string", "enum": ["count", "ratio", "percent", "currency", "duration_ms", "position", "score", "custom"] },
25
+ "custom_unit": { "type": "string", "minLength": 1 },
26
+ "value_type": { "type": "string", "enum": ["integer", "number"] },
27
+ "aggregation": { "type": "string", "enum": ["sum", "average", "minimum", "maximum", "latest", "provider_defined"] },
28
+ "direction": { "type": "string", "enum": ["increase", "decrease", "maintain", "contextual"] },
29
+ "dimensions": { "type": "array", "uniqueItems": true, "items": { "type": "string", "minLength": 1 } },
30
+ "limitations": { "type": "array", "items": { "type": "string", "minLength": 1 } },
31
+ "status": { "type": "string", "enum": ["active", "deprecated"] }
32
+ },
33
+ "allOf": [
34
+ {
35
+ "if": { "properties": { "unit": { "const": "custom" } } },
36
+ "then": { "required": ["custom_unit"] }
37
+ }
38
+ ]
39
+ }
40
+ }
41
+ }
42
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "$id": "citable://schemas/objective.schema.json",
3
+ "$schema": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Objective registry",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["version", "kind", "updated", "entries"],
8
+ "properties": {
9
+ "version": { "const": 1 },
10
+ "kind": { "const": "objectives" },
11
+ "updated": { "type": "string", "format": "date-time" },
12
+ "entries": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "additionalProperties": false,
17
+ "required": ["objective_id", "name", "status", "primary_metrics", "supporting_metrics", "guardrails", "cohort", "comparison"],
18
+ "properties": {
19
+ "objective_id": { "type": "string", "pattern": "^OBJECTIVE-[A-Z0-9][A-Z0-9_-]*$" },
20
+ "name": { "type": "string", "minLength": 1 },
21
+ "description": { "type": "string" },
22
+ "owner": { "type": ["string", "null"] },
23
+ "status": { "type": "string", "enum": ["draft", "active", "paused", "archived"] },
24
+ "primary_metrics": { "type": "array", "minItems": 1, "uniqueItems": true, "items": { "$ref": "#/definitions/metricRef" } },
25
+ "supporting_metrics": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/definitions/metricRef" } },
26
+ "guardrails": { "type": "array", "items": { "$ref": "#/definitions/guardrail" } },
27
+ "cohort": { "$ref": "#/definitions/cohort" },
28
+ "comparison": {
29
+ "type": "object",
30
+ "additionalProperties": false,
31
+ "required": ["baseline_days", "evaluation_days", "minimum_observations"],
32
+ "properties": {
33
+ "baseline_days": { "type": "integer", "minimum": 1 },
34
+ "evaluation_days": { "type": "integer", "minimum": 1 },
35
+ "minimum_observations": { "type": "integer", "minimum": 1 }
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ },
42
+ "definitions": {
43
+ "metricRef": { "type": "string", "pattern": "^METRIC-[A-Z0-9][A-Z0-9_-]*$" },
44
+ "guardrail": {
45
+ "type": "object",
46
+ "additionalProperties": false,
47
+ "required": ["metric_id", "operator", "threshold"],
48
+ "properties": {
49
+ "metric_id": { "$ref": "#/definitions/metricRef" },
50
+ "operator": { "type": "string", "enum": ["gte", "lte", "increase_by", "decrease_by", "no_regression"] },
51
+ "threshold": { "type": "number" }
52
+ }
53
+ },
54
+ "cohort": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["urls", "queries", "labels"],
58
+ "properties": {
59
+ "urls": { "type": "array", "items": { "type": "string" } },
60
+ "queries": { "type": "array", "items": { "type": "string" } },
61
+ "labels": { "type": "array", "items": { "type": "string" } }
62
+ }
63
+ }
64
+ }
65
+ }
@@ -7,7 +7,7 @@
7
7
  "required": ["observation_id", "kind", "state", "collected_at", "collection_method", "confidence", "source", "evidence_hash", "data"],
8
8
  "properties": {
9
9
  "observation_id": { "type": "string", "minLength": 1 },
10
- "kind": { "type": "string", "enum": ["render", "index", "citation", "citation_review", "crawler_log", "passage", "canonical_freshness", "performance", "corroboration"] },
10
+ "kind": { "type": "string", "enum": ["render", "index", "citation", "citation_review", "crawler_log", "passage", "canonical_freshness", "performance", "corroboration", "metric"] },
11
11
  "state": { "type": "string", "enum": ["observed", "not_observed", "not_evidenced", "incomplete", "review_required", "failed"] },
12
12
  "collected_at": { "type": "string", "format": "date-time" },
13
13
  "collection_method": { "type": "string", "enum": ["live_api", "browser", "owner_import", "synthetic_fetch", "static_analysis", "human_review"] },
package/skill/SKILL.md CHANGED
@@ -7,7 +7,7 @@ description: >
7
7
  generative engine optimization, structured data governance, claim substantiation,
8
8
  crawler policy, entity consistency, content discoverability, or wants a site audited
9
9
  for how search and AI systems will retrieve, understand, cite, or recommend it.
10
- version: 1.4.0
10
+ version: 1.5.1
11
11
  ---
12
12
 
13
13
  # Citable — search & generative discoverability governance
@@ -88,6 +88,10 @@ citable action-plan [run-id] # ordered actions, blockers, semantic gate
88
88
  citable observe <mode> [options] # render/index/citation/log/passage/consensus/performance evidence
89
89
  citable apply --input <spec> [--write] # reviewed, hash-locked remediation; dry run by default
90
90
  citable monitor [runA runB] # observation regression alerts
91
+ citable metrics import --provider <name> --input <csv|json>
92
+ citable objectives init --input <json|yaml> [--write]
93
+ citable objectives validate
94
+ citable evaluate [objective-id] [--ref-date YYYY-MM-DD]
91
95
  ```
92
96
 
93
97
  Audit scopes: `technical seo aeo geo architecture entity claims evidence schema
@@ -144,8 +148,9 @@ mandatory.
144
148
 
145
149
  ## Registries are the source of truth
146
150
 
147
- `.citable/` holds nine YAML registries (queries, prompts, entities, claims,
148
- evidence, pages, crawlers, competitors, experiments), all schema-validated
151
+ `.citable/` holds YAML registries for queries, prompts, entities, claims,
152
+ evidence, pages, crawlers, competitors, experiments, metrics, objectives,
153
+ interventions, and optional connection state, all schema-validated
149
154
  (`schemas/*.schema.json`) with referential integrity checks. Rules:
150
155
 
151
156
  - Never overwrite registry content without history — use the loader/saver in
@@ -24,11 +24,14 @@ intent, not executability, so statuses are strict:
24
24
  | /citable observe | implemented — render, index, citation, crawler-log, passage, consensus, performance, and corroboration evidence |
25
25
  | /citable apply | implemented — reviewed, hash-locked source replacements; dry run by default |
26
26
  | /citable monitor | implemented — compares immutable observation runs and emits evidence-linked alerts |
27
+ | /citable metrics import | implemented — validates declared CSV/JSON metrics and writes immutable observations |
28
+ | /citable objectives init / validate | implemented — user-owned metric selection, cohorts, windows, and guardrails |
29
+ | /citable evaluate | implemented — independent baseline/evaluation comparisons with inconclusive handling |
27
30
  | /citable ingest, map-site, map-queries, map-prompts, map-entities, map-evidence | specified (ingest.md) |
28
31
  | /citable optimize-page | specified (optimize-page.md) — requires source-to-render mapping, claim-preserving rewrites, build execution, rollback; none of that is proven yet |
29
32
  | /citable create-page, answer-block, architect, interlink, consolidate, metadata | specified (page-work.md) |
30
33
  | /citable validate-render | partially implemented by `observe render`; mobile/cross-browser and interaction exploration remain |
31
- | /citable measure seo/aeo/geo, test-prompts, monitor-crawlers, monitor-contradictions | partially implemented by observation imports, Google/CrUX live APIs, custom citation adapters, and monitor comparisons |
34
+ | /citable measure seo/aeo/geo, test-prompts, monitor-crawlers, monitor-contradictions | partially implemented by metric/observation imports, objective evaluation, Google/CrUX live APIs, custom citation adapters, and monitor comparisons |
32
35
 
33
36
  Shared refusal conditions (all commands): any action that would fabricate
34
37
  facts, citations, evidence, authorship, reviews, corroboration, or hidden
@@ -10,6 +10,23 @@ imports, and a disclosed custom citation-adapter protocol. It does not claim
10
10
  Bing AI Performance automation or equivalence between an API adapter and a
11
11
  consumer answer product. Preserve collector and product mode on every result.
12
12
 
13
+ # implemented metric and objective workflow
14
+
15
+ `citable metrics import --provider <name> --input <csv|json>` validates every
16
+ row against a declared entry in `metrics.yaml` and writes immutable metric
17
+ observations. `citable objectives init --input <json|yaml>` is dry-run by
18
+ default; `--write` adds the validated objective with registry history.
19
+ `citable objectives validate` checks contracts and metric references.
20
+ `citable evaluate [objective-id]` compares independent baseline and evaluation
21
+ windows, deduplicates repeated evidence, and reports each metric and guardrail
22
+ separately. Insufficient data is `inconclusive`, never zero. Results describe
23
+ temporal association and do not establish causation.
24
+
25
+ Metrics, objectives, interventions, and connection state are optional. The
26
+ absence of provider credentials is `not_configured`, not a finding and not
27
+ evidence of zero activity. Credentials must never enter registries or evidence
28
+ packages.
29
+
13
30
  # measure seo
14
31
  Inputs the operator exports (Search Console/Bing/analytics CSV or JSON).
15
32
  Workflow: validate segmentation (brand vs non-brand, intent, page type,
package/src/cli/index.js CHANGED
@@ -15,6 +15,7 @@ import { actionPlan } from '../commands/actionPlan.js';
15
15
  import { observe } from '../commands/observe.js';
16
16
  import { applyRemediation } from '../commands/applyRemediation.js';
17
17
  import { monitor } from '../commands/monitor.js';
18
+ import { evaluateObjective, importMetrics, initializeObjective, validateObjectives } from '../commands/measurement.js';
18
19
 
19
20
  const HELP = `citable — SEO / AEO / GEO audit, remediation, validation, and governance
20
21
 
@@ -41,6 +42,10 @@ Commands
41
42
  consensus, performance, or corroboration evidence
42
43
  apply Apply a reviewed, hash-locked remediation spec
43
44
  monitor [runA runB] Compare observation runs and emit regression alerts
45
+ metrics import Import declared metric observations from CSV/JSON
46
+ objectives init Validate/add one objective from --input (--write to save)
47
+ objectives validate Validate objective contracts and metric references
48
+ evaluate [objective-id] Compare objective baseline and evaluation windows
44
49
  self-upgrade Check for a newer version and upgrade the npx cache
45
50
 
46
51
  Options
@@ -167,6 +172,29 @@ export async function main(argv = process.argv.slice(2), options = {}) {
167
172
  if (r.summary.critical_or_high > 0) process.exitCode = 1;
168
173
  break;
169
174
  }
175
+ case 'metrics': {
176
+ if (args._[0] !== 'import') throw new Error('usage: citable metrics import --provider <name> --input <csv|json>');
177
+ const r = importMetrics(root, { input: args.input, provider: args.provider });
178
+ out(args, `metrics import: ${r.summary.total} observation(s) from ${args.provider}\nEvidence package: ${r.dir}`, r);
179
+ break;
180
+ }
181
+ case 'objectives': {
182
+ const mode = args._[0];
183
+ if (mode === 'init') {
184
+ const r = initializeObjective(root, { input: args.input, write: args.write });
185
+ out(args, `objectives init: ${r.objective.objective_id} ${r.written ? 'written' : 'valid (dry run; use --write to save)'}`, r);
186
+ } else if (mode === 'validate') {
187
+ const r = validateObjectives(root);
188
+ out(args, `objectives validate: ${r.ok ? 'OK' : 'PROBLEMS'} (${r.count} objective(s))${r.problems.length ? `\n${r.problems.map((problem) => ` - ${problem}`).join('\n')}` : ''}`, r);
189
+ if (!r.ok) process.exitCode = 1;
190
+ } else throw new Error('usage: citable objectives <init|validate> [--input <json|yaml>] [--write]');
191
+ break;
192
+ }
193
+ case 'evaluate': {
194
+ const r = evaluateObjective(root, { objectiveId: args._[0], refDate: args.refDate });
195
+ out(args, `evaluate ${r.objective_id}: ${r.status}\n` + r.metrics.map((metric) => ` ${metric.metric_id}: ${metric.state}${metric.state === 'observed' ? ` (${metric.baseline} → ${metric.evaluation})` : ''}`).join('\n') + `\n${r.interpretation}`, r);
196
+ break;
197
+ }
170
198
  case 'self-upgrade': {
171
199
  const output = await selfUpgradeCommand(argv.slice(1));
172
200
  console.log(output);
@@ -0,0 +1,205 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { parse as parseCsv } from 'csv-parse/sync';
4
+ import { loadRegistries, saveRegistry } from '../registries/index.js';
5
+ import { envelope, observationRun } from '../observations/common.js';
6
+ import { parseRefDate, readJson, readYaml } from '../shared/io.js';
7
+ import { validateAgainst } from '../shared/schemaValidator.js';
8
+
9
+ function inputDocument(file) {
10
+ if (!file) throw new Error('--input <file> is required');
11
+ if (!fs.existsSync(file)) throw new Error(`input not found: ${file}`);
12
+ const raw = fs.readFileSync(file, 'utf8');
13
+ const ext = path.extname(file).toLowerCase();
14
+ if (ext === '.csv') return { raw, rows: parseCsv(raw, { columns: true, skip_empty_lines: true, trim: true }), file: path.resolve(file) };
15
+ const value = ext === '.yaml' || ext === '.yml' ? readYaml(file) : readJson(file);
16
+ return { raw, rows: Array.isArray(value) ? value : value.rows || value.entries || [value], file: path.resolve(file) };
17
+ }
18
+
19
+ function numericValue(raw, type, rowNumber) {
20
+ if (raw === '' || raw == null) throw new Error(`row ${rowNumber}: value is required`);
21
+ const value = Number(raw);
22
+ if (!Number.isFinite(value)) throw new Error(`row ${rowNumber}: value must be numeric`);
23
+ if (type === 'integer' && !Number.isInteger(value)) throw new Error(`row ${rowNumber}: value must be an integer`);
24
+ return value;
25
+ }
26
+
27
+ function isoTime(raw, field, rowNumber) {
28
+ if (!raw || Number.isNaN(new Date(raw).getTime())) throw new Error(`row ${rowNumber}: ${field} must be a valid date-time`);
29
+ return new Date(raw).toISOString();
30
+ }
31
+
32
+ export function importMetrics(root, { input, provider }) {
33
+ if (!provider) throw new Error('metrics import requires --provider <name>');
34
+ const source = inputDocument(input);
35
+ if (!source.rows.length) throw new Error('metric import contains no rows');
36
+ const { registries, problems } = loadRegistries(root);
37
+ if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
38
+ const definitions = new Map(registries.metrics.entries.map((metric) => [metric.metric_id, metric]));
39
+ const observations = source.rows.map((row, index) => {
40
+ const rowNumber = index + 1;
41
+ const metric = definitions.get(row.metric_id);
42
+ if (!metric) throw new Error(`row ${rowNumber}: unknown metric_id ${row.metric_id || '(missing)'}`);
43
+ if (metric.status === 'deprecated') throw new Error(`row ${rowNumber}: metric ${metric.metric_id} is deprecated`);
44
+ if (metric.provider.toLowerCase() !== String(provider).toLowerCase()) {
45
+ throw new Error(`row ${rowNumber}: metric ${metric.metric_id} belongs to ${metric.provider}, not ${provider}`);
46
+ }
47
+ const observedAt = isoTime(row.observed_at || row.date, 'observed_at', rowNumber);
48
+ const dimensions = {};
49
+ for (const dimension of metric.dimensions) {
50
+ if (row[dimension] != null && row[dimension] !== '') dimensions[dimension] = String(row[dimension]);
51
+ }
52
+ const data = {
53
+ metric_id: metric.metric_id,
54
+ provider: metric.provider,
55
+ external_name: metric.external_name,
56
+ value: numericValue(row.value, metric.value_type, rowNumber),
57
+ unit: metric.unit === 'custom' ? metric.custom_unit : metric.unit,
58
+ aggregation: metric.aggregation,
59
+ observed_at: observedAt,
60
+ period_start: row.period_start ? isoTime(row.period_start, 'period_start', rowNumber) : observedAt,
61
+ period_end: row.period_end ? isoTime(row.period_end, 'period_end', rowNumber) : observedAt,
62
+ dimensions,
63
+ source_row: rowNumber,
64
+ };
65
+ if (new Date(data.period_start) > new Date(data.period_end)) throw new Error(`row ${rowNumber}: period_start is after period_end`);
66
+ return envelope('metric', data, {
67
+ method: 'owner_import',
68
+ source: source.file,
69
+ raw: JSON.stringify(row),
70
+ limitations: metric.limitations,
71
+ });
72
+ });
73
+ return observationRun(root, 'metrics import', source.file, observations, { rawInputs: { metric_import: source.raw } });
74
+ }
75
+
76
+ function readObjectiveInput(input) {
77
+ const document = inputDocument(input);
78
+ if (document.rows.length !== 1) throw new Error('objectives init requires exactly one objective');
79
+ return document.rows[0];
80
+ }
81
+
82
+ export function initializeObjective(root, { input, write = false }) {
83
+ const objective = readObjectiveInput(input);
84
+ const { registries, problems } = loadRegistries(root);
85
+ if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
86
+ const candidate = { ...registries.objectives, entries: [...registries.objectives.entries, objective] };
87
+ const check = validateAgainst('objective.schema.json', candidate);
88
+ if (!check.valid) throw new Error(`objective violates contract: ${check.errors.join('; ')}`);
89
+ if (registries.objectives.entries.some((item) => item.objective_id === objective.objective_id)) throw new Error(`objective ${objective.objective_id} already exists`);
90
+ const metricIds = new Set(registries.metrics.entries.map((item) => item.metric_id));
91
+ const refs = [...objective.primary_metrics, ...objective.supporting_metrics, ...objective.guardrails.map((item) => item.metric_id)];
92
+ const unknown = [...new Set(refs.filter((ref) => !metricIds.has(ref)))];
93
+ if (unknown.length) throw new Error(`objective references unknown metrics: ${unknown.join(', ')}`);
94
+ if (write) saveRegistry(root, 'objectives', candidate);
95
+ return { objective, written: write, file: path.join(root, '.citable', 'objectives.yaml') };
96
+ }
97
+
98
+ export function validateObjectives(root) {
99
+ const { registries, problems } = loadRegistries(root);
100
+ const objectiveProblems = problems.filter((problem) => problem.startsWith('objectives/') || problem.startsWith('objectives.yaml'));
101
+ return { ok: objectiveProblems.length === 0, problems: objectiveProblems, count: registries.objectives.entries.length };
102
+ }
103
+
104
+ function metricObservations(root) {
105
+ const runs = path.join(root, '.citable', 'runs');
106
+ if (!fs.existsSync(runs)) return [];
107
+ const items = [];
108
+ for (const run of fs.readdirSync(runs)) {
109
+ const dir = path.join(runs, run, 'observations');
110
+ if (!fs.existsSync(dir)) continue;
111
+ for (const file of fs.readdirSync(dir).filter((name) => name.endsWith('-metric.json'))) {
112
+ const item = readJson(path.join(dir, file));
113
+ if (item.kind === 'metric') items.push(item);
114
+ }
115
+ }
116
+ return [...new Map(items.map((item) => [item.evidence_hash, item])).values()];
117
+ }
118
+
119
+ function inCohort(data, cohort) {
120
+ const url = data.dimensions.url || data.dimensions.page || null;
121
+ const query = data.dimensions.query || null;
122
+ const labels = String(data.dimensions.labels || data.dimensions.label || '').split(',').map((label) => label.trim()).filter(Boolean);
123
+ const urlMatch = !cohort.urls.length || (url && cohort.urls.some((pattern) => pattern.endsWith('*') ? url.startsWith(pattern.slice(0, -1)) : url === pattern));
124
+ const queryMatch = !cohort.queries.length || (query && cohort.queries.includes(query));
125
+ const labelMatch = !cohort.labels.length || cohort.labels.some((label) => labels.includes(label));
126
+ return urlMatch && queryMatch && labelMatch;
127
+ }
128
+
129
+ function aggregate(values, method) {
130
+ if (!values.length) return null;
131
+ if (method === 'sum') return values.reduce((sum, value) => sum + value, 0);
132
+ if (method === 'minimum') return Math.min(...values);
133
+ if (method === 'maximum') return Math.max(...values);
134
+ if (method === 'latest') return values.at(-1);
135
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
136
+ }
137
+
138
+ function evaluateGuardrail(guardrail, metric, definition) {
139
+ if (metric.state !== 'observed') return { ...guardrail, state: 'inconclusive', passed: null, reason: 'metric comparison is inconclusive' };
140
+ let passed;
141
+ if (guardrail.operator === 'gte') passed = metric.evaluation >= guardrail.threshold;
142
+ else if (guardrail.operator === 'lte') passed = metric.evaluation <= guardrail.threshold;
143
+ else if (guardrail.operator === 'increase_by') passed = metric.relative_change != null && metric.relative_change >= guardrail.threshold;
144
+ else if (guardrail.operator === 'decrease_by') passed = metric.relative_change != null && metric.relative_change <= -guardrail.threshold;
145
+ else if (definition.direction === 'increase') passed = metric.evaluation >= metric.baseline;
146
+ else if (definition.direction === 'decrease') passed = metric.evaluation <= metric.baseline;
147
+ else if (definition.direction === 'maintain') passed = metric.relative_change != null && Math.abs(metric.relative_change) <= guardrail.threshold;
148
+ else return { ...guardrail, state: 'inconclusive', passed: null, reason: 'contextual metric requires a user-defined directional guardrail' };
149
+ return { ...guardrail, state: 'observed', passed, reason: passed ? 'observed condition met' : 'observed condition not met' };
150
+ }
151
+
152
+ export function evaluateObjective(root, { objectiveId, refDate }) {
153
+ const { registries, problems } = loadRegistries(root);
154
+ if (problems.length) throw new Error(`registry validation failed: ${problems.join('; ')}`);
155
+ const objective = objectiveId ? registries.objectives.entries.find((item) => item.objective_id === objectiveId) : registries.objectives.entries[0];
156
+ if (!objective) throw new Error(objectiveId ? `objective not found: ${objectiveId}` : 'no objective configured');
157
+ const definitions = new Map(registries.metrics.entries.map((item) => [item.metric_id, item]));
158
+ const end = parseRefDate(refDate);
159
+ end.setUTCHours(23, 59, 59, 999);
160
+ const evaluationStart = new Date(end); evaluationStart.setUTCDate(evaluationStart.getUTCDate() - objective.comparison.evaluation_days + 1); evaluationStart.setUTCHours(0, 0, 0, 0);
161
+ const baselineEnd = new Date(evaluationStart.getTime() - 1);
162
+ const baselineStart = new Date(baselineEnd); baselineStart.setUTCDate(baselineStart.getUTCDate() - objective.comparison.baseline_days + 1); baselineStart.setUTCHours(0, 0, 0, 0);
163
+ const all = metricObservations(root).filter((item) => inCohort(item.data, objective.cohort));
164
+ const metricIds = [...new Set([...objective.primary_metrics, ...objective.supporting_metrics, ...objective.guardrails.map((item) => item.metric_id)])];
165
+ const metrics = metricIds.map((metricId) => {
166
+ const definition = definitions.get(metricId);
167
+ const observations = all.filter((item) => item.data.metric_id === metricId).sort((a, b) => a.data.observed_at.localeCompare(b.data.observed_at));
168
+ const baselineRows = observations.filter((item) => new Date(item.data.observed_at) >= baselineStart && new Date(item.data.observed_at) <= baselineEnd);
169
+ const evaluationRows = observations.filter((item) => new Date(item.data.observed_at) >= evaluationStart && new Date(item.data.observed_at) <= end);
170
+ const aggregatable = definition.aggregation !== 'provider_defined';
171
+ const enough = aggregatable && baselineRows.length >= objective.comparison.minimum_observations && evaluationRows.length >= objective.comparison.minimum_observations;
172
+ const baseline = enough ? aggregate(baselineRows.map((item) => item.data.value), definition.aggregation) : null;
173
+ const evaluation = enough ? aggregate(evaluationRows.map((item) => item.data.value), definition.aggregation) : null;
174
+ return {
175
+ metric_id: metricId,
176
+ provider: definition.provider,
177
+ state: enough ? 'observed' : 'inconclusive',
178
+ baseline,
179
+ evaluation,
180
+ absolute_change: enough ? evaluation - baseline : null,
181
+ relative_change: enough && baseline !== 0 ? (evaluation - baseline) / Math.abs(baseline) : null,
182
+ baseline_observations: baselineRows.length,
183
+ evaluation_observations: evaluationRows.length,
184
+ limitations: [
185
+ ...definition.limitations,
186
+ ...(!aggregatable ? ['provider_defined aggregation requires an explicit normalized aggregation before evaluation'] : []),
187
+ ...(aggregatable && !enough ? [`requires at least ${objective.comparison.minimum_observations} observations in each window`] : []),
188
+ ],
189
+ };
190
+ });
191
+ const guardrails = objective.guardrails.map((guardrail) => {
192
+ const metric = metrics.find((item) => item.metric_id === guardrail.metric_id);
193
+ return evaluateGuardrail(guardrail, metric, definitions.get(guardrail.metric_id));
194
+ });
195
+ return {
196
+ objective_id: objective.objective_id,
197
+ objective_name: objective.name,
198
+ windows: { baseline_start: baselineStart.toISOString(), baseline_end: baselineEnd.toISOString(), evaluation_start: evaluationStart.toISOString(), evaluation_end: end.toISOString() },
199
+ metrics,
200
+ guardrails,
201
+ guardrail_status: guardrails.some((item) => item.state === 'inconclusive') ? 'inconclusive' : guardrails.some((item) => !item.passed) ? 'not_met' : 'met',
202
+ status: metrics.every((metric) => metric.state === 'observed') ? 'observed' : 'inconclusive',
203
+ interpretation: 'Changes are temporal associations. This evaluation does not establish that an intervention caused an observed outcome.',
204
+ };
205
+ }
@@ -105,6 +105,8 @@ export function parseInstallerArgs(argv) {
105
105
  help: false,
106
106
  version: false,
107
107
  failOnUpdate: false,
108
+ installMode: 'copy',
109
+ skillSelection: null,
108
110
  _: [],
109
111
  };
110
112
 
@@ -118,17 +120,29 @@ export function parseInstallerArgs(argv) {
118
120
  else if (arg === '--dry-run') args.dryRun = true;
119
121
  else if (arg === '--force') args.force = true;
120
122
  else if (arg === '--json') args.json = true;
121
- else if (arg === '--all') args.all = true;
123
+ else if (arg === '--all') { args.all = true; args.yes = true; }
122
124
  else if (arg === '--help' || arg === '-h') args.help = true;
123
125
  else if (arg === '--version' || arg === '-v') args.version = true;
124
- else if (arg === '--project' || arg === '--local') args.scope = mergeScope(args.scope, 'project');
125
- else if (arg === '--global' || arg === '--user') args.scope = mergeScope(args.scope, 'global');
126
+ else if (arg === '--project' || arg === '--local' || arg === '-p') args.scope = mergeScope(args.scope, 'project');
127
+ else if (arg === '--global' || arg === '--user' || arg === '-g') args.scope = mergeScope(args.scope, 'global');
128
+ else if (arg === '--copy') args.installMode = 'copy';
129
+ else if (arg === '--symlink') throw new CitableInstallerError('--symlink is not supported: Citable uses hashed managed copies for atomic validation and safe uninstall', EXIT_CODES.invalidArguments);
126
130
  else if (arg === '--fail-on-update') args.failOnUpdate = true;
127
131
  else if (arg === '--providers') {
128
132
  if (i + 1 >= argv.length) throw new CitableInstallerError('--providers requires a value', EXIT_CODES.invalidArguments);
129
133
  args.providersRaw = argv[++i];
130
134
  } else if (arg.startsWith('--providers=')) {
131
135
  args.providersRaw = arg.slice('--providers='.length);
136
+ } else if (arg === '--agent' || arg === '-a') {
137
+ if (i + 1 >= argv.length) throw new CitableInstallerError(`${arg} requires a value`, EXIT_CODES.invalidArguments);
138
+ args.providersRaw = [args.providersRaw, argv[++i]].filter(Boolean).join(',');
139
+ } else if (arg.startsWith('--agent=')) {
140
+ args.providersRaw = [args.providersRaw, arg.slice('--agent='.length)].filter(Boolean).join(',');
141
+ } else if (arg === '--skill' || arg === '-s') {
142
+ if (i + 1 >= argv.length) throw new CitableInstallerError(`${arg} requires a value`, EXIT_CODES.invalidArguments);
143
+ args.skillSelection = argv[++i];
144
+ } else if (arg.startsWith('--skill=')) {
145
+ args.skillSelection = arg.slice('--skill='.length);
132
146
  } else if (arg === '--scope') {
133
147
  if (i + 1 >= argv.length) throw new CitableInstallerError('--scope requires a value', EXIT_CODES.invalidArguments);
134
148
  args.scope = mergeScope(args.scope, normalizeScopeValue(argv[++i]));
@@ -141,7 +155,16 @@ export function parseInstallerArgs(argv) {
141
155
  }
142
156
  }
143
157
 
144
- if (args.providersRaw !== null) {
158
+ if (args.skillSelection !== null && !['citable', '*'].includes(String(args.skillSelection).trim().toLowerCase())) {
159
+ throw new CitableInstallerError(`this package contains only the citable skill; unsupported --skill value: ${args.skillSelection}`, EXIT_CODES.invalidArguments);
160
+ }
161
+
162
+ if (args.all) {
163
+ if (args.providersRaw !== null) throw new CitableInstallerError('--all cannot be combined with --providers or --agent', EXIT_CODES.invalidArguments);
164
+ args.providerSelection = { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
165
+ }
166
+
167
+ if (args.providersRaw !== null && !args.providerSelection) {
145
168
  const parsed = parseProviderList(args.providersRaw);
146
169
  if (parsed.unknown.length) {
147
170
  const supported = PROVIDER_IDS.join(', ');
@@ -1102,12 +1125,17 @@ Install examples
1102
1125
  npx @nebulacomponents/citable install
1103
1126
  npx @nebulacomponents/citable install --yes
1104
1127
  npx @nebulacomponents/citable install --providers=claude,codex,cursor --project --yes
1128
+ npx @nebulacomponents/citable install --agent claude-code --agent codex --skill citable --copy -p -y
1105
1129
  npx @nebulacomponents/citable install --providers=all --global --dry-run
1106
1130
 
1107
1131
  Options
1108
- --providers=<list> Provider ids, detected, or all
1109
- --project, --local Install/check project-local skill locations
1110
- --global, --user Install/check user-global skill locations
1132
+ --providers=<list> Provider ids, detected, all, or *
1133
+ --agent=<id>, -a <id> Repeatable provider alias compatible with skills CLI
1134
+ --skill=<name>, -s <name> Select citable or * (this package contains one skill)
1135
+ --all Select every provider and confirm non-interactively
1136
+ --copy Use managed copies (default and only install mode)
1137
+ --project, --local, -p Install/check project-local skill locations
1138
+ --global, --user, -g Install/check user-global skill locations
1111
1139
  --scope=<project|global> Scope equivalent
1112
1140
  --yes, -y Confirm non-interactively
1113
1141
  --dry-run Preview filesystem changes without mutation
@@ -130,7 +130,7 @@ export function parseProviderList(value) {
130
130
  if (!raw) return { kind: 'explicit', providers: [], unknown: [] };
131
131
  const lowered = raw.toLowerCase();
132
132
  if (lowered === 'detected') return { kind: 'detected', providers: [], unknown: [] };
133
- if (lowered === 'all') return { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
133
+ if (lowered === 'all' || lowered === '*') return { kind: 'all', providers: PROVIDER_IDS, unknown: [] };
134
134
 
135
135
  const providers = [];
136
136
  const unknown = [];