@zjaaal/artemis 0.0.0-stage → 0.1.2

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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +568 -2
  3. package/dist/cli/constants.d.ts +30 -0
  4. package/dist/cli/constants.js +34 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +1 -0
  7. package/dist/cli/init-command.d.ts +8 -0
  8. package/dist/cli/init-command.js +42 -0
  9. package/dist/cli/init.d.ts +23 -0
  10. package/dist/cli/init.js +153 -0
  11. package/dist/cli/launch.d.ts +10 -0
  12. package/dist/cli/launch.js +116 -0
  13. package/dist/cli/main.d.ts +2 -0
  14. package/dist/cli/main.js +7 -0
  15. package/dist/cli/resolve-hook.d.ts +9 -0
  16. package/dist/cli/resolve-hook.js +21 -0
  17. package/dist/cli/settings.d.ts +18 -0
  18. package/dist/cli/settings.js +110 -0
  19. package/dist/constants.d.ts +94 -0
  20. package/dist/constants.js +86 -0
  21. package/dist/context/context.d.ts +6 -0
  22. package/dist/context/context.js +35 -0
  23. package/dist/define/define.d.ts +15 -0
  24. package/dist/define/define.js +99 -0
  25. package/dist/discover/discover.d.ts +7 -0
  26. package/dist/discover/discover.js +99 -0
  27. package/dist/edges/files.d.ts +25 -0
  28. package/dist/edges/files.js +111 -0
  29. package/dist/edges/modules.d.ts +2 -0
  30. package/dist/edges/modules.js +6 -0
  31. package/dist/edges/process.d.ts +2 -0
  32. package/dist/edges/process.js +9 -0
  33. package/dist/edges/prompts.d.ts +2 -0
  34. package/dist/edges/prompts.js +14 -0
  35. package/dist/edges/system.d.ts +4 -0
  36. package/dist/edges/system.js +5 -0
  37. package/dist/errors/errors.d.ts +25 -0
  38. package/dist/errors/errors.js +37 -0
  39. package/dist/help/help.d.ts +15 -0
  40. package/dist/help/help.js +169 -0
  41. package/dist/index.d.ts +13 -0
  42. package/dist/index.js +12 -0
  43. package/dist/kits/terminal/constants.d.ts +10 -0
  44. package/dist/kits/terminal/constants.js +10 -0
  45. package/dist/kits/terminal/index.d.ts +3 -0
  46. package/dist/kits/terminal/index.js +2 -0
  47. package/dist/kits/terminal/models.d.ts +57 -0
  48. package/dist/kits/terminal/models.js +2 -0
  49. package/dist/kits/terminal/open-link.d.ts +2 -0
  50. package/dist/kits/terminal/open-link.js +12 -0
  51. package/dist/kits/terminal/open-tabs.d.ts +3 -0
  52. package/dist/kits/terminal/open-tabs.js +5 -0
  53. package/dist/kits/terminal/toml.d.ts +3 -0
  54. package/dist/kits/terminal/toml.js +53 -0
  55. package/dist/kits/terminal/warp.d.ts +6 -0
  56. package/dist/kits/terminal/warp.js +32 -0
  57. package/dist/kits/worktrees/constants.d.ts +25 -0
  58. package/dist/kits/worktrees/constants.js +25 -0
  59. package/dist/kits/worktrees/git.d.ts +2 -0
  60. package/dist/kits/worktrees/git.js +7 -0
  61. package/dist/kits/worktrees/index.d.ts +3 -0
  62. package/dist/kits/worktrees/index.js +2 -0
  63. package/dist/kits/worktrees/instances.d.ts +9 -0
  64. package/dist/kits/worktrees/instances.js +33 -0
  65. package/dist/kits/worktrees/lock-edges.d.ts +7 -0
  66. package/dist/kits/worktrees/lock-edges.js +21 -0
  67. package/dist/kits/worktrees/lock.d.ts +7 -0
  68. package/dist/kits/worktrees/lock.js +104 -0
  69. package/dist/kits/worktrees/models.d.ts +123 -0
  70. package/dist/kits/worktrees/models.js +2 -0
  71. package/dist/kits/worktrees/names.d.ts +5 -0
  72. package/dist/kits/worktrees/names.js +9 -0
  73. package/dist/kits/worktrees/queries.d.ts +20 -0
  74. package/dist/kits/worktrees/queries.js +61 -0
  75. package/dist/kits/worktrees/registration.d.ts +15 -0
  76. package/dist/kits/worktrees/registration.js +107 -0
  77. package/dist/kits/worktrees/registry-store.d.ts +3 -0
  78. package/dist/kits/worktrees/registry-store.js +27 -0
  79. package/dist/kits/worktrees/worktrees.d.ts +10 -0
  80. package/dist/kits/worktrees/worktrees.js +56 -0
  81. package/dist/models.d.ts +485 -0
  82. package/dist/models.js +1 -0
  83. package/dist/parse/parse.d.ts +8 -0
  84. package/dist/parse/parse.js +81 -0
  85. package/dist/resolve/resolve.d.ts +18 -0
  86. package/dist/resolve/resolve.js +96 -0
  87. package/dist/run/report.d.ts +6 -0
  88. package/dist/run/report.js +51 -0
  89. package/dist/run/run.d.ts +8 -0
  90. package/dist/run/run.js +163 -0
  91. package/dist/run/world.d.ts +3 -0
  92. package/dist/run/world.js +12 -0
  93. package/dist/utils/error-code.d.ts +3 -0
  94. package/dist/utils/error-code.js +7 -0
  95. package/dist/utils/error-message.d.ts +3 -0
  96. package/dist/utils/error-message.js +2 -0
  97. package/dist/utils/is-record.d.ts +3 -0
  98. package/dist/utils/is-record.js +2 -0
  99. package/dist/utils/tag.d.ts +3 -0
  100. package/dist/utils/tag.js +8 -0
  101. package/dist/utils/terminator.d.ts +5 -0
  102. package/dist/utils/terminator.js +8 -0
  103. package/package.json +72 -4
@@ -0,0 +1,96 @@
1
+ import { ALIAS_SEPARATOR, FLAG_PREFIX, HELP_WORD, ID_SEPARATOR, GLOBAL_FLAG_TOKENS, SUGGEST_LENGTH_DIVISOR, SUGGEST_MIN_DISTANCE, TERMINATOR } from '../constants.js';
2
+ /** One row of the edit-distance table, from the row above it. */
3
+ function nextRow(previous, char, target, rowNumber) {
4
+ const row = [rowNumber];
5
+ target.split('').forEach((targetChar, index) => {
6
+ const substitution = (previous[index] ?? 0) + (char === targetChar ? 0 : 1);
7
+ const deletion = (previous[index + 1] ?? 0) + 1;
8
+ const insertion = (row[index] ?? 0) + 1;
9
+ row.push(Math.min(substitution, deletion, insertion));
10
+ });
11
+ return row;
12
+ }
13
+ function editDistance(a, b) {
14
+ const firstRow = Array.from({ length: b.length + 1 }, (_, index) => index);
15
+ const lastRow = a
16
+ .split('')
17
+ .reduce((previous, char, index) => nextRow(previous, char, b, index + 1), firstRow);
18
+ return lastRow[b.length] ?? 0;
19
+ }
20
+ const maxSuggestDistance = (name) => Math.max(SUGGEST_MIN_DISTANCE, Math.floor(name.length / SUGGEST_LENGTH_DIVISOR));
21
+ /** The closest command, topic or alias name, if one is close enough to be a likely typo. */
22
+ export const suggest = (registry, name) => {
23
+ const names = [
24
+ ...registry.commands.keys(),
25
+ ...registry.topics.keys(),
26
+ ...registry.aliases.keys()
27
+ ];
28
+ const [best] = names
29
+ .map((candidate) => ({ candidate, score: editDistance(name, candidate) }))
30
+ .sort((a, b) => a.score - b.score);
31
+ return best && best.score <= maxSuggestDistance(name) ? best.candidate : undefined;
32
+ };
33
+ /** A command by id or by alias. */
34
+ export const findCommand = (registry, id) => {
35
+ const aliased = registry.aliases.get(id);
36
+ return registry.commands.get(id) ?? (aliased ? registry.commands.get(aliased) : undefined);
37
+ };
38
+ const isFlagToken = (token) => token.startsWith(FLAG_PREFIX);
39
+ /** The words that can name a command: everything before the first flag. */
40
+ function leadingWords(tokens) {
41
+ const firstFlag = tokens.findIndex(isFlagToken);
42
+ return firstFlag === -1 ? tokens : tokens.slice(0, firstFlag);
43
+ }
44
+ // "uve" and "uve help" both show the topic; anything else after a topic is unknown
45
+ const isHelpAfterTopic = (words, length) => length === words.length - 1 && words[length] === HELP_WORD;
46
+ const isTopicOnly = (words, length) => length === words.length || isHelpAfterTopic(words, length);
47
+ function resolveTopic(topic, words, length, tokens) {
48
+ return isTopicOnly(words, length)
49
+ ? { kind: 'topic', topic, rest: tokens.slice(length) }
50
+ : { kind: 'unknown', name: words.slice(0, length + 1).join(ID_SEPARATOR) };
51
+ }
52
+ function matchLongest(registry, tokens, words) {
53
+ for (let length = words.length; length > 0; length--) {
54
+ const id = words.slice(0, length).join(ID_SEPARATOR);
55
+ const command = registry.commands.get(id);
56
+ const topic = registry.topics.get(id);
57
+ if (command) {
58
+ return { kind: 'command', entry: command, rest: tokens.slice(length) };
59
+ }
60
+ if (topic) {
61
+ return resolveTopic(topic, words, length, tokens);
62
+ }
63
+ }
64
+ return undefined;
65
+ }
66
+ /**
67
+ * Works out which command (or topic) the command line names. Words are matched greedily, so
68
+ * `uve config set http://x` is the command `uve config set` with `http://x` left as its argument.
69
+ * `uve:config:set` and declared aliases are accepted as well, and a topic followed by `help` (`uve help`)
70
+ * shows that topic.
71
+ */
72
+ export const resolve = (registry, argv) => {
73
+ const [first, ...others] = argv;
74
+ if (first === undefined) {
75
+ return { kind: 'unknown', name: '' };
76
+ }
77
+ const aliased = registry.aliases.has(first) ? findCommand(registry, first) : undefined;
78
+ if (aliased) {
79
+ return { kind: 'command', entry: aliased, rest: others };
80
+ }
81
+ const tokens = first.includes(ALIAS_SEPARATOR)
82
+ ? [...first.split(ALIAS_SEPARATOR), ...others]
83
+ : [...argv];
84
+ const words = leadingWords(tokens);
85
+ return matchLongest(registry, tokens, words) ?? { kind: 'unknown', name: words[0] ?? first };
86
+ };
87
+ export const isGlobalFlagToken = (token) => GLOBAL_FLAG_TOKENS.includes(token);
88
+ /**
89
+ * Splits off the flags typed before the command name (`art --json math add 1 2`). They belong to
90
+ * the run, not to a command; `--` ends them like it ends any flags.
91
+ */
92
+ export const splitLeadingFlags = (argv) => {
93
+ const firstWord = argv.findIndex((token) => !isFlagToken(token) || token === TERMINATOR);
94
+ const end = firstWord === -1 ? argv.length : firstWord;
95
+ return { leading: argv.slice(0, end), rest: argv.slice(end) };
96
+ };
@@ -0,0 +1,6 @@
1
+ import type { ReportErrorFn } from '../models.js';
2
+ /**
3
+ * Prints an error the way the run was asked to: a JSON object on stdout under --json, text on
4
+ * stderr otherwise. Returns the exit code.
5
+ */
6
+ export declare const reportError: ReportErrorFn;
@@ -0,0 +1,51 @@
1
+ import { EXIT_CODE, FAILURE_CODES } from '../constants.js';
2
+ import { isCommandError, isUsageError } from '../errors/errors.js';
3
+ import { messageOf } from '../utils/error-message.js';
4
+ /** The exit code of a tagged error, trusted only when it is a failure code: a failed run never exits 0. */
5
+ const failureCodeOf = (value) => FAILURE_CODES.find((code) => code === value) ?? EXIT_CODE.FAILED;
6
+ /** Splits an error into what the user sees; only a plain error (a bug) carries its stack. */
7
+ function describeError(error) {
8
+ const isExpected = isCommandError(error);
9
+ return {
10
+ message: messageOf(error),
11
+ hint: isExpected ? error.hint : undefined,
12
+ usage: isUsageError(error) ? error.usage : undefined,
13
+ stack: !isExpected && error instanceof Error ? error.stack : undefined,
14
+ exitCode: isExpected ? failureCodeOf(error.exitCode) : EXIT_CODE.FAILED
15
+ };
16
+ }
17
+ function toJson(report, commandId) {
18
+ return JSON.stringify({
19
+ ok: false,
20
+ command: commandId ?? null,
21
+ error: report.message,
22
+ ...(report.hint ? { hint: report.hint } : {})
23
+ });
24
+ }
25
+ function toText(report, { style }) {
26
+ const lines = [`${style.red('Error:')} ${report.message}`];
27
+ if (report.hint) {
28
+ lines.push(`${style.yellow('Hint:')} ${report.hint}`);
29
+ }
30
+ if (report.usage) {
31
+ lines.push('', `${style.bold('Usage')} ${report.usage}`);
32
+ }
33
+ if (report.stack) {
34
+ lines.push('', style.dim(report.stack));
35
+ }
36
+ return lines.join('\n');
37
+ }
38
+ /**
39
+ * Prints an error the way the run was asked to: a JSON object on stdout under --json, text on
40
+ * stderr otherwise. Returns the exit code.
41
+ */
42
+ export const reportError = (error, context) => {
43
+ const report = describeError(error);
44
+ if (context.json) {
45
+ context.io.stdout.write(`${toJson(report, context.commandId)}\n`);
46
+ }
47
+ else {
48
+ context.io.stderr.write(`${toText(report, context)}\n`);
49
+ }
50
+ return report.exitCode;
51
+ };
@@ -0,0 +1,8 @@
1
+ import type { RunCliFn } from '../models.js';
2
+ /**
3
+ * Runs a command line and resolves to the exit code; it never calls process.exit, so it can be
4
+ * embedded and tested. Exit codes: 0 success, 1 the command failed, 2 the command line was wrong.
5
+ * With --json every call prints one object on stdout: `{ ok: true, command, result }` or
6
+ * `{ ok: false, command, error, hint }`.
7
+ */
8
+ export declare const run: RunCliFn;
@@ -0,0 +1,163 @@
1
+ import { createContext } from '../context/context.js';
2
+ import { EXIT_CODE, FLAG_PREFIX, HELP_WORD, ID_SEPARATOR } from '../constants.js';
3
+ import { discover } from '../discover/discover.js';
4
+ import { CommandError, UsageError, isUsageError } from '../errors/errors.js';
5
+ import { commandHelp, manifest, rootHelp, topicHelp, topicManifest, usage } from '../help/help.js';
6
+ import { parse } from '../parse/parse.js';
7
+ import { reportError } from './report.js';
8
+ import { resolveWorld } from './world.js';
9
+ import { findCommand, isGlobalFlagToken, resolve, splitLeadingFlags, suggest } from '../resolve/resolve.js';
10
+ import { jsonRequested } from '../utils/terminator.js';
11
+ const isFlagToken = (token) => token.startsWith(FLAG_PREFIX);
12
+ /** Prints text, or under --json the data wrapped in the success envelope. */
13
+ function print(session, text, data) {
14
+ const output = session.wantsJson
15
+ ? JSON.stringify({ ok: true, command: session.state.commandId ?? null, result: data })
16
+ : text;
17
+ session.io.stdout.write(`${output}\n`);
18
+ }
19
+ const unknownHint = (bin, registry, name) => {
20
+ const match = suggest(registry, name);
21
+ if (match) {
22
+ return `Did you mean "${match}"?`;
23
+ }
24
+ // Inside a topic, point at that topic's list rather than the whole CLI
25
+ const topic = name.split(ID_SEPARATOR).slice(0, -1).join(ID_SEPARATOR);
26
+ const listing = registry.topics.has(topic) ? `${bin} ${topic} help` : `${bin} help`;
27
+ return `Run "${listing}" to see the commands.`;
28
+ };
29
+ const unknownCommand = ({ bin, registry }, name) => new UsageError(`Unknown command "${name}".`, { hint: unknownHint(bin, registry, name) });
30
+ /** A bad call to `ctx.invoke` is the calling command's bug, not a wrong command line: exit 1. */
31
+ function parseNested(session, target, id, argv) {
32
+ try {
33
+ const nested = parse(target.definition, argv, usage(session.bin, target));
34
+ if (nested.global.help) {
35
+ throw new CommandError(`ctx.invoke("${id}"): --help is not something a command can invoke`, {
36
+ hint: `Call it with its arguments, or read its help with "${session.bin} help ${id}".`
37
+ });
38
+ }
39
+ return nested;
40
+ }
41
+ catch (error) {
42
+ if (!isUsageError(error)) {
43
+ throw error;
44
+ }
45
+ throw new CommandError(`ctx.invoke("${id}"): ${error.message}`, {
46
+ hint: error.usage ? `Usage: ${error.usage}` : error.hint,
47
+ cause: error
48
+ });
49
+ }
50
+ }
51
+ /** `ctx.invoke`: runs another command, which inherits --json and --yes from the caller. */
52
+ const createInvoke = (session, caller) => async (id, argv = []) => {
53
+ const target = findCommand(session.registry, id);
54
+ if (!target) {
55
+ throw new CommandError(`ctx.invoke: there is no command "${id}"`);
56
+ }
57
+ const nested = parseNested(session, target, id, argv);
58
+ const global = {
59
+ ...nested.global,
60
+ json: nested.global.json || caller.json,
61
+ yes: nested.global.yes || caller.yes
62
+ };
63
+ return execute(session, target, { ...nested, global });
64
+ };
65
+ async function execute(session, entry, parsed) {
66
+ const ctx = createContext({
67
+ io: session.io,
68
+ global: parsed.global,
69
+ // --json promises exactly one object on stdout, so nothing may prompt there either
70
+ interactive: session.canPrompt && !parsed.global.yes && !parsed.global.json,
71
+ style: session.style,
72
+ registry: session.registry,
73
+ invoke: createInvoke(session, parsed.global)
74
+ });
75
+ return entry.definition.run({ args: parsed.args, flags: parsed.flags, ctx });
76
+ }
77
+ /** The text and the --json data for something that was resolved. */
78
+ function helpFor(session, target) {
79
+ const { bin, registry, style } = session;
80
+ const everything = manifest(bin, registry);
81
+ if (target.kind === 'topic') {
82
+ return {
83
+ text: topicHelp(bin, registry, target.topic, style),
84
+ data: topicManifest(everything, target.topic.id)
85
+ };
86
+ }
87
+ return {
88
+ text: commandHelp(bin, target.entry, style),
89
+ data: everything.commands.find((command) => command.id === target.entry.id)
90
+ };
91
+ }
92
+ function showHelpFor(session, words) {
93
+ if (!words.length) {
94
+ session.state.commandId = HELP_WORD;
95
+ print(session, rootHelp(session.bin, session.registry, session.style), manifest(session.bin, session.registry));
96
+ return;
97
+ }
98
+ const target = resolve(session.registry, words);
99
+ if (target.kind === 'unknown') {
100
+ throw unknownCommand(session, target.name);
101
+ }
102
+ session.state.commandId = target.kind === 'command' ? target.entry.id : target.topic.id;
103
+ const { text, data } = helpFor(session, target);
104
+ print(session, text, data);
105
+ }
106
+ /** Works out what the command line asks for and does it. */
107
+ async function dispatch(session, argv) {
108
+ const { leading, rest } = splitLeadingFlags(argv);
109
+ const stray = leading.find((token) => !isGlobalFlagToken(token));
110
+ if (stray) {
111
+ throw new UsageError(`Unknown option "${stray}"`, {
112
+ hint: `Flags go after the command: ${session.bin} <command> ${stray}`
113
+ });
114
+ }
115
+ const [first] = rest;
116
+ if (first === undefined || first === HELP_WORD) {
117
+ showHelpFor(session, rest.slice(1).filter((token) => !isFlagToken(token)));
118
+ return EXIT_CODE.OK;
119
+ }
120
+ const target = resolve(session.registry, rest);
121
+ if (target.kind === 'unknown') {
122
+ throw unknownCommand(session, target.name);
123
+ }
124
+ if (target.kind === 'topic') {
125
+ showHelpFor(session, target.topic.id.split(ID_SEPARATOR));
126
+ return EXIT_CODE.OK;
127
+ }
128
+ session.state.commandId = target.entry.id;
129
+ const parsed = parse(target.entry.definition, [...leading, ...target.rest], usage(session.bin, target.entry));
130
+ if (parsed.global.help) {
131
+ showHelpFor(session, target.entry.id.split(ID_SEPARATOR));
132
+ return EXIT_CODE.OK;
133
+ }
134
+ const result = await execute(session, target.entry, parsed);
135
+ if (session.wantsJson) {
136
+ print(session, '', result ?? null);
137
+ }
138
+ return EXIT_CODE.OK;
139
+ }
140
+ /**
141
+ * Runs a command line and resolves to the exit code; it never calls process.exit, so it can be
142
+ * embedded and tested. Exit codes: 0 success, 1 the command failed, 2 the command line was wrong.
143
+ * With --json every call prints one object on stdout: `{ ok: true, command, result }` or
144
+ * `{ ok: false, command, error, hint }`.
145
+ */
146
+ export const run = async (argv, options) => {
147
+ const world = { ...resolveWorld(options), bin: options.bin };
148
+ const wantsJson = jsonRequested(argv);
149
+ const state = { commandId: undefined };
150
+ try {
151
+ const registry = await discover(options.commandsDir);
152
+ registry.warnings.forEach((warning) => world.io.stderr.write(`${world.style.yellow('warning:')} ${warning}\n`));
153
+ return await dispatch({ ...world, registry, wantsJson, state }, argv);
154
+ }
155
+ catch (error) {
156
+ return reportError(error, {
157
+ io: world.io,
158
+ style: world.style,
159
+ json: wantsJson,
160
+ commandId: state.commandId
161
+ });
162
+ }
163
+ };
@@ -0,0 +1,3 @@
1
+ import type { ResolveWorldFn } from '../models.js';
2
+ /** Works out where output goes, whether to color and whether prompts may show. */
3
+ export declare const resolveWorld: ResolveWorldFn;
@@ -0,0 +1,12 @@
1
+ import { DEFAULT_COLOR_ENV } from '../constants.js';
2
+ import { currentProcess } from '../edges/process.js';
3
+ import { createStyle } from '../help/help.js';
4
+ /** Works out where output goes, whether to color and whether prompts may show. */
5
+ export const resolveWorld = (options) => {
6
+ const system = currentProcess();
7
+ const io = { stdout: options.stdout ?? system.stdout, stderr: options.stderr ?? system.stderr };
8
+ const env = options.env ?? system.env;
9
+ const color = options.color ?? (Boolean(io.stdout.isTTY) && !env[DEFAULT_COLOR_ENV]);
10
+ const canPrompt = options.interactive ?? (system.stdinIsTTY && system.stdoutIsTTY);
11
+ return { io, style: createStyle(color), canPrompt };
12
+ };
@@ -0,0 +1,3 @@
1
+ import type { ErrorCodeFn } from '../models.js';
2
+ /** The `code` of a Node system error ("ENOENT", "EEXIST", ...), if the error has one. */
3
+ export declare const codeOf: ErrorCodeFn;
@@ -0,0 +1,7 @@
1
+ /** The `code` of a Node system error ("ENOENT", "EEXIST", ...), if the error has one. */
2
+ export const codeOf = (error) => {
3
+ if (!(error instanceof Error) || !('code' in error)) {
4
+ return undefined;
5
+ }
6
+ return typeof error.code === 'string' ? error.code : undefined;
7
+ };
@@ -0,0 +1,3 @@
1
+ import type { MessageOfFn } from '../models.js';
2
+ /** The message of whatever was thrown: an Error's message, or the value as text. */
3
+ export declare const messageOf: MessageOfFn;
@@ -0,0 +1,2 @@
1
+ /** The message of whatever was thrown: an Error's message, or the value as text. */
2
+ export const messageOf = (error) => error instanceof Error ? error.message : String(error);
@@ -0,0 +1,3 @@
1
+ import type { IsRecordFn } from '../models.js';
2
+ /** A plain object: not null, not an array. */
3
+ export declare const isRecord: IsRecordFn;
@@ -0,0 +1,2 @@
1
+ /** A plain object: not null, not an array. */
2
+ export const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -0,0 +1,3 @@
1
+ import type { IsTaggedFn } from '../models.js';
2
+ /** Whether a value carries a `Symbol.for` tag, which survives two copies of the package being loaded. */
3
+ export declare const hasTag: IsTaggedFn;
@@ -0,0 +1,8 @@
1
+ /** Whether a value carries a `Symbol.for` tag, which survives two copies of the package being loaded. */
2
+ export const hasTag = (value, tag) => {
3
+ if (typeof value !== 'object' || value === null) {
4
+ return false;
5
+ }
6
+ const found = Reflect.get(value, tag);
7
+ return Boolean(found);
8
+ };
@@ -0,0 +1,5 @@
1
+ import type { BeforeTerminatorFn, JsonRequestedFn } from '../models.js';
2
+ /** The words before `--`: after it everything is a plain argument, never a flag. */
3
+ export declare const beforeTerminator: BeforeTerminatorFn;
4
+ /** Whether the command line asks for `--json`. After `--` it is a plain argument, so it does not count. */
5
+ export declare const jsonRequested: JsonRequestedFn;
@@ -0,0 +1,8 @@
1
+ import { JSON_FLAG, TERMINATOR } from '../constants.js';
2
+ /** The words before `--`: after it everything is a plain argument, never a flag. */
3
+ export const beforeTerminator = (argv) => {
4
+ const end = argv.indexOf(TERMINATOR);
5
+ return argv.slice(0, end === -1 ? argv.length : end);
6
+ };
7
+ /** Whether the command line asks for `--json`. After `--` it is a plain argument, so it does not count. */
8
+ export const jsonRequested = (argv) => beforeTerminator(argv).includes(JSON_FLAG);
package/package.json CHANGED
@@ -1,6 +1,74 @@
1
1
  {
2
2
  "name": "@zjaaal/artemis",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.2",
4
+ "description": "Drop one file in a folder and get a CLI command. A command framework agents can write and read.",
5
+ "type": "module",
6
+ "packageManager": "pnpm@10.34.7",
7
+ "engines": {
8
+ "node": ">=22"
9
+ },
10
+ "bin": {
11
+ "art": "dist/cli/main.js",
12
+ "artemis": "dist/cli/main.js"
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./worktrees": {
26
+ "types": "./dist/kits/worktrees/index.d.ts",
27
+ "import": "./dist/kits/worktrees/index.js"
28
+ },
29
+ "./terminal": {
30
+ "types": "./dist/kits/terminal/index.d.ts",
31
+ "import": "./dist/kits/terminal/index.js"
32
+ },
33
+ "./cli": {
34
+ "types": "./dist/cli/index.d.ts",
35
+ "import": "./dist/cli/index.js"
36
+ }
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/zJaaal/artemis.git"
41
+ },
42
+ "license": "MIT",
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.build.json",
45
+ "test": "vitest run",
46
+ "test:all": "tsc --noEmit && eslint . && prettier --check . && vitest run",
47
+ "check-exports": "node scripts/check-exports.mjs",
48
+ "check-lock": "node scripts/check-lock.mjs",
49
+ "check-bin": "node scripts/check-bin.mjs"
50
+ },
51
+ "dependencies": {
52
+ "prompts": "2.4.2"
53
+ },
54
+ "peerDependencies": {
55
+ "@types/prompts": "^2.4.9"
56
+ },
57
+ "peerDependenciesMeta": {
58
+ "@types/prompts": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "22.20.5",
64
+ "@types/prompts": "2.4.9",
65
+ "@vitest/eslint-plugin": "1.6.27",
66
+ "eslint": "9.39.5",
67
+ "eslint-config-prettier": "10.1.8",
68
+ "eslint-plugin-prettier": "5.5.7",
69
+ "prettier": "3.9.10",
70
+ "typescript": "6.0.3",
71
+ "typescript-eslint": "8.72.0",
72
+ "vitest": "4.1.12"
73
+ }
74
+ }