@norma-team/cli 0.1.0 → 0.1.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.
- package/README.md +2 -2
- package/dist/index.js +102 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,10 +14,10 @@ only does the creative work of one task at a time.** Same dependency graph, same
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm i -g @norma/cli # provides the `norma` command
|
|
17
|
+
npm i -g @norma-team/cli # provides the `norma` command
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`@norma/cli` is a self-contained binary — the whole engine, adapters, and runtimes are
|
|
20
|
+
`@norma-team/cli` is a self-contained binary — the whole engine, adapters, and runtimes are
|
|
21
21
|
bundled in.
|
|
22
22
|
|
|
23
23
|
## Try it in 30 seconds
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import { mkdir, appendFile, readFile, access, writeFile, readdir } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
4
|
+
import { join, resolve, dirname, basename, extname } from 'node:path';
|
|
4
5
|
import { execFile, spawn } from 'node:child_process';
|
|
5
6
|
import { Command } from 'commander';
|
|
6
7
|
import * as p from '@clack/prompts';
|
|
7
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
8
8
|
import { createServer } from 'node:http';
|
|
9
9
|
import { promisify } from 'node:util';
|
|
10
10
|
|
|
@@ -6473,6 +6473,67 @@ async function runPlanner(flags, r) {
|
|
|
6473
6473
|
}
|
|
6474
6474
|
return parseEpicPlan(JSON.parse(await readFile(outPath, "utf8")));
|
|
6475
6475
|
}
|
|
6476
|
+
function parseDotenv(text2) {
|
|
6477
|
+
const out = {};
|
|
6478
|
+
for (const raw of text2.split(/\r?\n/)) {
|
|
6479
|
+
const line = raw.trim();
|
|
6480
|
+
if (!line || line.startsWith("#")) continue;
|
|
6481
|
+
const eq = line.indexOf("=");
|
|
6482
|
+
if (eq === -1) continue;
|
|
6483
|
+
const key = line.slice(0, eq).trim();
|
|
6484
|
+
let val = line.slice(eq + 1).trim();
|
|
6485
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
6486
|
+
val = val.slice(1, -1);
|
|
6487
|
+
}
|
|
6488
|
+
if (key) out[key] = val;
|
|
6489
|
+
}
|
|
6490
|
+
return out;
|
|
6491
|
+
}
|
|
6492
|
+
function loadDotenv(dir = process.cwd()) {
|
|
6493
|
+
for (const name of [".env", ".env.local"]) {
|
|
6494
|
+
const path = join(dir, name);
|
|
6495
|
+
if (!existsSync(path)) continue;
|
|
6496
|
+
try {
|
|
6497
|
+
const parsed = parseDotenv(readFileSync(path, "utf8"));
|
|
6498
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
6499
|
+
if (process.env[k] === void 0) process.env[k] = v;
|
|
6500
|
+
}
|
|
6501
|
+
} catch {
|
|
6502
|
+
}
|
|
6503
|
+
}
|
|
6504
|
+
}
|
|
6505
|
+
function saveEnv(dir, entries) {
|
|
6506
|
+
const path = join(dir, ".env");
|
|
6507
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
6508
|
+
const lines = existing ? existing.split(/\r?\n/) : [];
|
|
6509
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6510
|
+
const next = lines.map((line) => {
|
|
6511
|
+
const eq = line.indexOf("=");
|
|
6512
|
+
if (eq === -1) return line;
|
|
6513
|
+
const key = line.slice(0, eq).trim();
|
|
6514
|
+
if (key in entries) {
|
|
6515
|
+
seen.add(key);
|
|
6516
|
+
return `${key}=${entries[key]}`;
|
|
6517
|
+
}
|
|
6518
|
+
return line;
|
|
6519
|
+
});
|
|
6520
|
+
for (const [k, v] of Object.entries(entries)) {
|
|
6521
|
+
if (!seen.has(k)) next.push(`${k}=${v}`);
|
|
6522
|
+
}
|
|
6523
|
+
const body = `${next.join("\n").replace(/\n+$/, "")}
|
|
6524
|
+
`;
|
|
6525
|
+
writeFileSync(path, body, "utf8");
|
|
6526
|
+
return path;
|
|
6527
|
+
}
|
|
6528
|
+
function ensureGitignoreEnv(dir) {
|
|
6529
|
+
const path = join(dir, ".gitignore");
|
|
6530
|
+
const current = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
6531
|
+
if (/^\.env(\.local)?\s*$/m.test(current) || /^\.env\*?\s*$/m.test(current)) return;
|
|
6532
|
+
const suffix = current && !current.endsWith("\n") ? "\n" : "";
|
|
6533
|
+
writeFileSync(path, `${current}${suffix}.env
|
|
6534
|
+
.env.local
|
|
6535
|
+
`, "utf8");
|
|
6536
|
+
}
|
|
6476
6537
|
|
|
6477
6538
|
// src/mapping.ts
|
|
6478
6539
|
var DEFAULT_LABELS = {
|
|
@@ -6604,6 +6665,20 @@ function check(value) {
|
|
|
6604
6665
|
if (p.isCancel(value)) bail("Cancelled.");
|
|
6605
6666
|
return value;
|
|
6606
6667
|
}
|
|
6668
|
+
async function ensureSecret(envName, label, flags, collected, masked = true) {
|
|
6669
|
+
if (process.env[envName]) return;
|
|
6670
|
+
if (flags.yes) {
|
|
6671
|
+
p.log.warn(`${envName} not set \u2014 export it (or add it to .env) to enable live access.`);
|
|
6672
|
+
return;
|
|
6673
|
+
}
|
|
6674
|
+
const val = check(
|
|
6675
|
+
masked ? await p.password({ message: `${label} (${envName})` }) : await p.text({ message: `${label} (${envName})` })
|
|
6676
|
+
);
|
|
6677
|
+
if (val) {
|
|
6678
|
+
process.env[envName] = val;
|
|
6679
|
+
collected[envName] = val;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6607
6682
|
function makeAdapter(kind, opts) {
|
|
6608
6683
|
const bounceMarker = DEFAULT_LABELS.bounceMarker;
|
|
6609
6684
|
if (kind === "linear") return new LinearAdapter({ team: opts.team, bounceMarker });
|
|
@@ -6654,6 +6729,7 @@ async function runInit(flags) {
|
|
|
6654
6729
|
})
|
|
6655
6730
|
);
|
|
6656
6731
|
const trackerOptions = {};
|
|
6732
|
+
const secrets = {};
|
|
6657
6733
|
if (trackerKind === "linear") {
|
|
6658
6734
|
trackerOptions.team = flags.yes ? flags.team ?? process.env.LINEAR_TEAM ?? "Redisco" : check(
|
|
6659
6735
|
await p.text({
|
|
@@ -6661,8 +6737,7 @@ async function runInit(flags) {
|
|
|
6661
6737
|
initialValue: flags.team ?? process.env.LINEAR_TEAM ?? ""
|
|
6662
6738
|
})
|
|
6663
6739
|
);
|
|
6664
|
-
|
|
6665
|
-
p.log.warn("LINEAR_API_KEY not set \u2014 set it to enable board introspection and runs.");
|
|
6740
|
+
await ensureSecret("LINEAR_API_KEY", "Linear API key", flags, secrets);
|
|
6666
6741
|
} else if (trackerKind === "jira") {
|
|
6667
6742
|
trackerOptions.baseUrl = flags.yes ? flags.baseUrl ?? process.env.JIRA_BASE_URL : check(
|
|
6668
6743
|
await p.text({
|
|
@@ -6676,6 +6751,8 @@ async function runInit(flags) {
|
|
|
6676
6751
|
initialValue: flags.projectKey ?? process.env.JIRA_PROJECT ?? ""
|
|
6677
6752
|
})
|
|
6678
6753
|
);
|
|
6754
|
+
await ensureSecret("JIRA_EMAIL", "Jira account email", flags, secrets, false);
|
|
6755
|
+
await ensureSecret("JIRA_API_TOKEN", "Jira API token", flags, secrets);
|
|
6679
6756
|
}
|
|
6680
6757
|
let states = [];
|
|
6681
6758
|
if (trackerKind !== "memory") {
|
|
@@ -6793,12 +6870,27 @@ async function runInit(flags) {
|
|
|
6793
6870
|
];
|
|
6794
6871
|
const specs = dedupeByRole(roles.map(([role, stage]) => specForRole(role, stage)));
|
|
6795
6872
|
const written = await writeAgents(agentsDir, specs);
|
|
6873
|
+
let envLine = null;
|
|
6874
|
+
if (Object.keys(secrets).length) {
|
|
6875
|
+
const save = flags.yes ? true : check(
|
|
6876
|
+
await p.confirm({
|
|
6877
|
+
message: `Save ${Object.keys(secrets).join(", ")} to .env (gitignored)?`,
|
|
6878
|
+
initialValue: true
|
|
6879
|
+
})
|
|
6880
|
+
);
|
|
6881
|
+
if (save) {
|
|
6882
|
+
const envPath = saveEnv(root, secrets);
|
|
6883
|
+
ensureGitignoreEnv(root);
|
|
6884
|
+
envLine = `secrets: ${Object.keys(secrets).join(", ")} \u2192 ${envPath} (gitignored)`;
|
|
6885
|
+
}
|
|
6886
|
+
}
|
|
6796
6887
|
p.note(
|
|
6797
6888
|
[
|
|
6798
6889
|
`config: ${configPath}`,
|
|
6799
6890
|
`agents: ${written.length} file(s) in ${agentsDir}`,
|
|
6800
6891
|
`tracker: ${trackerKind} \xB7 runtime: ${runtimeKind}`,
|
|
6801
|
-
`workers: ${workers.join(", ")}
|
|
6892
|
+
`workers: ${workers.join(", ")}`,
|
|
6893
|
+
...envLine ? [envLine] : []
|
|
6802
6894
|
].join("\n"),
|
|
6803
6895
|
"Generated"
|
|
6804
6896
|
);
|
|
@@ -7405,8 +7497,12 @@ function registerWorktree(program2, resolved2) {
|
|
|
7405
7497
|
}
|
|
7406
7498
|
|
|
7407
7499
|
// src/index.ts
|
|
7500
|
+
loadDotenv();
|
|
7501
|
+
var { version: VERSION } = JSON.parse(
|
|
7502
|
+
readFileSync(new URL("../package.json", import.meta.url), "utf8")
|
|
7503
|
+
);
|
|
7408
7504
|
var program = new Command();
|
|
7409
|
-
program.name("norma").description("Norma \u2014 provider-agnostic orchestration for AI agent pipelines").version(
|
|
7505
|
+
program.name("norma").description("Norma \u2014 provider-agnostic orchestration for AI agent pipelines").version(VERSION).option(
|
|
7410
7506
|
"-c, --config <path>",
|
|
7411
7507
|
"path to a norma config JSON (default: discover norma.config.json)"
|
|
7412
7508
|
).option("--tracker <kind>", "override tracker kind (linear|jira|memory)").option("--runtime <kind>", "override agent runtime (claude-code|echo)");
|