@spira-lab/cli 0.0.4 → 0.0.5

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 (3) hide show
  1. package/README.md +22 -21
  2. package/dist/index.cjs +275 -3
  3. package/package.json +25 -2
package/README.md CHANGED
@@ -1,21 +1,22 @@
1
- # Spira CLI
2
-
3
- Command line client for Spira.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g @spira-lab/cli
9
- ```
10
-
11
- ## Usage
12
-
13
- ```bash
14
- spira --help
15
- spira auth login
16
- spira ai models list
17
- spira workflows create --name "Product launch workflow"
18
- spira workflows update <workflowId> --workflow-definition-json '{"nodes":[],"edges":[]}'
19
- ```
20
-
21
- Use `spira --json` when you need machine-readable output.
1
+ # Spira CLI
2
+
3
+ Command line client for Spira.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @spira-lab/cli
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ spira --help
15
+ spira auth login
16
+ spira ai models list
17
+ spira workflows node-catalog --json
18
+ spira workflows create --name "Product launch workflow"
19
+ spira workflows update <workflowId> --workflow-definition-json '{"nodes":[],"edges":[]}'
20
+ ```
21
+
22
+ Use `spira --json` when you need machine-readable output.
package/dist/index.cjs CHANGED
@@ -18049,6 +18049,7 @@ var cliResultTypeSchema = external_exports.enum([
18049
18049
  "aiModelList",
18050
18050
  "aiModelDetail",
18051
18051
  "workflowList",
18052
+ "workflowNodeCatalog",
18052
18053
  "workflowDetail",
18053
18054
  "workflowRunList",
18054
18055
  "workflowRunDetail",
@@ -18058,6 +18059,10 @@ var cliResultTypeSchema = external_exports.enum([
18058
18059
  "scheduleOccurrenceList",
18059
18060
  "scheduleExecutionList",
18060
18061
  "scheduleAction",
18062
+ "dataBaseGlob",
18063
+ "dataBaseRead",
18064
+ "dataBaseGrep",
18065
+ "dataBaseSearch",
18061
18066
  "generic"
18062
18067
  ]);
18063
18068
  var cliResultSchema = external_exports.object({
@@ -20592,6 +20597,36 @@ var commandDefinitions = [
20592
20597
  "spira workflows runs list <workflowId>"
20593
20598
  ]
20594
20599
  },
20600
+ {
20601
+ path: ["workflows", "node-catalog"],
20602
+ purpose: "Show the current server workflow node catalog.",
20603
+ options: [
20604
+ {
20605
+ name: "surface",
20606
+ flag: "--surface <surface>",
20607
+ description: "Workflow access surface.",
20608
+ type: "string",
20609
+ enumValues: ["dashboard", "admin"],
20610
+ defaultValue: "dashboard"
20611
+ }
20612
+ ],
20613
+ http: {
20614
+ method: "GET",
20615
+ path: "/api/cli/v1/workflows/node-catalog",
20616
+ authRequired: true,
20617
+ query: [{ name: "surface", source: "option", from: "surface" }]
20618
+ },
20619
+ resultType: "workflowNodeCatalog",
20620
+ examples: [
20621
+ "spira workflows node-catalog --json",
20622
+ "spira workflows node-catalog --surface admin --json"
20623
+ ],
20624
+ relatedCommands: [
20625
+ "spira workflows create --name <name>",
20626
+ "spira workflows update <workflowId>",
20627
+ "spira workflows get <workflowId>"
20628
+ ]
20629
+ },
20595
20630
  {
20596
20631
  path: ["workflows", "create"],
20597
20632
  purpose: "Create a canvas workflow.",
@@ -20869,6 +20904,105 @@ var commandDefinitions = [
20869
20904
  resultType: "scheduleOccurrenceList",
20870
20905
  examples: ["spira schedules calendar --from 1781884800000 --to 1782490000000"],
20871
20906
  relatedCommands: ["spira schedules get <scheduleId>"]
20907
+ },
20908
+ {
20909
+ path: ["dataBase", "glob"],
20910
+ purpose: "List files and folders in your Brand database matching a glob pattern.",
20911
+ arguments: [{ name: "pattern", description: "Glob pattern (default: /**).", required: false }],
20912
+ http: {
20913
+ method: "GET",
20914
+ path: "/api/cli/v1/data-base/glob",
20915
+ authRequired: true,
20916
+ query: [{ name: "pattern", source: "argument", from: "pattern" }]
20917
+ },
20918
+ resultType: "dataBaseGlob",
20919
+ examples: ["spira dataBase glob", "spira dataBase glob '/docs/**'", "spira dataBase glob '/**/*.md'"],
20920
+ relatedCommands: ["spira dataBase read <path>", 'spira dataBase search "<query>"']
20921
+ },
20922
+ {
20923
+ path: ["dataBase", "read"],
20924
+ purpose: "Read file content at a Brand database path with optional line/page slicing.",
20925
+ arguments: [{ name: "path", description: "Brand database path (e.g. /docs/style-guide.md).", required: true }],
20926
+ options: [
20927
+ { name: "line", flag: "--line <number>", description: "Start line (1-based).", type: "number" },
20928
+ { name: "lineEnd", flag: "--line-end <number>", description: "End line (inclusive).", type: "number" },
20929
+ { name: "page", flag: "--page <number>", description: "Start page (1-based, PDFs).", type: "number" },
20930
+ { name: "pageEnd", flag: "--page-end <number>", description: "End page (inclusive).", type: "number" },
20931
+ { name: "expand", flag: "--expand <number>", description: "Extra lines of context around the range.", type: "number" }
20932
+ ],
20933
+ http: {
20934
+ method: "GET",
20935
+ path: "/api/cli/v1/data-base/read",
20936
+ authRequired: true,
20937
+ query: [
20938
+ { name: "path", source: "argument", from: "path" },
20939
+ { name: "line", source: "option", from: "line" },
20940
+ { name: "lineEnd", source: "option", from: "lineEnd" },
20941
+ { name: "page", source: "option", from: "page" },
20942
+ { name: "pageEnd", source: "option", from: "pageEnd" },
20943
+ { name: "expand", source: "option", from: "expand" }
20944
+ ]
20945
+ },
20946
+ resultType: "dataBaseRead",
20947
+ examples: [
20948
+ "spira dataBase read /README.md",
20949
+ "spira dataBase read /docs/style-guide.md --line 40 --line-end 120",
20950
+ "spira dataBase read /decks/pitch.pdf --page 2 --page-end 3"
20951
+ ],
20952
+ relatedCommands: ["spira dataBase glob", 'spira dataBase search "<query>"']
20953
+ },
20954
+ {
20955
+ path: ["dataBase", "grep"],
20956
+ purpose: "Lexical (FTS + exact) search across your Brand database. Fast and deterministic.",
20957
+ arguments: [{ name: "query", description: "Search query.", required: true }],
20958
+ options: [
20959
+ { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20960
+ { name: "limit", flag: "--limit <number>", description: "Max matches (default 20, max 100).", type: "number" },
20961
+ { name: "debug", flag: "--debug", description: "Include candidate scoring breakdown (JSON mode).", type: "boolean" }
20962
+ ],
20963
+ http: {
20964
+ method: "POST",
20965
+ path: "/api/cli/v1/data-base/grep",
20966
+ authRequired: true,
20967
+ body: [
20968
+ { name: "query", source: "argument", from: "query" },
20969
+ { name: "scope", source: "option", from: "scope" },
20970
+ { name: "limit", source: "option", from: "limit" },
20971
+ { name: "debug", source: "option", from: "debug" }
20972
+ ]
20973
+ },
20974
+ resultType: "dataBaseGrep",
20975
+ examples: ['spira dataBase grep "brand voice"', 'spira dataBase grep "tone" --scope /docs --limit 10'],
20976
+ relatedCommands: ["spira dataBase read <path>", 'spira dataBase search "<query>"']
20977
+ },
20978
+ {
20979
+ path: ["dataBase", "search"],
20980
+ purpose: "Semantic search (pgvector + RRF + MMR) across your Brand database.",
20981
+ arguments: [{ name: "query", description: "Natural-language query.", required: true }],
20982
+ options: [
20983
+ { name: "scope", flag: "--scope <path>", description: "Restrict to a folder path.", type: "string" },
20984
+ { name: "limit", flag: "--limit <number>", description: "Max matches (default 20, max 100).", type: "number" },
20985
+ { name: "rerank", flag: "--rerank", description: "Enable LLM reranking (slower, higher quality).", type: "boolean" },
20986
+ { name: "debug", flag: "--debug", description: "Include candidate scoring breakdown (JSON mode).", type: "boolean" }
20987
+ ],
20988
+ http: {
20989
+ method: "POST",
20990
+ path: "/api/cli/v1/data-base/search",
20991
+ authRequired: true,
20992
+ body: [
20993
+ { name: "query", source: "argument", from: "query" },
20994
+ { name: "scope", source: "option", from: "scope" },
20995
+ { name: "limit", source: "option", from: "limit" },
20996
+ { name: "rerank", source: "option", from: "rerank" },
20997
+ { name: "debug", source: "option", from: "debug" }
20998
+ ]
20999
+ },
21000
+ resultType: "dataBaseSearch",
21001
+ examples: [
21002
+ 'spira dataBase search "what is our stance on price anchoring"',
21003
+ 'spira dataBase search "founder biography" --scope /about --rerank'
21004
+ ],
21005
+ relatedCommands: ["spira dataBase read <path>", 'spira dataBase grep "<query>"']
20872
21006
  }
20873
21007
  ];
20874
21008
  function findCommandDefinition(path) {
@@ -21512,6 +21646,29 @@ function formatCreditOption(option) {
21512
21646
  const when = formatWhen(option.when ?? option.conditions);
21513
21647
  return ` ${key}: ${label} - ${credits} credits ${unit}${when ? ` (${when})` : ""}`;
21514
21648
  }
21649
+ function portSummary(ports) {
21650
+ if (!Array.isArray(ports) || ports.length === 0) return "-";
21651
+ return ports.map((port) => {
21652
+ const record2 = objectRecord(port);
21653
+ const suffix = record2.multiple ? "[]" : "";
21654
+ return `${valueToText(record2.id)}:${valueToText(record2.valueType)}${suffix}`;
21655
+ }).join(", ");
21656
+ }
21657
+ function renderWorkflowNodeCatalog(data) {
21658
+ const record2 = objectRecord(data);
21659
+ const nodes = Array.isArray(record2.nodes) ? record2.nodes : [];
21660
+ const rows = nodes.map((node) => {
21661
+ const nodeRecord = objectRecord(node);
21662
+ const ports = objectRecord(nodeRecord.ports);
21663
+ return {
21664
+ type: nodeRecord.type,
21665
+ inputs: portSummary(ports.inputs),
21666
+ outputs: portSummary(ports.outputs),
21667
+ requiredPermission: nodeRecord.requiredPermission
21668
+ };
21669
+ });
21670
+ return renderRows(rows, ["type", "inputs", "outputs", "requiredPermission"]);
21671
+ }
21515
21672
  function renderAccountList(data) {
21516
21673
  const channels = data && typeof data === "object" ? data.publishChannels : null;
21517
21674
  if (Array.isArray(channels)) {
@@ -21703,6 +21860,112 @@ function renderScheduleDetail(data) {
21703
21860
  `scheduledAt: ${valueToText(currentPattern.scheduledAt)}`
21704
21861
  ].join("\n").concat("\n");
21705
21862
  }
21863
+ function formatByteSize(value) {
21864
+ const n = typeof value === "number" && Number.isFinite(value) ? value : NaN;
21865
+ if (!Number.isFinite(n) || n < 0) return "-";
21866
+ if (n < 1024) return `${n} B`;
21867
+ if (n < 1024 * 1024) return `${(n / 1024).toFixed(n < 10 * 1024 ? 1 : 0)} KB`;
21868
+ if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
21869
+ return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
21870
+ }
21871
+ function formatAnchor(anchor) {
21872
+ const parts = [];
21873
+ const line = anchor.line;
21874
+ const lineEnd = anchor.lineEnd;
21875
+ const page = anchor.page;
21876
+ const pageEnd = anchor.pageEnd;
21877
+ const timeStart = anchor.timeStartMs;
21878
+ const timeEnd = anchor.timeEndMs;
21879
+ if (typeof line === "number") {
21880
+ parts.push(typeof lineEnd === "number" ? `L${line}-${lineEnd}` : `L${line}`);
21881
+ }
21882
+ if (typeof page === "number") {
21883
+ parts.push(typeof pageEnd === "number" ? `p${page}-${pageEnd}` : `p${page}`);
21884
+ }
21885
+ if (typeof timeStart === "number") {
21886
+ parts.push(typeof timeEnd === "number" ? `t${timeStart}-${timeEnd}ms` : `t${timeStart}ms`);
21887
+ }
21888
+ return parts.join(", ") || "-";
21889
+ }
21890
+ function renderDataBaseGlob(data) {
21891
+ const record2 = objectRecord(data);
21892
+ const pattern = valueToText(record2.pattern);
21893
+ const entries = Array.isArray(record2.entries) ? record2.entries.map(objectRecord) : [];
21894
+ if (entries.length === 0) return `pattern: ${pattern}
21895
+ No entries.
21896
+ `;
21897
+ const rows = entries.map((entry) => {
21898
+ const isFolder = entry.type === "folder";
21899
+ const isVirtual = entry.virtual === true;
21900
+ return {
21901
+ type: isFolder ? "dir" : isVirtual ? "file*" : "file",
21902
+ path: entry.path,
21903
+ fileType: isFolder ? "-" : valueToText(entry.fileType),
21904
+ size: isFolder ? `${valueToText(entry.fileCount)} files` : formatByteSize(entry.size),
21905
+ status: isFolder ? "-" : valueToText(entry.processingStatus),
21906
+ updatedAt: entry.updatedAt
21907
+ };
21908
+ });
21909
+ return `pattern: ${pattern} (${entries.length} entries)
21910
+ ${renderRows(rows, ["type", "path", "fileType", "size", "status", "updatedAt"])}`;
21911
+ }
21912
+ function renderDataBaseRead(data) {
21913
+ const record2 = objectRecord(data);
21914
+ const path = valueToText(record2.path);
21915
+ const fileType = valueToText(record2.fileType);
21916
+ const mimeType = valueToText(record2.mimeType);
21917
+ const rangeObj = objectRecord(record2.range);
21918
+ let rangeStr = "-";
21919
+ if (Array.isArray(rangeObj.line) && rangeObj.line.length === 2) {
21920
+ rangeStr = `lines ${rangeObj.line[0]}-${rangeObj.line[1]}`;
21921
+ } else if (Array.isArray(rangeObj.page) && rangeObj.page.length === 2) {
21922
+ rangeStr = `pages ${rangeObj.page[0]}-${rangeObj.page[1]}`;
21923
+ }
21924
+ const totalLines = record2.totalLines !== void 0 ? valueToText(record2.totalLines) : "-";
21925
+ const truncated = valueToText(record2.truncated);
21926
+ const lines = [
21927
+ `path: ${path}`,
21928
+ `fileType: ${fileType}`,
21929
+ `mimeType: ${mimeType}`,
21930
+ `range: ${rangeStr}`,
21931
+ `totalLines: ${totalLines}`,
21932
+ `truncated: ${truncated}`
21933
+ ];
21934
+ if (record2.virtual === true) lines.push("virtual: true");
21935
+ if (record2.processingStatus) lines.push(`processingStatus: ${valueToText(record2.processingStatus)}`);
21936
+ if (record2.storageUrl) lines.push(`storageUrl: ${valueToText(record2.storageUrl)}`);
21937
+ const content = typeof record2.content === "string" ? record2.content : "";
21938
+ const header = `${lines.join("\n")}
21939
+ `;
21940
+ const nextLine = typeof record2.nextLine === "number" ? record2.nextLine : void 0;
21941
+ const footer = record2.truncated === true && nextLine ? `
21942
+ --
21943
+ (truncated; continue with: spira dataBase read ${path} --line ${nextLine})
21944
+ ` : "";
21945
+ return `${header}
21946
+ ${content}${footer}`;
21947
+ }
21948
+ function renderDataBaseMatches(data) {
21949
+ const record2 = objectRecord(data);
21950
+ const query = valueToText(record2.query);
21951
+ const matches = Array.isArray(record2.matches) ? record2.matches.map(objectRecord) : [];
21952
+ if (matches.length === 0) return `query: ${query}
21953
+ No matches.
21954
+ `;
21955
+ const blocks = matches.map((match) => {
21956
+ const anchor = formatAnchor(objectRecord(match.anchor));
21957
+ const score = typeof match.score === "number" ? match.score.toFixed(3) : valueToText(match.score);
21958
+ const fields = Array.isArray(match.matchedFields) ? match.matchedFields.join(",") : "";
21959
+ const header = `${valueToText(match.path)}(${anchor}) [score=${score}${fields ? ` fields=${fields}` : ""}]`;
21960
+ const snippet = typeof match.snippet === "string" ? match.snippet : valueToText(match.snippet);
21961
+ const indented = snippet.split("\n").map((line) => ` ${line}`).join("\n");
21962
+ return `${header}
21963
+ ${indented}`;
21964
+ });
21965
+ return `query: ${query} (${matches.length} matches)
21966
+ ${blocks.join("\n\n")}
21967
+ `;
21968
+ }
21706
21969
  function renderSuccess(result) {
21707
21970
  switch (result.resultType) {
21708
21971
  case "authLogin":
@@ -21745,6 +22008,8 @@ function renderSuccess(result) {
21745
22008
  return renderAiModelDetail(result.data);
21746
22009
  case "workflowList":
21747
22010
  return renderRows(dataArray(result.data), ["id", "name", "creatorName", "nodeTypes", "updatedAt"]);
22011
+ case "workflowNodeCatalog":
22012
+ return renderWorkflowNodeCatalog(result.data);
21748
22013
  case "workflowDetail":
21749
22014
  return renderWorkflowDetail(result.data);
21750
22015
  case "workflowRunList":
@@ -21757,6 +22022,13 @@ function renderSuccess(result) {
21757
22022
  return renderRows(dataArray(result.data), ["id", "scheduleId", "scheduledAt", "status", "executorType"]);
21758
22023
  case "scheduleExecutionList":
21759
22024
  return renderRows(dataArray(result.data), ["id", "scheduleId", "status", "originalScheduledAt", "startedAt", "finishedAt"]);
22025
+ case "dataBaseGlob":
22026
+ return renderDataBaseGlob(result.data);
22027
+ case "dataBaseRead":
22028
+ return renderDataBaseRead(result.data);
22029
+ case "dataBaseGrep":
22030
+ case "dataBaseSearch":
22031
+ return renderDataBaseMatches(result.data);
21760
22032
  default:
21761
22033
  return renderKeyValues(result.data);
21762
22034
  }
@@ -22066,8 +22338,8 @@ function renderCommandIndex() {
22066
22338
  `;
22067
22339
  }
22068
22340
  async function packageVersion() {
22069
- if ("0.0.4".length > 0) {
22070
- return "0.0.4";
22341
+ if ("0.0.5".length > 0) {
22342
+ return "0.0.5";
22071
22343
  }
22072
22344
  try {
22073
22345
  const packageUrl = new URL("../package.json", import_meta.url);
@@ -22258,7 +22530,7 @@ function addLeafCommand(root, definition, output) {
22258
22530
  for (const option of definition.options ?? []) addOption(leaf, option);
22259
22531
  leaf.action(async (...values) => {
22260
22532
  const command = values[values.length - 1];
22261
- const positional = values.slice(0, -1).map((value) => String(value));
22533
+ const positional = values.slice(0, -1).map((value) => value === void 0 ? "" : String(value));
22262
22534
  const args = Object.fromEntries((definition.arguments ?? []).map((argument, index) => [argument.name, positional[index] ?? ""]));
22263
22535
  const options = command.optsWithGlobals();
22264
22536
  const validationResult2 = validateCommandInput(definition, args, options);
package/package.json CHANGED
@@ -1,17 +1,40 @@
1
1
  {
2
2
  "name": "@spira-lab/cli",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Command line client for Spira.",
5
5
  "type": "module",
6
+ "main": "./dist/index.cjs",
6
7
  "bin": {
7
8
  "spira": "dist/index.cjs"
8
9
  },
9
- "main": "./dist/index.cjs",
10
10
  "engines": {
11
11
  "node": ">=20.0.0"
12
12
  },
13
13
  "publishConfig": {
14
14
  "access": "public",
15
15
  "registry": "https://registry.npmjs.org/"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "package.json",
20
+ "README.md"
21
+ ],
22
+ "scripts": {
23
+ "build": "pnpm --filter @spira-ai/contracts build && node scripts/build.mjs && node scripts/fix-bin-mode.mjs",
24
+ "dev": "tsx src/index.ts",
25
+ "publish:public": "node scripts/publish.mjs",
26
+ "typecheck": "tsgo --noEmit -p tsconfig.json",
27
+ "test": "vitest run"
28
+ },
29
+ "devDependencies": {
30
+ "@typescript/native-preview": "catalog:",
31
+ "@spira-ai/contracts": "workspace:*",
32
+ "@types/node": "catalog:",
33
+ "commander": "^14.0.2",
34
+ "esbuild": "^0.27.1",
35
+ "tsx": "^4.22.4",
36
+ "typescript": "catalog:",
37
+ "vitest": "catalog:",
38
+ "zod": "catalog:"
16
39
  }
17
40
  }